diff --git a/core/pom.xml b/core/pom.xml
index 1efc45d4f4..5eb71d92e0 100644
--- a/core/pom.xml
+++ b/core/pom.xml
@@ -7,7 +7,7 @@
org.alfresco
alfresco-community-repo
- 7.301-SNAPSHOT
+ 7.308-SNAPSHOT
diff --git a/data-model/pom.xml b/data-model/pom.xml
index 8c6a5095d2..60b9399430 100644
--- a/data-model/pom.xml
+++ b/data-model/pom.xml
@@ -7,7 +7,7 @@
org.alfresco
alfresco-community-repo
- 7.301-SNAPSHOT
+ 7.308-SNAPSHOT
diff --git a/packaging/distribution/pom.xml b/packaging/distribution/pom.xml
index 8ffad77639..9ae9b0e0b1 100644
--- a/packaging/distribution/pom.xml
+++ b/packaging/distribution/pom.xml
@@ -9,6 +9,6 @@
org.alfresco
alfresco-community-repo-packaging
- 7.301-SNAPSHOT
+ 7.308-SNAPSHOT
diff --git a/packaging/docker-alfresco/pom.xml b/packaging/docker-alfresco/pom.xml
index b596dfd585..12f35e3498 100644
--- a/packaging/docker-alfresco/pom.xml
+++ b/packaging/docker-alfresco/pom.xml
@@ -7,7 +7,7 @@
org.alfresco
alfresco-community-repo-packaging
- 7.301-SNAPSHOT
+ 7.308-SNAPSHOT
diff --git a/packaging/pom.xml b/packaging/pom.xml
index 83a413180f..fed3d07726 100644
--- a/packaging/pom.xml
+++ b/packaging/pom.xml
@@ -7,7 +7,7 @@
org.alfresco
alfresco-community-repo
- 7.301-SNAPSHOT
+ 7.308-SNAPSHOT
diff --git a/packaging/tests/pom.xml b/packaging/tests/pom.xml
index 9272137d29..ba6d966439 100644
--- a/packaging/tests/pom.xml
+++ b/packaging/tests/pom.xml
@@ -6,7 +6,7 @@
org.alfresco
alfresco-community-repo-packaging
- 7.301-SNAPSHOT
+ 7.308-SNAPSHOT
diff --git a/packaging/tests/tas-cmis/pom.xml b/packaging/tests/tas-cmis/pom.xml
index fbf6f75456..dbfc3970ce 100644
--- a/packaging/tests/tas-cmis/pom.xml
+++ b/packaging/tests/tas-cmis/pom.xml
@@ -9,7 +9,7 @@
org.alfresco
alfresco-community-repo-tests
- 7.301-SNAPSHOT
+ 7.308-SNAPSHOT
diff --git a/packaging/tests/tas-email/pom.xml b/packaging/tests/tas-email/pom.xml
index 9cfe335543..795a6da3e7 100644
--- a/packaging/tests/tas-email/pom.xml
+++ b/packaging/tests/tas-email/pom.xml
@@ -9,7 +9,7 @@
org.alfresco
alfresco-community-repo-tests
- 7.301-SNAPSHOT
+ 7.308-SNAPSHOT
diff --git a/packaging/tests/tas-integration/pom.xml b/packaging/tests/tas-integration/pom.xml
index c785ff0aa8..1d9b148844 100644
--- a/packaging/tests/tas-integration/pom.xml
+++ b/packaging/tests/tas-integration/pom.xml
@@ -9,7 +9,7 @@
org.alfresco
alfresco-community-repo-tests
- 7.301-SNAPSHOT
+ 7.308-SNAPSHOT
diff --git a/packaging/tests/tas-restapi/pom.xml b/packaging/tests/tas-restapi/pom.xml
index 27719019eb..771f20488b 100644
--- a/packaging/tests/tas-restapi/pom.xml
+++ b/packaging/tests/tas-restapi/pom.xml
@@ -9,7 +9,7 @@
org.alfresco
alfresco-community-repo-tests
- 7.301-SNAPSHOT
+ 7.308-SNAPSHOT
diff --git a/packaging/tests/tas-webdav/pom.xml b/packaging/tests/tas-webdav/pom.xml
index a75a12dc9b..d4ac8753a8 100644
--- a/packaging/tests/tas-webdav/pom.xml
+++ b/packaging/tests/tas-webdav/pom.xml
@@ -9,7 +9,7 @@
org.alfresco
alfresco-community-repo-tests
- 7.301-SNAPSHOT
+ 7.308-SNAPSHOT
diff --git a/packaging/war/pom.xml b/packaging/war/pom.xml
index 252c14e3ce..aef949e5ac 100644
--- a/packaging/war/pom.xml
+++ b/packaging/war/pom.xml
@@ -7,7 +7,7 @@
org.alfresco
alfresco-community-repo-packaging
- 7.301-SNAPSHOT
+ 7.308-SNAPSHOT
diff --git a/pom.xml b/pom.xml
index 511f659d2c..a55f391504 100644
--- a/pom.xml
+++ b/pom.xml
@@ -2,7 +2,7 @@
4.0.0
alfresco-community-repo
- 7.301-SNAPSHOT
+ 7.308-SNAPSHOT
pom
Alfresco Community Repo Parent
diff --git a/remote-api/pom.xml b/remote-api/pom.xml
index 106c7f4794..27937591aa 100644
--- a/remote-api/pom.xml
+++ b/remote-api/pom.xml
@@ -7,7 +7,7 @@
org.alfresco
alfresco-community-repo
- 7.301-SNAPSHOT
+ 7.308-SNAPSHOT
diff --git a/remote-api/src/main/java/org/alfresco/rest/api/impl/NodesImpl.java b/remote-api/src/main/java/org/alfresco/rest/api/impl/NodesImpl.java
index f46825c593..12a8f3df72 100644
--- a/remote-api/src/main/java/org/alfresco/rest/api/impl/NodesImpl.java
+++ b/remote-api/src/main/java/org/alfresco/rest/api/impl/NodesImpl.java
@@ -1831,6 +1831,19 @@ public class NodesImpl implements Nodes
{
versionMajor = Boolean.valueOf(str);
}
+ String versioningEnabledStringValue = parameters.getParameter("versioningEnabled");
+ if (null != versioningEnabledStringValue)
+ {
+ boolean versioningEnabled = Boolean.parseBoolean(versioningEnabledStringValue);
+ if (versioningEnabled)
+ {
+ versionMajor = (null != versionMajor) ? versionMajor : true;
+ }
+ else
+ {
+ versionMajor = null;
+ }
+ }
String versionComment = parameters.getParameter(PARAM_VERSION_COMMENT);
// Create the node
@@ -2331,6 +2344,11 @@ public class NodesImpl implements Nodes
private void handleNodeRename(Map props, NodeRef nodeRef)
{
Serializable nameProp = props.get(ContentModel.PROP_NAME);
+ handleNodeRename(nameProp, nodeRef);
+ }
+
+ private void handleNodeRename(Serializable nameProp, NodeRef nodeRef)
+ {
if ((nameProp != null))
{
String currentName = (String) nodeService.getProperty(nodeRef, ContentModel.PROP_NAME);
@@ -2705,6 +2723,7 @@ public class NodesImpl implements Nodes
String fileName = parameters.getParameter(PARAM_NAME);
if (fileName != null)
{
+ handleNodeRename(fileName, nodeRef);
// optionally rename, before updating the content
nodeService.setProperty(nodeRef, ContentModel.PROP_NAME, fileName);
}
@@ -2891,6 +2910,7 @@ public class NodesImpl implements Nodes
String versionComment = null;
String relativePath = null;
String renditionNames = null;
+ boolean versioningEnabled = true;
Map qnameStrProps = new HashMap<>();
Map properties = null;
@@ -2947,6 +2967,19 @@ public class NodesImpl implements Nodes
case "renditions":
renditionNames = getStringOrNull(field.getValue());
break;
+ case "versioningenabled":
+ String versioningEnabledStringValue = getStringOrNull(field.getValue());
+ if (null != versioningEnabledStringValue)
+ {
+ // MNT-22036 versioningenabled parameter was added to disable versioning of newly created nodes.
+ // The default API mechanism should not be changed/affected.
+ // Versioning is enabled by default when creating a node using form-data.
+ // To preserve this, versioningEnabled value must be 'true' for any given value typo/valuesNotSupported (except case-insensitive 'false')
+ // .equalsIgnoreCase("false") will return true only when the input value is 'false'
+ // !.equalsIgnoreCase("false") will return false only when the input value is 'false'
+ versioningEnabled = !versioningEnabledStringValue.equalsIgnoreCase("false");
+ }
+ break;
default:
{
@@ -3019,12 +3052,14 @@ public class NodesImpl implements Nodes
throw new ConstraintViolatedException(fileName + " already exists.");
}
}
-
+
// Note: pending REPO-159, we currently auto-enable versioning on new upload (but not when creating empty file)
if (versionMajor == null)
{
versionMajor = true;
}
+ // MNT-22036 add versioningEnabled property for newly created nodes.
+ versionMajor = versioningEnabled ? versionMajor : null;
// Create a new file.
NodeRef nodeRef = createNewFile(parentNodeRef, fileName, nodeTypeQName, content, properties, assocTypeQName, parameters, versionMajor, versionComment);
diff --git a/remote-api/src/test/java/org/alfresco/rest/api/tests/NodeApiTest.java b/remote-api/src/test/java/org/alfresco/rest/api/tests/NodeApiTest.java
index 6e14d98b0c..20800724cf 100644
--- a/remote-api/src/test/java/org/alfresco/rest/api/tests/NodeApiTest.java
+++ b/remote-api/src/test/java/org/alfresco/rest/api/tests/NodeApiTest.java
@@ -5605,6 +5605,279 @@ public class NodeApiTest extends AbstractSingleNetworkSiteTest
assertTrue(((ArrayList) (propUpdateResponse.get("custom:locations"))).size() == 1);
}
+ @Test
+ public void versioningEnabledMultipartNodeCreationTest() throws Exception
+ {
+ setRequestContext(user1);
+ String myNodeId = getMyNodeId();
+ // Test Scenarios:
+ // 1: majorVersion not set - versioningEnabled not set Expect: MAJOR version
+ // 2: majorVersion not set - versioningEnabled false Expect: versioning disabled
+ // 3: majorVersion true - versioningEnabled false Expect: versioning disabled
+ // 4: majorVersion false - versioningEnabled false Expect: versioning disabled
+ // 5: majorVersion not set - versioningEnabled true Expect: MAJOR version
+ // 6: majorVersion true - versioningEnabled true Expect: MAJOR version
+ // 7: majorVersion false - versioningEnabled true Expect: Minor version
+ // 8: majorVersion not set - versioningEnabled False Expect: versioning disabled
+ // 9: majorVersion not set - versioningEnabled invalid Expect: MAJOR version
+
+ // Scenario 1:
+ String fileName = "myfile" + UUID.randomUUID() + ".txt";
+ File file = getResourceFile("quick-2.pdf");
+ MultiPartBuilder multiPartBuilder = MultiPartBuilder.create().setFileData(new FileData(fileName, file));
+
+ MultiPartRequest reqBody = multiPartBuilder.build();
+ HttpResponse response = post(getNodeChildrenUrl(myNodeId), reqBody.getBody(), null, reqBody.getContentType(), 201);
+ Document documentResponse = RestApiUtil.parseRestApiEntry(response.getJsonResponse(), Document.class);
+
+ // Default behaviour, expect to be MAJOR Version 1.0
+ Map documentProperties = documentResponse.getProperties();
+ assertEquals("MAJOR", documentProperties.get("cm:versionType"));
+ assertEquals("1.0", documentProperties.get("cm:versionLabel"));
+
+ // Scenario 2:
+ fileName = "myfile" + UUID.randomUUID() + ".txt";
+ multiPartBuilder = MultiPartBuilder.create().setFileData(new FileData(fileName, file));
+ multiPartBuilder.setVersioningEnabled("false");
+
+ reqBody = multiPartBuilder.build();
+ response = post(getNodeChildrenUrl(myNodeId), reqBody.getBody(), null, reqBody.getContentType(), 201);
+ documentResponse = RestApiUtil.parseRestApiEntry(response.getJsonResponse(), Document.class);
+
+ documentProperties = documentResponse.getProperties();
+ assertNull(documentProperties);
+
+ // Scenario 3:
+ fileName = "myfile" + UUID.randomUUID() + ".txt";
+ multiPartBuilder = MultiPartBuilder.create().setFileData(new FileData(fileName, file));
+ multiPartBuilder.setMajorVersion(true);
+ multiPartBuilder.setVersioningEnabled("false");
+
+ reqBody = multiPartBuilder.build();
+ response = post(getNodeChildrenUrl(myNodeId), reqBody.getBody(), null, reqBody.getContentType(), 201);
+ documentResponse = RestApiUtil.parseRestApiEntry(response.getJsonResponse(), Document.class);
+
+ documentProperties = documentResponse.getProperties();
+ assertNull(documentProperties);
+
+ // Scenario 4:
+ fileName = "myfile" + UUID.randomUUID() + ".txt";
+ multiPartBuilder = MultiPartBuilder.create().setFileData(new FileData(fileName, file));
+ multiPartBuilder.setMajorVersion(false);
+ multiPartBuilder.setVersioningEnabled("false");
+
+ reqBody = multiPartBuilder.build();
+ response = post(getNodeChildrenUrl(myNodeId), reqBody.getBody(), null, reqBody.getContentType(), 201);
+ documentResponse = RestApiUtil.parseRestApiEntry(response.getJsonResponse(), Document.class);
+
+ documentProperties = documentResponse.getProperties();
+ assertNull(documentProperties);
+
+ // Scenario 5:
+ fileName = "myfile" + UUID.randomUUID() + ".txt";
+ multiPartBuilder = MultiPartBuilder.create().setFileData(new FileData(fileName, file));
+ multiPartBuilder.setVersioningEnabled("true");
+
+ reqBody = multiPartBuilder.build();
+ response = post(getNodeChildrenUrl(myNodeId), reqBody.getBody(), null, reqBody.getContentType(), 201);
+ documentResponse = RestApiUtil.parseRestApiEntry(response.getJsonResponse(), Document.class);
+
+ documentProperties = documentResponse.getProperties();
+ assertEquals("MAJOR", documentProperties.get("cm:versionType"));
+ assertEquals("1.0", documentProperties.get("cm:versionLabel"));
+
+ // Scenario 6:
+ fileName = "myfile" + UUID.randomUUID() + ".txt";
+ multiPartBuilder = MultiPartBuilder.create().setFileData(new FileData(fileName, file));
+ multiPartBuilder.setMajorVersion(true);
+ multiPartBuilder.setVersioningEnabled("true");
+
+ reqBody = multiPartBuilder.build();
+ response = post(getNodeChildrenUrl(myNodeId), reqBody.getBody(), null, reqBody.getContentType(), 201);
+ documentResponse = RestApiUtil.parseRestApiEntry(response.getJsonResponse(), Document.class);
+
+ documentProperties = documentResponse.getProperties();
+ assertEquals("MAJOR", documentProperties.get("cm:versionType"));
+ assertEquals("1.0", documentProperties.get("cm:versionLabel"));
+
+ // Scenario 7:
+ fileName = "myfile" + UUID.randomUUID() + ".txt";
+ multiPartBuilder = MultiPartBuilder.create().setFileData(new FileData(fileName, file));
+ multiPartBuilder.setMajorVersion(false);
+ multiPartBuilder.setVersioningEnabled("true");
+
+ reqBody = multiPartBuilder.build();
+ response = post(getNodeChildrenUrl(myNodeId), reqBody.getBody(), null, reqBody.getContentType(), 201);
+ documentResponse = RestApiUtil.parseRestApiEntry(response.getJsonResponse(), Document.class);
+
+ documentProperties = documentResponse.getProperties();
+ assertEquals("MINOR", documentProperties.get("cm:versionType"));
+ assertEquals("0.1", documentProperties.get("cm:versionLabel"));
+
+ // Scenario 8:
+ fileName = "myfile" + UUID.randomUUID() + ".txt";
+ multiPartBuilder = MultiPartBuilder.create().setFileData(new FileData(fileName, file));
+ multiPartBuilder.setVersioningEnabled("False");
+
+ reqBody = multiPartBuilder.build();
+ response = post(getNodeChildrenUrl(myNodeId), reqBody.getBody(), null, reqBody.getContentType(), 201);
+ documentResponse = RestApiUtil.parseRestApiEntry(response.getJsonResponse(), Document.class);
+
+ documentProperties = documentResponse.getProperties();
+ assertNull(documentProperties);
+
+ // Scenario 9:
+ fileName = "myfile" + UUID.randomUUID() + ".txt";
+ multiPartBuilder = MultiPartBuilder.create().setFileData(new FileData(fileName, file));
+ multiPartBuilder.setVersioningEnabled("invalid");
+
+ reqBody = multiPartBuilder.build();
+ response = post(getNodeChildrenUrl(myNodeId), reqBody.getBody(), null, reqBody.getContentType(), 201);
+ documentResponse = RestApiUtil.parseRestApiEntry(response.getJsonResponse(), Document.class);
+
+ documentProperties = documentResponse.getProperties();
+ assertEquals("MAJOR", documentProperties.get("cm:versionType"));
+ assertEquals("1.0", documentProperties.get("cm:versionLabel"));
+ }
+
+ @Test
+ public void versioningEnabledJSONNodeCreationTest() throws Exception
+ {
+ setRequestContext(user1);
+ String myNodeId = getMyNodeId();
+
+ // Test Scenarios:
+ // 1: majorVersion not set - versioningEnabled not set Expect: versioning disabled
+ // 2: majorVersion not set - versioningEnabled false Expect: versioning disabled
+ // 3: majorVersion true - versioningEnabled false Expect: versioning disabled
+ // 4: majorVersion false - versioningEnabled false Expect: versioning disabled
+ // 5: majorVersion not set - versioningEnabled true Expect: MAJOR version
+ // 6: majorVersion true - versioningEnabled true Expect: MAJOR version
+ // 7: majorVersion false - versioningEnabled true Expect: Minor version
+ // 8: majorVersion not set - versioningEnabled False Expect: versioning disabled
+ // 9: majorVersion not set - versioningEnabled invalid Expect: versioning disabled
+ // 10 majorVersion not set - versioningenabled true Expect: versioning disabled
+
+ Document d1 = new Document();
+ Map requestHeaders = new HashMap<>();
+
+ //Scenario 1:
+ d1.setName("testDoc" + UUID.randomUUID());
+ d1.setNodeType(TYPE_CM_CONTENT);
+
+ HttpResponse response = post(getNodeChildrenUrl(myNodeId), toJsonAsStringNonNull(d1),requestHeaders, null, null, 201);
+ Document documentResponse = RestApiUtil.parseRestApiEntry(response.getJsonResponse(), Document.class);
+
+ Map documentProperties = documentResponse.getProperties();
+ assertNull(documentProperties);
+
+ //Scenario 2:
+ d1.setName("testDoc" + UUID.randomUUID());
+ requestHeaders = new HashMap<>();
+ requestHeaders.put("versioningEnabled","false");
+
+ response = post(getNodeChildrenUrl(myNodeId), toJsonAsStringNonNull(d1),requestHeaders, null, null, 201);
+ documentResponse = RestApiUtil.parseRestApiEntry(response.getJsonResponse(), Document.class);
+
+ documentProperties = documentResponse.getProperties();
+ assertNull(documentProperties);
+
+ //Scenario 3:
+ d1.setName("testDoc" + UUID.randomUUID());
+ requestHeaders = new HashMap<>();
+ requestHeaders.put("versioningEnabled","false");
+ requestHeaders.put("majorVersion","true");
+
+ response = post(getNodeChildrenUrl(myNodeId), toJsonAsStringNonNull(d1),requestHeaders, null, null, 201);
+ documentResponse = RestApiUtil.parseRestApiEntry(response.getJsonResponse(), Document.class);
+
+ documentProperties = documentResponse.getProperties();
+ assertNull(documentProperties);
+
+ //Scenario 4:
+ d1.setName("testDoc" + UUID.randomUUID());
+ requestHeaders = new HashMap<>();
+ requestHeaders.put("versioningEnabled","false");
+ requestHeaders.put("majorVersion","false");
+
+ response = post(getNodeChildrenUrl(myNodeId), toJsonAsStringNonNull(d1),requestHeaders, null, null, 201);
+ documentResponse = RestApiUtil.parseRestApiEntry(response.getJsonResponse(), Document.class);
+
+ documentProperties = documentResponse.getProperties();
+ assertNull(documentProperties);
+
+ //Scenario 5:
+ d1.setName("testDoc" + UUID.randomUUID());
+ requestHeaders = new HashMap<>();
+ requestHeaders.put("versioningEnabled","true");
+
+ response = post(getNodeChildrenUrl(myNodeId), toJsonAsStringNonNull(d1),requestHeaders, null, null, 201);
+ documentResponse = RestApiUtil.parseRestApiEntry(response.getJsonResponse(), Document.class);
+
+ documentProperties = documentResponse.getProperties();
+ assertEquals("MAJOR", documentProperties.get("cm:versionType"));
+ assertEquals("1.0", documentProperties.get("cm:versionLabel"));
+
+ //Scenario 6:
+ d1.setName("testDoc" + UUID.randomUUID());
+ requestHeaders = new HashMap<>();
+ requestHeaders.put("versioningEnabled","true");
+ requestHeaders.put("majorVersion","true");
+
+ response = post(getNodeChildrenUrl(myNodeId), toJsonAsStringNonNull(d1),requestHeaders, null, null, 201);
+ documentResponse = RestApiUtil.parseRestApiEntry(response.getJsonResponse(), Document.class);
+
+ documentProperties = documentResponse.getProperties();
+ assertEquals("MAJOR", documentProperties.get("cm:versionType"));
+ assertEquals("1.0", documentProperties.get("cm:versionLabel"));
+
+ //Scenario 7:
+ d1.setName("testDoc" + UUID.randomUUID());
+ requestHeaders = new HashMap<>();
+ requestHeaders.put("versioningEnabled","true");
+ requestHeaders.put("majorVersion","false");
+
+ response = post(getNodeChildrenUrl(myNodeId), toJsonAsStringNonNull(d1),requestHeaders, null, null, 201);
+ documentResponse = RestApiUtil.parseRestApiEntry(response.getJsonResponse(), Document.class);
+
+ documentProperties = documentResponse.getProperties();
+ assertEquals("MINOR", documentProperties.get("cm:versionType"));
+ assertEquals("0.1", documentProperties.get("cm:versionLabel"));
+
+ //Scenario 8:
+ d1.setName("testDoc" + UUID.randomUUID());
+ requestHeaders = new HashMap<>();
+ requestHeaders.put("versioningEnabled","False");
+
+ response = post(getNodeChildrenUrl(myNodeId), toJsonAsStringNonNull(d1),requestHeaders, null, null, 201);
+ documentResponse = RestApiUtil.parseRestApiEntry(response.getJsonResponse(), Document.class);
+
+ documentProperties = documentResponse.getProperties();
+ assertNull(documentProperties);
+
+ //Scenario 9:
+ d1.setName("testDoc" + UUID.randomUUID());
+ requestHeaders = new HashMap<>();
+ requestHeaders.put("versioningEnabled","invalid");
+
+ response = post(getNodeChildrenUrl(myNodeId), toJsonAsStringNonNull(d1),requestHeaders, null, null, 201);
+ documentResponse = RestApiUtil.parseRestApiEntry(response.getJsonResponse(), Document.class);
+
+ documentProperties = documentResponse.getProperties();
+ assertNull(documentProperties);
+
+ //Scenario 10:
+ d1.setName("testDoc" + UUID.randomUUID());
+ requestHeaders = new HashMap<>();
+ requestHeaders.put("versioningenabled","true");
+
+ response = post(getNodeChildrenUrl(myNodeId), toJsonAsStringNonNull(d1),requestHeaders, null, null, 201);
+ documentResponse = RestApiUtil.parseRestApiEntry(response.getJsonResponse(), Document.class);
+
+ documentProperties = documentResponse.getProperties();
+ assertNull(documentProperties);
+ }
+
@Test public void testAuditableProperties() throws Exception
{
setRequestContext(user1);
@@ -5708,6 +5981,55 @@ public class NodeApiTest extends AbstractSingleNetworkSiteTest
assertTrue(currentPath.equals(expectedPath));
}
+ @Test
+ public void testPrimaryPathVersion() throws Exception
+ {
+ setRequestContext(user1);
+ AuthenticationUtil.setFullyAuthenticatedUser(user1);
+ String myNodeId = getMyNodeId();
+
+ // /Company Home/User Homes/user/folder_A
+ String folderName = "folder_A";
+ Folder folder = createFolder(myNodeId, folderName);
+ NodeRef folderNodeRef = new NodeRef(StoreRef.STORE_REF_WORKSPACE_SPACESSTORE, folder.getId());
+
+ // /Company Home/User Homes/user/folder_A/testDoc
+ String docName = "testDoc" + GUID.generate();
+ Document doc = new Document();
+ doc.setName(docName);
+ doc.setNodeType(TYPE_CM_CONTENT);
+ HttpResponse response = post(getNodeChildrenUrl(folderNodeRef.getId()), toJsonAsStringNonNull(doc), 201);
+ Document docResp = RestApiUtil.parseRestApiEntry(response.getJsonResponse(), Document.class);
+ NodeRef docNodeRef = new NodeRef(StoreRef.STORE_REF_WORKSPACE_SPACESSTORE, docResp.getId());
+
+ // Checks that current path and name match
+ String expectedPath1 = "/Company Home/User Homes/" + user1 + "/" + folderName + "/" + docName;
+ Path docPath1 = nodeService.getPath(docNodeRef);
+ Path.ChildAssocElement docPathLast1 = (Path.ChildAssocElement) docPath1.last();
+ String docLocalName1 = docPathLast1.getRef().getQName().getLocalName();
+ String currentPath1 = docPath1.toDisplayPath(nodeService, permissionService) + "/" + docLocalName1;
+ assertTrue(docName.equals(docLocalName1));
+ assertTrue(expectedPath1.equals(currentPath1));
+
+ // Upload document new content supplying a different name
+ String docName2 = "testDoc2" + GUID.generate();
+ Map params = new HashMap<>();
+ params.put("name", docName2);
+ Document docResp2 = updateTextFileWithRandomContent(docNodeRef.getId(), 1024L, params);
+ NodeRef docNodeRef2 = new NodeRef(StoreRef.STORE_REF_WORKSPACE_SPACESSTORE, docResp2.getId());
+
+ // Checks new path and name after new version upload
+ String expectedPath2 = "/Company Home/User Homes/" + user1 + "/" + folderName + "/" + docName2;
+ Path docPath2 = nodeService.getPath(docNodeRef2);
+ Path.ChildAssocElement docPathLast2 = (Path.ChildAssocElement) docPath2.last();
+ String docLocalName2 = docPathLast2.getRef().getQName().getLocalName();
+ String currentPath2 = docPath2.toDisplayPath(nodeService, permissionService) + "/" + docLocalName2;
+ assertFalse(docLocalName1.equals(docLocalName2));
+ assertTrue(docName2.equals(docLocalName2));
+ assertFalse(expectedPath1.equals(currentPath2));
+ assertTrue(expectedPath2.equals(currentPath2));
+ }
+
private String getDataDictionaryNodeId() throws Exception
{
Map params = new HashMap<>();
diff --git a/remote-api/src/test/java/org/alfresco/rest/api/tests/util/MultiPartBuilder.java b/remote-api/src/test/java/org/alfresco/rest/api/tests/util/MultiPartBuilder.java
index 527d5fa3d9..433caeff30 100644
--- a/remote-api/src/test/java/org/alfresco/rest/api/tests/util/MultiPartBuilder.java
+++ b/remote-api/src/test/java/org/alfresco/rest/api/tests/util/MultiPartBuilder.java
@@ -43,7 +43,6 @@ 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.
@@ -57,6 +56,7 @@ public class MultiPartBuilder
private String updateNodeRef;
private String description;
private String contentTypeQNameStr;
+ private String versioningEnabled;
private List aspects = Collections.emptyList();
private Boolean majorVersion;
private Boolean overwrite;
@@ -76,6 +76,7 @@ public class MultiPartBuilder
this.updateNodeRef = that.updateNodeRef;
this.description = that.description;
this.contentTypeQNameStr = that.contentTypeQNameStr;
+ this.versioningEnabled = that.versioningEnabled;
this.aspects = new ArrayList<>(that.aspects);
this.majorVersion = that.majorVersion;
this.overwrite = that.overwrite;
@@ -125,6 +126,12 @@ public class MultiPartBuilder
return this;
}
+ public MultiPartBuilder setVersioningEnabled(String versioningEnabled)
+ {
+ this.versioningEnabled = versioningEnabled;
+ return this;
+ }
+
public MultiPartBuilder setAspects(List aspects)
{
this.aspects = aspects;
@@ -278,6 +285,7 @@ public class MultiPartBuilder
addPartIfNotNull(parts, "updatenoderef", updateNodeRef);
addPartIfNotNull(parts, "description", description);
addPartIfNotNull(parts, "contenttype", contentTypeQNameStr);
+ addPartIfNotNull(parts, "versioningenabled", versioningEnabled);
addPartIfNotNull(parts, "aspects", getCommaSeparated(aspects));
addPartIfNotNull(parts, "majorversion", majorVersion);
addPartIfNotNull(parts, "overwrite", overwrite);
diff --git a/repository/pom.xml b/repository/pom.xml
index 79b17f6cbb..4f2ade7ee3 100644
--- a/repository/pom.xml
+++ b/repository/pom.xml
@@ -7,7 +7,7 @@
org.alfresco
alfresco-community-repo
- 7.301-SNAPSHOT
+ 7.308-SNAPSHOT
diff --git a/repository/src/main/java/org/alfresco/opencmis/search/CMISResultSetRow.java b/repository/src/main/java/org/alfresco/opencmis/search/CMISResultSetRow.java
index a833c5acfa..8f003ebb5e 100644
--- a/repository/src/main/java/org/alfresco/opencmis/search/CMISResultSetRow.java
+++ b/repository/src/main/java/org/alfresco/opencmis/search/CMISResultSetRow.java
@@ -177,13 +177,13 @@ public class CMISResultSetRow implements ResultSetRow
context.setScore(getScore());
for (Column column : query.getColumns())
{
- // When an SCORE selector is included, score must be adapted to range 0..1 due to CMIS specification
- if (column.getFunction()!= null && column.getFunction().getName().equals(Score.NAME))
- {
- return getNormalisedScore();
- }
- else if (column.getAlias().equals(columnName))
+ if (column.getAlias().equals(columnName))
{
+ //When an SCORE selector is included, score must be adapted to range 0..1 due to CMIS specification
+ if (column.getFunction()!= null && column.getFunction().getName().equals(Score.NAME))
+ {
+ return getNormalisedScore();
+ }
return column.getFunction().getValue(column.getFunctionArguments(), context);
}
// Special case for one selector - ignore any table aliases
diff --git a/repository/src/main/java/org/alfresco/repo/cache/DefaultSimpleCache.java b/repository/src/main/java/org/alfresco/repo/cache/DefaultSimpleCache.java
index 422a902dc1..376330e12e 100644
--- a/repository/src/main/java/org/alfresco/repo/cache/DefaultSimpleCache.java
+++ b/repository/src/main/java/org/alfresco/repo/cache/DefaultSimpleCache.java
@@ -1,28 +1,28 @@
-/*
- * #%L
- * Alfresco Repository
- * %%
- * 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%
- */
+/*
+ * #%L
+ * Alfresco Repository
+ * %%
+ * 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.repo.cache;
import java.io.Serializable;
@@ -150,10 +150,24 @@ public final class DefaultSimpleCache
* @return true
if the put resulted in a change in value, false
otherwise.
*/
public boolean putAndCheckUpdate(K key, V value)
+ {
+ return putAndCheckUpdate(key, value, false);
+ }
+
+ /**
+ * put
method that may be used to check for updates in a thread-safe manner.
+ *
+ * @param includeNewCheck if true then we include the new value in the check
+ * @return true
if the put resulted in a change in value,
+ * or if includeNewCheck is true and the put resulted in a new value,
+ * false
otherwise.
+ */
+ public boolean putAndCheckUpdate(K key, V value, boolean includeNewCheck)
{
AbstractMap.SimpleImmutableEntry kvp = new AbstractMap.SimpleImmutableEntry(key, value);
AbstractMap.SimpleImmutableEntry priorKVP = cache.asMap().put(key, kvp);
- return priorKVP != null && (! priorKVP.equals(kvp));
+
+ return (includeNewCheck && priorKVP == null) || (priorKVP != null && (!priorKVP.equals(kvp)));
}
@Override
diff --git a/repository/src/main/java/org/alfresco/repo/deployment/DeploymentMethod.java b/repository/src/main/java/org/alfresco/repo/deployment/DeploymentMethod.java
index f1c50c0723..ea502af0dd 100644
--- a/repository/src/main/java/org/alfresco/repo/deployment/DeploymentMethod.java
+++ b/repository/src/main/java/org/alfresco/repo/deployment/DeploymentMethod.java
@@ -35,6 +35,7 @@ public enum DeploymentMethod
INSTALLER,
DOCKER_COMPOSE,
HELM_CHART,
+ ANSIBLE,
/**
* The distribution zip was used to lay down the ACS artifacts
*/
diff --git a/repository/src/main/java/org/alfresco/repo/domain/permissions/ADMAccessControlListDAO.java b/repository/src/main/java/org/alfresco/repo/domain/permissions/ADMAccessControlListDAO.java
index 8d7b54d07d..b32ccaf972 100644
--- a/repository/src/main/java/org/alfresco/repo/domain/permissions/ADMAccessControlListDAO.java
+++ b/repository/src/main/java/org/alfresco/repo/domain/permissions/ADMAccessControlListDAO.java
@@ -23,7 +23,7 @@
* along with Alfresco. If not, see .
* #L%
*/
-package org.alfresco.repo.domain.permissions;
+package org.alfresco.repo.domain.permissions;
import java.io.Serializable;
import java.util.ArrayList;
@@ -392,8 +392,20 @@ public class ADMAccessControlListDAO implements AccessControlListDAO
{
return;
}
- else
- {
+ else
+ {
+ // When node is copied when the aspect is applied, the sharedACLtoReplace will not match the children's ACLS
+ // to replace, we need to use the current one.
+ Long currentAcl = nodeDAO.getNodeAclId(nodeId);
+
+ if (nodeDAO.hasNodeAspect(nodeId, ContentModel.ASPECT_PENDING_FIX_ACL))
+ {
+ // If node has a pending acl, retrieve the sharedAclToReplace from node property. When the job calls
+ // this, it already does it but on move and copy operations, it uses the new parents old ACL.
+ sharedAclToReplace = (Long) nodeDAO.getNodeProperty(nodeId, ContentModel.PROP_SHARED_ACL_TO_REPLACE);
+
+ }
+
// Lazily retrieve/create the shared ACL
if (mergeFrom == null)
{
@@ -405,33 +417,26 @@ public class ADMAccessControlListDAO implements AccessControlListDAO
nodeDAO.setNodeAclId(nodeId, mergeFrom);
}
- List children = nodeDAO.getPrimaryChildrenAcls(nodeId);
-
- if(children.size() > 0)
- {
- nodeDAO.setPrimaryChildrenSharedAclId(nodeId, sharedAclToReplace, mergeFrom);
- }
+ List children = nodeDAO.getPrimaryChildrenAcls(nodeId);
if (!propagateOnChildren)
{
return;
- }
+ }
+
for (NodeIdAndAclId child : children)
- {
- Long acl = child.getAclId();
-
+ {
+ //Use the current ACL instead of the stored value, it could've been changed meanwhile
+ Long acl = nodeDAO.getNodeAclId(child.getId());
+
if (acl == null)
{
propagateOnChildren = setFixAclPending(child.getId(), inheritFrom, mergeFrom, sharedAclToReplace, changes, false, asyncCall, propagateOnChildren);
}
else
{
-// if(acl.equals(mergeFrom))
-// {
-// setFixedAcls(child.getId(), inheritFrom, mergeFrom, sharedAclToReplace, changes, false);
-// }
// Still has old shared ACL or already replaced
- if(acl.equals(sharedAclToReplace) || acl.equals(mergeFrom))
+ if(acl.equals(sharedAclToReplace) || acl.equals(mergeFrom) || acl.equals(currentAcl))
{
propagateOnChildren = setFixAclPending(child.getId(), inheritFrom, mergeFrom, sharedAclToReplace, changes, false, asyncCall, propagateOnChildren);
}
@@ -456,7 +461,22 @@ public class ADMAccessControlListDAO implements AccessControlListDAO
}
}
}
- }
+ }
+
+ // By doing an eager update of the direct children we canot see if another thread has changed the ACL
+ // between the time we get the child nodes and we update them. By updating the direct children last it is
+ // possible to verify if any child has changed meanwhile.
+ if(children.size() > 0)
+ {
+ nodeDAO.setPrimaryChildrenSharedAclId(nodeId, sharedAclToReplace, mergeFrom);
+ }
+
+ // When this is not executed triggered by the job, but a move or copy operation occures on a pending
+ // node, we don't want to apply the OLD ACL that was pending
+ if(nodeDAO.hasNodeAspect(nodeId, ContentModel.ASPECT_PENDING_FIX_ACL))
+ {
+ removePendingAclAspect(nodeId);
+ }
}
}
@@ -509,25 +529,45 @@ public class ADMAccessControlListDAO implements AccessControlListDAO
}
// set ASPECT_PENDING_FIX_ACL aspect on node to be later on processed with FixedAclUpdater amd switch flag
// FIXED_ACL_ASYNC_REQUIRED_KEY
- addFixedAclPendingAspect(nodeId, sharedAclToReplace, inheritFrom);
+ addFixedAclPendingAspect(nodeId, sharedAclToReplace, inheritFrom, mergeFrom);
AlfrescoTransactionSupport.bindResource(FixedAclUpdater.FIXED_ACL_ASYNC_REQUIRED_KEY, true);
// stop propagating on children nodes
return false;
}
- private void addFixedAclPendingAspect(Long nodeId, Long sharedAclToReplace, Long inheritFrom)
- {
- Set aspect = new HashSet<>();
- aspect.add(ContentModel.ASPECT_PENDING_FIX_ACL);
- nodeDAO.addNodeAspects(nodeId, aspect);
- Map pendingAclProperties = new HashMap<>();
- pendingAclProperties.put(ContentModel.PROP_SHARED_ACL_TO_REPLACE, sharedAclToReplace);
- pendingAclProperties.put(ContentModel.PROP_INHERIT_FROM_ACL, inheritFrom);
- nodeDAO.addNodeProperties(nodeId, pendingAclProperties);
- if (log.isDebugEnabled())
- {
- log.debug("Set Fixed Acl Pending : " + nodeId + " " + nodeDAO.getNodePair(nodeId).getSecond());
+ private void addFixedAclPendingAspect(Long nodeId, Long sharedAclToReplace, Long inheritFrom, Long mergeFrom)
+ {
+ //If the node already has the pending ACL aspect, just update the new inheritFrom value
+ if (nodeDAO.hasNodeAspect(nodeId, ContentModel.ASPECT_PENDING_FIX_ACL))
+ {
+ Map pendingAclProperties = new HashMap<>();
+ pendingAclProperties.put(ContentModel.PROP_INHERIT_FROM_ACL, inheritFrom);
+ nodeDAO.addNodeProperties(nodeId, pendingAclProperties);
+ return;
+ }
+
+ Set aspect = new HashSet<>();
+ aspect.add(ContentModel.ASPECT_PENDING_FIX_ACL);
+ nodeDAO.addNodeAspects(nodeId, aspect);
+ Map pendingAclProperties = new HashMap<>();
+ pendingAclProperties.put(ContentModel.PROP_SHARED_ACL_TO_REPLACE, sharedAclToReplace);
+ pendingAclProperties.put(ContentModel.PROP_INHERIT_FROM_ACL, inheritFrom);
+ nodeDAO.addNodeProperties(nodeId, pendingAclProperties);
+ if (log.isDebugEnabled())
+ {
+ log.debug("Set Fixed Acl Pending : " + nodeId + " " + nodeDAO.getNodePair(nodeId).getSecond());
}
+ }
+
+ public void removePendingAclAspect(Long nodeId)
+ {
+ Set aspects = new HashSet<>(1);
+ aspects.add(ContentModel.ASPECT_PENDING_FIX_ACL);
+ Set pendingFixAclProperties = new HashSet<>();
+ pendingFixAclProperties.add(ContentModel.PROP_SHARED_ACL_TO_REPLACE);
+ pendingFixAclProperties.add(ContentModel.PROP_INHERIT_FROM_ACL);
+ nodeDAO.removeNodeAspects(nodeId, aspects);
+ nodeDAO.removeNodeProperties(nodeId, pendingFixAclProperties);
}
/**
diff --git a/repository/src/main/java/org/alfresco/repo/domain/permissions/AccessControlListDAO.java b/repository/src/main/java/org/alfresco/repo/domain/permissions/AccessControlListDAO.java
index e5ac53fbf3..700f280c80 100644
--- a/repository/src/main/java/org/alfresco/repo/domain/permissions/AccessControlListDAO.java
+++ b/repository/src/main/java/org/alfresco/repo/domain/permissions/AccessControlListDAO.java
@@ -105,5 +105,7 @@ public interface AccessControlListDAO
public void updateInheritance(Long childNodeId, Long oldParentAclId, Long newParentAclId);
- public void setFixedAcls(Long nodeId, Long inheritFrom, Long mergeFrom, Long sharedAclToReplace, List changes, boolean set);
+ public void setFixedAcls(Long nodeId, Long inheritFrom, Long mergeFrom, Long sharedAclToReplace, List changes, boolean set);
+
+ public void removePendingAclAspect(Long nodeId);
}
diff --git a/repository/src/main/java/org/alfresco/repo/domain/permissions/FixedAclUpdater.java b/repository/src/main/java/org/alfresco/repo/domain/permissions/FixedAclUpdater.java
index 5de9123a21..8812ff5028 100644
--- a/repository/src/main/java/org/alfresco/repo/domain/permissions/FixedAclUpdater.java
+++ b/repository/src/main/java/org/alfresco/repo/domain/permissions/FixedAclUpdater.java
@@ -53,6 +53,7 @@ import org.alfresco.repo.transaction.AlfrescoTransactionSupport;
import org.alfresco.repo.transaction.RetryingTransactionHelper.RetryingTransactionCallback;
import org.alfresco.repo.transaction.TransactionListenerAdapter;
import org.alfresco.service.cmr.repository.NodeRef;
+import org.alfresco.service.cmr.repository.StoreRef;
import org.alfresco.service.namespace.NamespaceService;
import org.alfresco.service.namespace.QName;
import org.alfresco.service.transaction.TransactionService;
@@ -91,8 +92,8 @@ public class FixedAclUpdater extends TransactionListenerAdapter implements Appli
private int maxItemBatchSize = 100;
private int numThreads = 4;
- private ClassPolicyDelegate onInheritPermissionsDisabledDelegate;
- private PolicyComponent policyComponent;
+ private ClassPolicyDelegate onInheritPermissionsDisabledDelegate;
+ private PolicyComponent policyComponent;
private PolicyIgnoreUtil policyIgnoreUtil;
public void setNumThreads(int numThreads)
@@ -135,8 +136,8 @@ public class FixedAclUpdater extends TransactionListenerAdapter implements Appli
{
this.lockTimeToLive = lockTimeToLive;
this.lockRefreshTime = lockTimeToLive / 2;
- }
-
+ }
+
public void setPolicyComponent(PolicyComponent policyComponent)
{
this.policyComponent = policyComponent;
@@ -149,7 +150,8 @@ public class FixedAclUpdater extends TransactionListenerAdapter implements Appli
public void init()
{
- onInheritPermissionsDisabledDelegate = policyComponent.registerClassPolicy(PermissionServicePolicies.OnInheritPermissionsDisabled.class);
+ onInheritPermissionsDisabledDelegate = policyComponent
+ .registerClassPolicy(PermissionServicePolicies.OnInheritPermissionsDisabled.class);
}
private class GetNodesWithAspects
@@ -262,26 +264,34 @@ public class FixedAclUpdater extends TransactionListenerAdapter implements Appli
{
log.debug(String.format("Processing node %s", nodeRef));
}
+
final Long nodeId = nodeDAO.getNodePair(nodeRef).getFirst();
+ // MNT-22009 - If node was deleted and in archive store, remove the aspect and properties and do not
+ // process
+ if (nodeRef.getStoreRef().equals(StoreRef.STORE_REF_ARCHIVE_SPACESSTORE))
+ {
+ accessControlListDAO.removePendingAclAspect(nodeId);
+ return null;
+ }
+
// retrieve acl properties from node
- Long inheritFrom = (Long) nodeDAO.getNodeProperty(nodeId,
- ContentModel.PROP_INHERIT_FROM_ACL);
- Long sharedAclToReplace = (Long) nodeDAO.getNodeProperty(nodeId,
- ContentModel.PROP_SHARED_ACL_TO_REPLACE);
+ Long inheritFrom = (Long) nodeDAO.getNodeProperty(nodeId, ContentModel.PROP_INHERIT_FROM_ACL);
+ Long sharedAclToReplace = (Long) nodeDAO.getNodeProperty(nodeId, ContentModel.PROP_SHARED_ACL_TO_REPLACE);
// set inheritance using retrieved prop
- accessControlListDAO.setInheritanceForChildren(nodeRef, inheritFrom, sharedAclToReplace,
- true);
+ accessControlListDAO.setInheritanceForChildren(nodeRef, inheritFrom, sharedAclToReplace, true);
+
+ // Remove aspect
+ accessControlListDAO.removePendingAclAspect(nodeId);
- nodeDAO.removeNodeAspects(nodeId, aspects);
- nodeDAO.removeNodeProperties(nodeId, PENDING_FIX_ACL_ASPECT_PROPS);
-
if (!policyIgnoreUtil.ignorePolicy(nodeRef))
{
- boolean transformedToAsyncOperation = toBoolean((Boolean) AlfrescoTransactionSupport.getResource(FixedAclUpdater.FIXED_ACL_ASYNC_REQUIRED_KEY));
+ boolean transformedToAsyncOperation = toBoolean(
+ (Boolean) AlfrescoTransactionSupport.getResource(FixedAclUpdater.FIXED_ACL_ASYNC_REQUIRED_KEY));
- OnInheritPermissionsDisabled onInheritPermissionsDisabledPolicy = onInheritPermissionsDisabledDelegate.get(ContentModel.TYPE_BASE);
+ OnInheritPermissionsDisabled onInheritPermissionsDisabledPolicy = onInheritPermissionsDisabledDelegate
+ .get(ContentModel.TYPE_BASE);
onInheritPermissionsDisabledPolicy.onInheritPermissionsDisabled(nodeRef, transformedToAsyncOperation);
}
@@ -395,12 +405,8 @@ public class FixedAclUpdater extends TransactionListenerAdapter implements Appli
AclWorkProvider provider = new AclWorkProvider();
AclWorker worker = new AclWorker();
- BatchProcessor bp = new BatchProcessor<>(
- "FixedAclUpdater",
- transactionService.getRetryingTransactionHelper(),
- provider,
- numThreads, maxItemBatchSize,
- applicationContext,
+ BatchProcessor bp = new BatchProcessor<>("FixedAclUpdater",
+ transactionService.getRetryingTransactionHelper(), provider, numThreads, maxItemBatchSize, applicationContext,
log, 100);
int count = bp.process(worker, true);
return count;
@@ -413,7 +419,7 @@ public class FixedAclUpdater extends TransactionListenerAdapter implements Appli
finally
{
jobLockRefreshCallback.isActive.set(false);
- if(lockToken != null)
+ if (lockToken != null)
{
jobLockService.releaseLock(lockToken, lockQName);
}
diff --git a/repository/src/main/resources/alfresco/repository.properties b/repository/src/main/resources/alfresco/repository.properties
index 333d646d6b..00091de25d 100644
--- a/repository/src/main/resources/alfresco/repository.properties
+++ b/repository/src/main/resources/alfresco/repository.properties
@@ -764,7 +764,7 @@ deployment.service.targetLockRefreshTime=60000
# How long to wait in mS from the last communication before deciding that deployment has failed, possibly
# the destination is no longer available?
deployment.service.targetLockTimeout=3600000
-# Deployment method used to deploy this Alfresco instance (DEFAULT, INSTALLER, DOCKER_COMPOSE, HELM_CHART, ZIP, QUICK_START)
+# Deployment method used to deploy this Alfresco instance (DEFAULT, INSTALLER, DOCKER_COMPOSE, HELM_CHART, ANSIBLE, ZIP, QUICK_START)
deployment.method=DEFAULT
#Invitation Service
diff --git a/repository/src/main/resources/alfresco/tx-cache-context.xml b/repository/src/main/resources/alfresco/tx-cache-context.xml
index 8ab850ec58..6b47973f2c 100644
--- a/repository/src/main/resources/alfresco/tx-cache-context.xml
+++ b/repository/src/main/resources/alfresco/tx-cache-context.xml
@@ -136,6 +136,7 @@
+
diff --git a/repository/src/test/java/org/alfresco/repo/cache/DefaultSimpleCacheTest.java b/repository/src/test/java/org/alfresco/repo/cache/DefaultSimpleCacheTest.java
index ec0b5dec1e..43d97ed8f6 100644
--- a/repository/src/test/java/org/alfresco/repo/cache/DefaultSimpleCacheTest.java
+++ b/repository/src/test/java/org/alfresco/repo/cache/DefaultSimpleCacheTest.java
@@ -1,28 +1,28 @@
-/*
- * #%L
- * Alfresco Repository
- * %%
- * 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%
- */
+/*
+ * #%L
+ * Alfresco Repository
+ * %%
+ * 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.repo.cache;
import static org.junit.Assert.*;
@@ -131,6 +131,45 @@ public class DefaultSimpleCacheTest extends SimpleCacheTestBase> errors;
+ private static String TEST_GROUP_NAME = "FixedACLUpdaterTest";
+ private static String TEST_GROUP_NAME_FULL = PermissionService.GROUP_PREFIX + TEST_GROUP_NAME;
+ private static String DEFAULT_PERMISSION = PermissionService.CONTRIBUTOR;
@Override
public void setUp() throws Exception
@@ -91,27 +111,1128 @@ public class FixedAclUpdaterTest extends TestCase
permissionsDaoComponent = (PermissionsDaoComponent) ctx.getBean("admPermissionsDaoComponent");
permissionService = (PermissionService) ctx.getBean("permissionService");
nodeDAO = (NodeDAO) ctx.getBean("nodeDAO");
-
+ lockService = (LockService) ctx.getBean("lockService");
+ checkOutCheckInService = (CheckOutCheckInService) ctx.getBean("checkOutCheckInService");
+ contentService = (ContentService) ctx.getBean("contentService");
+ authorityService = (AuthorityService) ctx.getBean("authorityService");
AuthenticationUtil.setFullyAuthenticatedUser(AuthenticationUtil.getSystemUserName());
- NodeRef home = repository.getCompanyHome();
- // create a folder hierarchy for which will change permission inheritance
- int[] filesPerLevel = { 5, 5, 10 };
- RetryingTransactionCallback cb1 = createFolderHierchyCallback(home, fileFolderService, "rootFolderAsyncCall",
- filesPerLevel);
- folderAsyncCallNodeRef = txnHelper.doInTransaction(cb1);
+ homeFolderNodeRef = repository.getCompanyHome();
+ maxTransactionTime = MAX_TRANSACTION_TIME_DEFAULT;
+ setFixedAclMaxTransactionTime(permissionsDaoComponent, homeFolderNodeRef, maxTransactionTime);
+ }
- RetryingTransactionCallback cb2 = createFolderHierchyCallback(home, fileFolderService, "rootFolderSyncCall",
- filesPerLevel);
- folderSyncCallNodeRef = txnHelper.doInTransaction(cb2);
+ @Override
+ public void tearDown() throws Exception
+ {
+ AuthenticationUtil.clearCurrentSecurityContext();
+ }
- RetryingTransactionCallback cb3 = createFolderHierchyCallback(home, fileFolderService,
- "rootFolderAsyncWithCreateCall", filesPerLevel);
- folderAsyncCallWithCreateNodeRef = txnHelper.doInTransaction(cb3);
+ /*
+ * Test setting permissions having the maxTransactionTime set to 24H, disabling the need for the job
+ */
+ @Test
+ public void testSyncNoTimeOut()
+ {
+ NodeRef folderRef = createFolderHierarchyInRootForFolderTests("testSyncNoTimeOutFolder");
+ ACLComparator aclComparator = new ACLComparator(folderRef);
- // change setFixedAclMaxTransactionTime to lower value so setInheritParentPermissions on created folder
- // hierarchy require async call
- setFixedAclMaxTransactionTime(permissionsDaoComponent, home, 50);
+ try
+ {
+ maxTransactionTime = 86400000;
+ setFixedAclMaxTransactionTime(permissionsDaoComponent, homeFolderNodeRef, maxTransactionTime);
+ setPermissionsOnTree(folderRef, false, false);
+ aclComparator.compareACLs();
+
+ assertEquals("There are nodes pending", 0, getNodesCountWithPendingFixedAclAspect());
+ }
+ finally
+ {
+ deleteNodes(folderRef);
+ }
+ }
+
+ /*
+ * Test setting permissions explicitly as sync, but the operaration times out
+ */
+ @Test
+ public void testSyncTimeOut()
+ {
+ NodeRef folderRef = createFolderHierarchyInRootForFolderTests("testSyncTimeOutFolder");
+ ACLComparator aclComparator = new ACLComparator(folderRef);
+
+ try
+ {
+ setPermissionsOnTree(folderRef, false, true);
+
+ // Get current ACLS on non pending nodes and validate
+ aclComparator.updateCurrentACLs();
+ assertTrue("Permissions not applied", aclComparator.parentHasOriginalPermission());
+
+ // Validate values in pending ACL node
+ NodeRef folderWithPendingAcl = getFirstNodeWithAclPending(ContentModel.TYPE_FOLDER);
+ ACLComparator aclComparatorForPending = new ACLComparator(folderWithPendingAcl);
+ assertEquals("Pending inheritFrom value should be the parent ACL id", aclComparator.getParentAcl(),
+ aclComparatorForPending.getPendingInheritFromAcl());
+ assertFalse("Permissions not expected to be applied on a pending node before job",
+ aclComparatorForPending.firstChildHasOriginalPermission());
+
+ // Trigger job
+ triggerFixedACLJob();
+
+ // Verify if ACLs where applied correctly
+ aclComparator.updateCurrentACLs();
+ aclComparatorForPending.updateCurrentACLs();
+ assertEquals("Not all nodes were processed", 0, getNodesCountWithPendingFixedAclAspect());
+ assertEquals("Processed Pending ACL children doesn't have correct ACL", aclComparator.getChildAcl(),
+ aclComparatorForPending.getChildAcl());
+ assertTrue("Permissions not applied on pending nodes", aclComparatorForPending.firstChildHasOriginalPermission());
+ }
+ finally
+ {
+ deleteNodes(folderRef);
+ }
+ }
+
+ /*
+ * Test setting permissions explicitly as async
+ */
+ @Test
+ public void testAsync()
+ {
+ NodeRef folderRef = createFolderHierarchyInRootForFolderTests("testAsyncFolder");
+ ACLComparator aclComparator = new ACLComparator(folderRef);
+
+ try
+ {
+ setPermissionsOnTree(folderRef, true, true);
+
+ // Get current ACLS on non pending nodes and validate
+ aclComparator.updateCurrentACLs();
+ assertTrue("Permissions not applied", aclComparator.parentHasOriginalPermission());
+
+ // Validate values in pending ACL node
+ NodeRef folderWithPendingAcl = getFirstNodeWithAclPending(ContentModel.TYPE_FOLDER);
+ assertNotNull("No children folders were found with pendingFixACl aspect", folderWithPendingAcl);
+ ACLComparator aclComparatorForPending = new ACLComparator(folderWithPendingAcl);
+ assertEquals("Pending inheritFrom value should be the parent ACL id", aclComparator.getParentAcl(),
+ aclComparatorForPending.getPendingInheritFromAcl());
+ assertFalse("Permissions not expected to be applied on a pending node before job",
+ aclComparatorForPending.firstChildHasOriginalPermission());
+
+ // Trigger job
+ triggerFixedACLJob();
+
+ // Verify if ACLs where applied correctly
+ aclComparator.updateCurrentACLs();
+ aclComparatorForPending.updateCurrentACLs();
+ assertEquals("Not all nodes were processed", 0, getNodesCountWithPendingFixedAclAspect());
+ assertEquals("Processed Pending ACL children doesn't have correct ACL", aclComparator.getChildAcl(),
+ aclComparatorForPending.getChildAcl());
+ assertTrue("Pending nodes doesn't have same permission as parent",
+ aclComparatorForPending.parentHasOriginalPermission());
+ assertTrue("Children of Pending nodes doesn't have same permission as parent",
+ aclComparatorForPending.firstChildHasOriginalPermission());
+ }
+ finally
+ {
+ deleteNodes(folderRef);
+ }
+ }
+
+ /*
+ * MNT-21847 - Create a new content in folder that has the aspect applied
+ */
+ @Test
+ public void testAsyncWithNodeCreation()
+ {
+ NodeRef folderRef = createFolderHierarchyInRootForFolderTests("testAsyncWithNodeCreationFolder");
+
+ try
+ {
+ setPermissionsOnTree(folderRef, true, true);
+
+ NodeRef folderWithPendingAcl = getFirstNodeWithAclPending(ContentModel.TYPE_FOLDER);
+ assertNotNull("No children folders were found with pendingFixACl aspect", folderWithPendingAcl);
+
+ txnHelper.doInTransaction((RetryingTransactionCallback) () -> {
+ createFile(fileFolderService, folderWithPendingAcl, "NewFile", ContentModel.TYPE_CONTENT);
+ return null;
+ }, false, true);
+
+ triggerFixedACLJob();
+ assertEquals("Not all nodes were processed", 0, getNodesCountWithPendingFixedAclAspect());
+
+ }
+ finally
+ {
+ deleteNodes(folderRef);
+ }
+ }
+
+ /*
+ * MNT-22009 - Delete node that has the aspect applied before job runs
+ */
+ @Test
+ public void testAsyncWithNodeDeletion()
+ {
+ NodeRef folderRef = createFolderHierarchyInRootForFolderTests("testAsyncWithNodeDeletionFolder");
+
+ try
+ {
+ setPermissionsOnTree(folderRef, true, true);
+
+ NodeRef folderWithPendingAcl = getFirstNodeWithAclPending(ContentModel.TYPE_FOLDER);
+ assertNotNull("No children folders were found with pendingFixACl aspect", folderWithPendingAcl);
+
+ txnHelper.doInTransaction((RetryingTransactionCallback) () -> {
+ fileFolderService.delete(folderWithPendingAcl);
+ return null;
+ }, false, true);
+
+ triggerFixedACLJob();
+ assertEquals("Not all nodes were processed", 0, getNodesCountWithPendingFixedAclAspect());
+ }
+ finally
+ {
+ deleteNodes(folderRef);
+ }
+ }
+
+ /*
+ * Copy node with no timeout and no pending nodes
+ */
+ @Test
+ public void testSyncCopyNoTimeOut() throws FileExistsException, FileNotFoundException
+ {
+ NodeRef originalRef = createFolderHierarchyInRootForFolderTests("originFolder");
+ NodeRef targetRef = createFolderHierarchyInRootForFolderTests("targetFolder");
+
+ // Get ACLS for later comparison
+ ACLComparator aclComparatorOrigin = new ACLComparator(originalRef);
+
+ try
+ {
+ maxTransactionTime = 86400000;
+ setFixedAclMaxTransactionTime(permissionsDaoComponent, homeFolderNodeRef, maxTransactionTime);
+
+ // Set Shared permissions on origin
+ permissionService.setInheritParentPermissions(originalRef, true, false);
+ permissionService.setPermission(originalRef, TEST_GROUP_NAME_FULL, PermissionService.COORDINATOR, true);
+ aclComparatorOrigin.setOriginalPermission(TEST_GROUP_NAME_FULL, PermissionService.COORDINATOR);
+
+ // Set Shared permissions on target and inherit permissions from parent
+ permissionService.setInheritParentPermissions(targetRef, true, false);
+ permissionService.setPermission(targetRef, TEST_GROUP_NAME_FULL, DEFAULT_PERMISSION, true);
+
+ // Copy the nodes
+ NodeRef copiedNode = fileFolderService.copy(originalRef, targetRef, null).getNodeRef();
+ ACLComparator aclComparatorCopied = new ACLComparator(copiedNode);
+ aclComparatorOrigin.setOriginalPermission(TEST_GROUP_NAME_FULL, PermissionService.COORDINATOR);
+
+ // Validate the results - Permissions should merge on copied node
+ assertEquals("There are nodes pending", 0, getNodesCountWithPendingFixedAclAspect());
+ assertTrue("Copied node did not inherit permissions from target",
+ aclComparatorCopied.hasPermission(TEST_GROUP_NAME_FULL, DEFAULT_PERMISSION));
+ assertTrue("Child of Copied node did not inherit permissions from target",
+ aclComparatorCopied.firstChildHasPermission(TEST_GROUP_NAME_FULL, DEFAULT_PERMISSION));
+ assertTrue("Copied node did not keep original permissions", aclComparatorCopied.parentHasOriginalPermission());
+ assertTrue("Child of Copied node did not keep original permissions",
+ aclComparatorCopied.firstChildHasOriginalPermission());
+ }
+ finally
+ {
+ deleteNodes(originalRef);
+ deleteNodes(targetRef);
+ }
+ }
+
+ /*
+ * MNT-22040 - Copy node that has the aspect applied before job runs
+ */
+ @Test
+ public void testAsyncWithNodeCopy()
+ {
+ NodeRef folderRef = createFolderHierarchyInRootForFolderTests("testAsyncWithNodeCopyOriginFolder");
+ NodeRef targetRef = createFile(fileFolderService, homeFolderNodeRef, "testAsyncWithNodeCopyTargetFolder",
+ ContentModel.TYPE_FOLDER);
+
+ // Get ACLS for later comparison
+ ACLComparator aclComparatorTarget = new ACLComparator(targetRef);
+
+ try
+ {
+ // Set permissions on target folder
+ txnHelper.doInTransaction((RetryingTransactionCallback) () -> {
+ permissionService.setInheritParentPermissions(targetRef, false, false);
+ permissionService.setPermission(targetRef, TEST_GROUP_NAME_FULL, PermissionService.COORDINATOR, true);
+ aclComparatorTarget.setOriginalPermission(TEST_GROUP_NAME_FULL, PermissionService.COORDINATOR);
+ return null;
+ }, false, true);
+
+ assertTrue("Target Folder does not have correct permission", aclComparatorTarget.parentHasOriginalPermission());
+
+ // Set permissions async on origin folder and inherit permissions from parent
+ txnHelper.doInTransaction((RetryingTransactionCallback) () -> {
+ permissionService.setInheritParentPermissions(folderRef, true, false);
+ permissionService.setPermission(folderRef, TEST_GROUP_NAME_FULL, DEFAULT_PERMISSION, true);
+ return null;
+ }, false, true);
+
+ // Find a pending ACL folder to copy
+ NodeRef folderWithPendingAcl = getFirstNodeWithAclPending(ContentModel.TYPE_FOLDER, folderRef);
+ assertNotNull("No children folders were found with pendingFixACl aspect", folderWithPendingAcl);
+
+ txnHelper.doInTransaction((RetryingTransactionCallback) () -> {
+ // copy folder with pending acl to target
+ fileFolderService.copy(folderWithPendingAcl, targetRef, "copyOfFolder");
+ return null;
+ }, false, true);
+
+ NodeRef copiedChild = fileFolderService.searchSimple(targetRef, "copyOfFolder");
+ ACLComparator aclComparatorCopiedPendingChild = new ACLComparator(copiedChild);
+
+ // Trigger job
+ triggerFixedACLJob();
+
+ // Validate results - ACL's aren't suppose to merge as the Copied folder doesn't have any ACL's directly
+ // applied to them and should just inherit from whatever parent they have
+ assertEquals("Not all nodes were processed", 0, getNodesCountWithPendingFixedAclAspect());
+ assertTrue("Pending Copied node did not inherit permissions from target",
+ aclComparatorCopiedPendingChild.hasPermission(TEST_GROUP_NAME_FULL, PermissionService.COORDINATOR));
+ assertTrue("Child of Pending Copied node did not inherit permissions from target",
+ aclComparatorCopiedPendingChild.firstChildHasPermission(TEST_GROUP_NAME_FULL, PermissionService.COORDINATOR));
+ assertFalse("Pending Copied node kept original permissions",
+ aclComparatorCopiedPendingChild.parentHasOriginalPermission());
+ assertFalse("Child of Pending Copied node kept original permissions",
+ aclComparatorCopiedPendingChild.firstChildHasOriginalPermission());
+ }
+ finally
+ {
+ deleteNodes(folderRef);
+ deleteNodes(targetRef);
+ }
+ }
+
+ /*
+ * Copy node that has the aspect to another folder that also has the aspect applied before job runs
+ */
+ @Test
+ public void testAsyncWithNodeCopyToPendingFolder()
+ {
+ NodeRef folderRef = createFolderHierarchyInRootForFolderTests("testAsyncWithNodeCopyOriginFolder");
+ NodeRef targetRef = createFolderHierarchyInRootForFolderTests("testAsyncWithNodeCopyTargetFolder");
+
+ // Get ACLS for later comparison
+ ACLComparator aclComparatorTarget = new ACLComparator(targetRef);
+
+ try
+ {
+ // Set permissions on target folder
+ txnHelper.doInTransaction((RetryingTransactionCallback) () -> {
+ permissionService.setInheritParentPermissions(targetRef, false, false);
+ permissionService.setPermission(targetRef, TEST_GROUP_NAME_FULL, PermissionService.COORDINATOR, true);
+ aclComparatorTarget.setOriginalPermission(TEST_GROUP_NAME_FULL, PermissionService.COORDINATOR);
+ return null;
+ }, false, true);
+
+ assertTrue("Target Folder does not have correct permission", aclComparatorTarget.parentHasOriginalPermission());
+
+ // Get target Folder with a pending ACL to copy the pending folder to
+ NodeRef targetFolderWithPendingAcl = getFirstNodeWithAclPending(ContentModel.TYPE_FOLDER, targetRef);
+ assertNotNull("No children folders were found with pendingFixACl aspect", targetFolderWithPendingAcl);
+ ACLComparator aclComparatorTargetPendingChild = new ACLComparator(targetFolderWithPendingAcl);
+ aclComparatorTargetPendingChild.setOriginalPermission(TEST_GROUP_NAME_FULL, PermissionService.COORDINATOR);
+
+ // Set permissions on origin folder and get a pending folder to copy
+ txnHelper.doInTransaction((RetryingTransactionCallback) () -> {
+ permissionService.setInheritParentPermissions(folderRef, true, false);
+ permissionService.setPermission(folderRef, TEST_GROUP_NAME_FULL, DEFAULT_PERMISSION, true);
+ return null;
+ }, false, true);
+
+ // Find a pending ACL folder to copy
+ NodeRef originFolderWithPendingAcl = getFirstNodeWithAclPending(ContentModel.TYPE_FOLDER, folderRef);
+ assertNotNull("No children folders were found with pendingFixACl aspect", originFolderWithPendingAcl);
+
+ // copy one pending folder into the other
+ txnHelper.doInTransaction((RetryingTransactionCallback) () -> {
+ // copy folder with pending acl to target
+ fileFolderService.copy(originFolderWithPendingAcl, targetFolderWithPendingAcl, "copyOfFolder");
+ return null;
+ }, false, true);
+
+ NodeRef copiedChild = fileFolderService.searchSimple(targetFolderWithPendingAcl, "copyOfFolder");
+ ACLComparator aclComparatorCopiedPendingChild = new ACLComparator(copiedChild);
+
+ // Trigger job
+ triggerFixedACLJob();
+
+ // Validate results - ACL's aren't suppose to merge as the Copied folder doesn't have any ACL's directly
+ // applied to them and should just inherit from whatever parent they have
+ assertEquals("Not all nodes were processed", 0, getNodesCountWithPendingFixedAclAspect());
+ assertTrue("Pending Copied node did not inherit permissions from target",
+ aclComparatorCopiedPendingChild.hasPermission(TEST_GROUP_NAME_FULL, PermissionService.COORDINATOR));
+ assertTrue("Child of PendingCopied node did not inherit permissions from target",
+ aclComparatorCopiedPendingChild.firstChildHasPermission(TEST_GROUP_NAME_FULL, PermissionService.COORDINATOR));
+ assertFalse("Pending Copied kept original permissions",
+ aclComparatorCopiedPendingChild.parentHasOriginalPermission());
+ assertFalse("Child of Pending Copied node kept original permissions",
+ aclComparatorCopiedPendingChild.firstChildHasOriginalPermission());
+ assertTrue("Pending target node does not have parent's permissions",
+ aclComparatorTargetPendingChild.parentHasOriginalPermission());
+ assertTrue("Child of Pending target node does not have parent's permissions",
+ aclComparatorTargetPendingChild.firstChildHasOriginalPermission());
+ }
+ finally
+ {
+ deleteNodes(folderRef);
+ deleteNodes(targetRef);
+ }
+ }
+
+ /*
+ * Copy parent of node that has the aspect to a child folder of a folder that also has the aspect applied before job
+ * runs
+ */
+ @Test
+ public void testAsyncWithNodeCopyParentToChildPendingFolder()
+ {
+ NodeRef folderRef = createFolderHierarchyInRootForFolderTests("testAsyncWithNodeCopyOriginFolder");
+ NodeRef targetRef = createFolderHierarchyInRootForFolderTests("testAsyncWithNodeCopyTargetFolder");
+
+ // Get ACLS for later comparison
+ ACLComparator aclComparatorTarget = new ACLComparator(targetRef);
+
+ try
+ {
+ // Set permissions on target folder
+ txnHelper.doInTransaction((RetryingTransactionCallback) () -> {
+ permissionService.setInheritParentPermissions(targetRef, false, false);
+ permissionService.setPermission(targetRef, TEST_GROUP_NAME_FULL, PermissionService.COORDINATOR, true);
+ aclComparatorTarget.setOriginalPermission(TEST_GROUP_NAME_FULL, PermissionService.COORDINATOR);
+ return null;
+ }, false, true);
+
+ assertTrue("Target Folder does not have correct permission", aclComparatorTarget.parentHasOriginalPermission());
+
+ // Get target Folder with a pending ACL to copy the pending folder to
+ NodeRef targetFolderWithPendingAcl = getFirstNodeWithAclPending(ContentModel.TYPE_FOLDER, targetRef);
+ assertNotNull("No children folders were found with pendingFixACl aspect", targetFolderWithPendingAcl);
+ NodeRef targetFolderWithPendingAclChild = nodeDAO
+ .getNodePair(getChild(nodeDAO.getNodePair(targetFolderWithPendingAcl).getFirst())).getSecond();
+ ACLComparator aclComparatorTargetPendingChild = new ACLComparator(targetFolderWithPendingAclChild);
+ aclComparatorTargetPendingChild.setOriginalPermission(TEST_GROUP_NAME_FULL, PermissionService.COORDINATOR);
+
+ // Set permissions on origin folder and get a pending folder to copy
+ txnHelper.doInTransaction((RetryingTransactionCallback) () -> {
+ permissionService.setInheritParentPermissions(folderRef, true, false);
+ permissionService.setPermission(folderRef, TEST_GROUP_NAME_FULL, DEFAULT_PERMISSION, true);
+ return null;
+ }, false, true);
+
+ // Find a pending ACL folder and copy its parent
+ NodeRef originFolderWithPendingAcl = getFirstNodeWithAclPending(ContentModel.TYPE_FOLDER, folderRef);
+ assertNotNull("No children folders were found with pendingFixACl aspect", originFolderWithPendingAcl);
+ NodeRef originFolderWithPendingAclParent = nodeDAO
+ .getPrimaryParentAssoc(nodeDAO.getNodePair(originFolderWithPendingAcl).getFirst()).getSecond().getParentRef();
+
+ // copy one pending folder into the other
+ txnHelper.doInTransaction((RetryingTransactionCallback) () -> {
+ // copy folder with pending acl to target
+ fileFolderService.copy(originFolderWithPendingAclParent, targetFolderWithPendingAcl, "copyOfFolder");
+ return null;
+ }, false, true);
+
+ NodeRef copiedChild = fileFolderService.searchSimple(targetFolderWithPendingAcl, "copyOfFolder");
+ ACLComparator aclComparatorCopiedPendingParent = new ACLComparator(copiedChild);
+
+ // Trigger job
+ triggerFixedACLJob();
+
+ // Validate results - ACL's aren't suppose to merge as the Copied folder doesn't have any ACL's directly
+ // applied to them and should just inherit from whatever parent they have
+ assertEquals("Not all nodes were processed", 0, getNodesCountWithPendingFixedAclAspect());
+ assertTrue("Copied Parent node did not inherit permissions from target",
+ aclComparatorCopiedPendingParent.hasPermission(TEST_GROUP_NAME_FULL, PermissionService.COORDINATOR));
+ assertTrue("Pending Node copied with parent node did not inherit permissions from target",
+ aclComparatorCopiedPendingParent.firstChildHasPermission(TEST_GROUP_NAME_FULL,
+ PermissionService.COORDINATOR));
+
+ // Depending on when the time runs out of the transaction, the parent node we copied may be the node we
+ // actually set permission on. In this case, if this parent node is copied, it is expected to keep the
+ // permissions we set directly on it
+ if (originFolderWithPendingAclParent.equals(folderRef))
+ {
+ assertTrue("Copied Parent (from original node where permissions were set) did not keep original permissions",
+ aclComparatorCopiedPendingParent.parentHasOriginalPermission());
+ assertTrue("Pending Node copied with parent node did not keep original permissions",
+ aclComparatorCopiedPendingParent.firstChildHasOriginalPermission());
+ }
+ else
+ {
+ assertFalse("Copied Parent kept original permissions",
+ aclComparatorCopiedPendingParent.parentHasOriginalPermission());
+ assertFalse("Pending Node copied with parent node kept original permissions",
+ aclComparatorCopiedPendingParent.firstChildHasOriginalPermission());
+ }
+
+ assertTrue("Pending target node does not have parent's permissions",
+ aclComparatorTargetPendingChild.parentHasOriginalPermission());
+ assertTrue("Child of Pending target node does not have parent's permissions",
+ aclComparatorTargetPendingChild.firstChildHasOriginalPermission());
+ }
+ finally
+ {
+ deleteNodes(folderRef);
+ deleteNodes(targetRef);
+ }
+ }
+
+ /*
+ * MNT-22040 - Move node that has the aspect applied before job runs
+ */
+ @Test
+ public void testAsyncWithNodeMove()
+ {
+ NodeRef folderRef = createFolderHierarchyInRootForFolderTests("testAsyncWithNodeMoveOriginFolder");
+ NodeRef targetRef = createFile(fileFolderService, homeFolderNodeRef, "testAsyncWithNodeMoveTargetFolder",
+ ContentModel.TYPE_FOLDER);
+
+ // Get ACLS for later comparison
+ ACLComparator aclComparatorTarget = new ACLComparator(targetRef);
+
+ try
+ {
+ // Set permissions on target folder
+ txnHelper.doInTransaction((RetryingTransactionCallback) () -> {
+ permissionService.setInheritParentPermissions(targetRef, false, false);
+ permissionService.setPermission(targetRef, TEST_GROUP_NAME_FULL, PermissionService.COORDINATOR, true);
+ aclComparatorTarget.setOriginalPermission(TEST_GROUP_NAME_FULL, PermissionService.COORDINATOR);
+ return null;
+ }, false, true);
+
+ assertTrue("Target Folder does not have correct permission", aclComparatorTarget.parentHasOriginalPermission());
+
+ // Set permissions async on origin folder and inherit permissions from parent
+ txnHelper.doInTransaction((RetryingTransactionCallback) () -> {
+ permissionService.setInheritParentPermissions(folderRef, true, false);
+ permissionService.setPermission(folderRef, TEST_GROUP_NAME_FULL, DEFAULT_PERMISSION, true);
+ return null;
+ }, false, true);
+
+ // Find a pending ACL folder to move
+ NodeRef folderWithPendingAcl = getFirstNodeWithAclPending(ContentModel.TYPE_FOLDER, folderRef);
+ ACLComparator aclComparatorOriginPendingChild = new ACLComparator(folderWithPendingAcl);
+ assertNotNull("No children folders were found with pendingFixACl aspect", folderWithPendingAcl);
+ txnHelper.doInTransaction((RetryingTransactionCallback) () -> {
+ // move folder with pending acl to target
+ fileFolderService.move(folderWithPendingAcl, targetRef, "moveOfFolder");
+ return null;
+ }, false, true);
+
+ // Trigger job
+ triggerFixedACLJob();
+
+ // Validate results - ACL's aren't suppose to merge as the Moved folder doesn't have any ACL's directly
+ // applied to them and should just inherit from whatever parent they have
+ assertEquals("Not all nodes were processed", 0, getNodesCountWithPendingFixedAclAspect());
+ assertTrue("Pending Moved node did not inherit permissions from target",
+ aclComparatorOriginPendingChild.hasPermission(TEST_GROUP_NAME_FULL, PermissionService.COORDINATOR));
+ assertTrue("Child of Pending Moved node did not inherit permissions from target",
+ aclComparatorOriginPendingChild.firstChildHasPermission(TEST_GROUP_NAME_FULL, PermissionService.COORDINATOR));
+ assertFalse("Pending Moved node kept original permissions",
+ aclComparatorOriginPendingChild.parentHasOriginalPermission());
+ assertFalse("Child of Pending Moved node kept original permissions",
+ aclComparatorOriginPendingChild.firstChildHasOriginalPermission());
+ }
+ finally
+ {
+ deleteNodes(folderRef);
+ deleteNodes(targetRef);
+ }
+ }
+
+ /*
+ * Move node that has the aspect to another folder that also has the aspect applied before job runs
+ */
+ @Test
+ public void testAsyncWithNodeMoveToPendingFolder()
+ {
+ NodeRef folderRef = createFolderHierarchyInRootForFolderTests("testAsyncWithNodeMoveOriginFolder");
+ NodeRef targetRef = createFolderHierarchyInRootForFolderTests("testAsyncWithNodeMoveTargetFolder");
+
+ // Get ACLS for later comparison
+ ACLComparator aclComparatorTarget = new ACLComparator(targetRef);
+
+ try
+ {
+ // Set permissions on target folder
+ txnHelper.doInTransaction((RetryingTransactionCallback) () -> {
+ permissionService.setInheritParentPermissions(targetRef, false, false);
+ permissionService.setPermission(targetRef, TEST_GROUP_NAME_FULL, PermissionService.COORDINATOR, true);
+ aclComparatorTarget.setOriginalPermission(TEST_GROUP_NAME_FULL, PermissionService.COORDINATOR);
+ return null;
+ }, false, true);
+
+ assertTrue("Target Folder does not have correct permission", aclComparatorTarget.parentHasOriginalPermission());
+
+ // Get target Folder with a pending ACL to move the pending folder to
+ NodeRef targetFolderWithPendingAcl = getFirstNodeWithAclPending(ContentModel.TYPE_FOLDER, targetRef);
+ assertNotNull("No children folders were found with pendingFixACl aspect", targetFolderWithPendingAcl);
+ ACLComparator aclComparatorTargetPendingChild = new ACLComparator(targetFolderWithPendingAcl);
+ aclComparatorTargetPendingChild.setOriginalPermission(TEST_GROUP_NAME_FULL, PermissionService.COORDINATOR);
+
+ // Set permissions on origin folder and get a pending folder to move
+ txnHelper.doInTransaction((RetryingTransactionCallback) () -> {
+ permissionService.setInheritParentPermissions(folderRef, true, false);
+ permissionService.setPermission(folderRef, TEST_GROUP_NAME_FULL, DEFAULT_PERMISSION, true);
+ return null;
+ }, false, true);
+
+ // Find a pending ACL folder to move
+ NodeRef originFolderWithPendingAcl = getFirstNodeWithAclPending(ContentModel.TYPE_FOLDER, folderRef);
+ assertNotNull("No children folders were found with pendingFixACl aspect", originFolderWithPendingAcl);
+ ACLComparator aclComparatorOriginPendingChild = new ACLComparator(originFolderWithPendingAcl);
+
+ // move one pending folder into the other
+ txnHelper.doInTransaction((RetryingTransactionCallback) () -> {
+ // move folder with pending acl to target
+ fileFolderService.move(originFolderWithPendingAcl, targetFolderWithPendingAcl, "movedFolder");
+ return null;
+ }, false, true);
+
+ // Trigger job
+ triggerFixedACLJob();
+
+ // Validate results - ACL's aren't suppose to merge as the Moved folder doesn't have any ACL's directly
+ // applied to them and should just inherit from whatever parent they have
+ assertEquals("Not all nodes were processed", 0, getNodesCountWithPendingFixedAclAspect());
+ assertTrue("Pending Moved node did not inherit permissions from target",
+ aclComparatorOriginPendingChild.hasPermission(TEST_GROUP_NAME_FULL, PermissionService.COORDINATOR));
+ assertTrue("Child of PendingMoved node did not inherit permissions from target",
+ aclComparatorOriginPendingChild.firstChildHasPermission(TEST_GROUP_NAME_FULL, PermissionService.COORDINATOR));
+ assertFalse("Pending Moved kept original permissions", aclComparatorOriginPendingChild.parentHasOriginalPermission());
+ assertFalse("Child of Pending Moved node kept original permissions",
+ aclComparatorOriginPendingChild.firstChildHasOriginalPermission());
+ assertTrue("Pending target node does not have parent's permissions",
+ aclComparatorTargetPendingChild.parentHasOriginalPermission());
+ assertTrue("Child of Pending target node does not have parent's permissions",
+ aclComparatorTargetPendingChild.firstChildHasOriginalPermission());
+ }
+ finally
+ {
+ deleteNodes(folderRef);
+ deleteNodes(targetRef);
+ }
+ }
+
+ /*
+ * Lock node that has the aspect applied before job runs
+ */
+ @Test
+ public void testAsyncWithNodeLock()
+ {
+ NodeRef folderRef = createFolderHierarchyInRootForFileTests("testAsyncWithNodeLockFolder");
+
+ try
+ {
+ setPermissionsOnTree(folderRef, true, true);
+
+ NodeRef nodeWithPendingAcl = getFirstNodeWithAclPending(ContentModel.TYPE_CONTENT);
+ assertNotNull("No children files were found with pendingFixACl aspect", nodeWithPendingAcl);
+
+ txnHelper.doInTransaction((RetryingTransactionCallback) () -> {
+ lockService.lock(nodeWithPendingAcl, LockType.READ_ONLY_LOCK);
+ return null;
+ }, false, true);
+
+ triggerFixedACLJob();
+ assertEquals("Not all nodes were processed", 0, getNodesCountWithPendingFixedAclAspect());
+ }
+ finally
+ {
+ deleteNodes(folderRef);
+ }
+ }
+
+ /*
+ * Checkout a node for editing that has the aspect applied before job runs
+ */
+ @Test
+ public void testAsyncWithNodeCheckout()
+ {
+ NodeRef folderRef = createFolderHierarchyInRootForFileTests("testAsyncWithNodeCheckoutFolder");
+
+ try
+ {
+ setPermissionsOnTree(folderRef, true, true);
+
+ NodeRef nodeWithPendingAcl = getFirstNodeWithAclPending(ContentModel.TYPE_CONTENT);
+ assertNotNull("No children files were found with pendingFixACl aspect", nodeWithPendingAcl);
+
+ txnHelper.doInTransaction((RetryingTransactionCallback) () -> {
+ NodeRef workingCopy = checkOutCheckInService.checkout(nodeWithPendingAcl);
+ assertNotNull("Working copy is null", workingCopy);
+ return null;
+ }, false, true);
+
+ triggerFixedACLJob();
+ assertEquals("Not all nodes were processed", 0, getNodesCountWithPendingFixedAclAspect());
+ }
+ finally
+ {
+ deleteNodes(folderRef);
+ }
+ }
+
+ /*
+ * Update the permissions of a node that has the aspect applied (new permissions: fixed)
+ */
+ @Test
+ public void testAsyncWithNodeUpdatePermissionsFixed()
+ {
+ NodeRef folderRef = createFolderHierarchyInRootForFolderTests("testAsyncWithNodeUpdatePermissionsFixedFolder");
+ ACLComparator aclComparatorTop = new ACLComparator(folderRef);
+
+ try
+ {
+ setPermissionsOnTree(folderRef, true, true);
+ aclComparatorTop.updateCurrentACLs();
+
+ NodeRef nodeWithPendingAcl = getFirstNodeWithAclPending(ContentModel.TYPE_FOLDER);
+ assertNotNull("No children files were found with pendingFixACl aspect", nodeWithPendingAcl);
+ ACLComparator aclComparator = new ACLComparator(nodeWithPendingAcl);
+
+ txnHelper.doInTransaction((RetryingTransactionCallback) () -> {
+ permissionService.setInheritParentPermissions(nodeWithPendingAcl, false, false);
+ permissionService.setPermission(nodeWithPendingAcl, TEST_GROUP_NAME_FULL, PermissionService.COORDINATOR, true);
+ return null;
+ }, false, true);
+ aclComparator.updateCurrentACLs();
+
+ triggerFixedACLJob();
+ assertEquals("Not all nodes were processed", 0, getNodesCountWithPendingFixedAclAspect());
+ assertFalse("Pending node is not expected to have old permission", aclComparator.parentHasOriginalPermission());
+ assertFalse("Child of Pending node is not expected to have old permission",
+ aclComparator.firstChildHasOriginalPermission());
+ assertTrue("Pending node is expected to have new permission",
+ aclComparator.hasPermission(TEST_GROUP_NAME_FULL, PermissionService.COORDINATOR));
+ assertTrue("Child of Pending node is expected to have new permission",
+ aclComparator.firstChildHasPermission(TEST_GROUP_NAME_FULL, PermissionService.COORDINATOR));
+ }
+ finally
+ {
+ deleteNodes(folderRef);
+ }
+ }
+
+ /*
+ * Update the permissions of a node that has the aspect applied (new permissions: shared)
+ */
+ @Test
+ public void testAsyncWithNodeUpdatePermissionsShared()
+ {
+ NodeRef folderRef = createFolderHierarchyInRootForFolderTests("testAsyncWithNodeUpdatePermissionsSharedFolder");
+
+ try
+ {
+ setPermissionsOnTree(folderRef, true, true);
+
+ NodeRef nodeWithPendingAcl = getFirstNodeWithAclPending(ContentModel.TYPE_FOLDER);
+ assertNotNull("No children files were found with pendingFixACl aspect", nodeWithPendingAcl);
+ ACLComparator aclComparator = new ACLComparator(nodeWithPendingAcl);
+
+ txnHelper.doInTransaction((RetryingTransactionCallback) () -> {
+ permissionService.setInheritParentPermissions(nodeWithPendingAcl, true, false);
+ permissionService.setPermission(nodeWithPendingAcl, TEST_GROUP_NAME_FULL, PermissionService.COORDINATOR, true);
+ return null;
+ }, false, true);
+
+ triggerFixedACLJob();
+ assertEquals("Not all nodes were processed", 0, getNodesCountWithPendingFixedAclAspect());
+ assertTrue("Pending node is expected to have old permission", aclComparator.parentHasOriginalPermission());
+ assertTrue("Child of Pending node is expected to have old permission",
+ aclComparator.firstChildHasOriginalPermission());
+ assertTrue("Pending node is expected to have new permission",
+ aclComparator.hasPermission(TEST_GROUP_NAME_FULL, PermissionService.COORDINATOR));
+ assertTrue("Child of Pending node is expected to have new permission",
+ aclComparator.firstChildHasPermission(TEST_GROUP_NAME_FULL, PermissionService.COORDINATOR));
+ }
+ finally
+ {
+ deleteNodes(folderRef);
+ }
+ }
+
+ /*
+ * Update the permissions of the parent of a node that has the aspect applied (new permissions: fixed)
+ */
+ @Test
+ public void testAsyncWithParentUpdatePermissionsFixed()
+ {
+ NodeRef folderRef = createFolderHierarchyInRootForFolderTests("testAsyncWithParentUpdatePermissionsFixedFolder");
+
+ try
+ {
+ setPermissionsOnTree(folderRef, true, true);
+
+ NodeRef nodeWithPendingAcl = getFirstNodeWithAclPending(ContentModel.TYPE_FOLDER);
+ assertNotNull("No children files were found with pendingFixACl aspect", nodeWithPendingAcl);
+ ACLComparator aclComparator = new ACLComparator(nodeWithPendingAcl);
+
+ NodeRef parentRef = nodeDAO.getPrimaryParentAssoc(nodeDAO.getNodePair(nodeWithPendingAcl).getFirst()).getSecond()
+ .getParentRef();
+
+ txnHelper.doInTransaction((RetryingTransactionCallback) () -> {
+ permissionService.setInheritParentPermissions(parentRef, false, false);
+ permissionService.setPermission(parentRef, TEST_GROUP_NAME_FULL, PermissionService.COORDINATOR, true);
+ return null;
+ }, false, true);
+
+ triggerFixedACLJob();
+ assertEquals("Not all nodes were processed", 0, getNodesCountWithPendingFixedAclAspect());
+ assertFalse("Pending node is not expected to have old permission", aclComparator.parentHasOriginalPermission());
+ assertFalse("Child of Pending node is not expected to have old permission",
+ aclComparator.firstChildHasOriginalPermission());
+ assertTrue("Pending node is expected to have new permission",
+ aclComparator.hasPermission(TEST_GROUP_NAME_FULL, PermissionService.COORDINATOR));
+ assertTrue("Child of Pending node is expected to have new permission",
+ aclComparator.firstChildHasPermission(TEST_GROUP_NAME_FULL, PermissionService.COORDINATOR));
+ }
+ finally
+ {
+ deleteNodes(folderRef);
+ }
+ }
+
+ /*
+ * Update the permissions of the parent of a node that has the aspect applied (new permissions: shared)
+ */
+ @Test
+ public void testAsyncWithParentUpdatePermissionsShared()
+ {
+ NodeRef folderRef = createFolderHierarchyInRootForFolderTests("testAsyncWithParentUpdatePermissionsSharedFolder");
+
+ try
+ {
+
+ setPermissionsOnTree(folderRef, true, true);
+
+ NodeRef nodeWithPendingAcl = getFirstNodeWithAclPending(ContentModel.TYPE_FOLDER);
+ assertNotNull("No children files were found with pendingFixACl aspect", nodeWithPendingAcl);
+ ACLComparator aclComparator = new ACLComparator(nodeWithPendingAcl);
+
+ txnHelper.doInTransaction((RetryingTransactionCallback) () -> {
+ NodeRef parentRef = nodeDAO.getPrimaryParentAssoc(nodeDAO.getNodePair(nodeWithPendingAcl).getFirst()).getSecond()
+ .getParentRef();
+ permissionService.setInheritParentPermissions(parentRef, true, false);
+ permissionService.setPermission(parentRef, TEST_GROUP_NAME_FULL, PermissionService.COORDINATOR, true);
+ return null;
+ }, false, true);
+
+ triggerFixedACLJob();
+ assertEquals("Not all nodes were processed", 0, getNodesCountWithPendingFixedAclAspect());
+ assertTrue("Pending node is expected to have old permission", aclComparator.parentHasOriginalPermission());
+ assertTrue("Child of Pending node is expected to have old permission",
+ aclComparator.firstChildHasOriginalPermission());
+ assertTrue("Pending node is expected to have new permission",
+ aclComparator.hasPermission(TEST_GROUP_NAME_FULL, PermissionService.COORDINATOR));
+ assertTrue("Child of Pending node is expected to have new permission",
+ aclComparator.firstChildHasPermission(TEST_GROUP_NAME_FULL, PermissionService.COORDINATOR));
+ }
+ finally
+ {
+ deleteNodes(folderRef);
+ }
+ }
+
+ @Test
+ public void testAsyncCascadeUpdatePermissions()
+ {
+ NodeRef folderRef = createFolderHierarchyInRootForFolderTests("testAsyncCascadeUpdatePermissionsFolder");
+ List subFolders = fileFolderService.listFolders(folderRef);
+ NodeRef subFolder1 = subFolders.get(0).getNodeRef();
+ // Get ACLS for later comparison
+ ACLComparator aclComparatorBase = new ACLComparator(folderRef);
+ ACLComparator aclComparatorSubfolder1 = new ACLComparator(subFolder1);
+
+ try
+ {
+
+ // Set permissions First Subfolder - should put ACL on subfolder and add aspect to child
+ txnHelper.doInTransaction((RetryingTransactionCallback) () -> {
+ permissionService.setInheritParentPermissions(subFolder1, false, true);
+ permissionService.setPermission(subFolder1, TEST_GROUP_NAME_FULL, PermissionService.CONTRIBUTOR, true);
+ aclComparatorSubfolder1.setOriginalPermission(TEST_GROUP_NAME_FULL, PermissionService.CONTRIBUTOR);
+ return null;
+ }, false, true);
+
+ // Set permissions on base folder - should put ACL on base folder and add aspect to the previous subfolder
+ txnHelper.doInTransaction((RetryingTransactionCallback) () -> {
+ permissionService.setInheritParentPermissions(folderRef, false, true);
+ permissionService.setPermission(folderRef, TEST_GROUP_NAME_FULL, PermissionService.CONSUMER, true);
+ aclComparatorBase.setOriginalPermission(TEST_GROUP_NAME_FULL, PermissionService.CONSUMER);
+ return null;
+ }, false, true);
+
+ assertTrue("There are no nodes to process", getNodesCountWithPendingFixedAclAspect() > 0);
+
+ // Trigger job
+ triggerFixedACLJob();
+
+ // Validate results
+ assertEquals("Not all nodes were processed", 0, getNodesCountWithPendingFixedAclAspect());
+ assertTrue("Base Folder permissions are incorrect",
+ aclComparatorBase.hasPermission(TEST_GROUP_NAME_FULL, PermissionService.CONSUMER));
+ assertTrue("First Sub-Folder permissions are incorrect",
+ aclComparatorSubfolder1.hasPermission(TEST_GROUP_NAME_FULL, PermissionService.CONTRIBUTOR));
+ assertTrue("Child of First Sub-Folder permissions are incorrect",
+ aclComparatorSubfolder1.firstChildHasPermission(TEST_GROUP_NAME_FULL, PermissionService.CONTRIBUTOR));
+ }
+ finally
+ {
+ deleteNodes(folderRef);
+ }
+ }
+
+ /*
+ * Update the content of a node that has the aspect applied before job runs
+ */
+ @Test
+ public void testAsyncWithNodeContentUpdate()
+ {
+ NodeRef folderRef = createFolderHierarchyInRootForFileTests("testAsyncWithNodeContentUpdateFolder");
+
+ try
+ {
+ setPermissionsOnTree(folderRef, true, true);
+
+ NodeRef nodeWithPendingAcl = getFirstNodeWithAclPending(ContentModel.TYPE_CONTENT);
+ assertNotNull("No children files were found with pendingFixACl aspect", nodeWithPendingAcl);
+
+ txnHelper.doInTransaction((RetryingTransactionCallback) () -> {
+ ContentWriter contentWriter = contentService.getWriter(nodeWithPendingAcl, ContentModel.PROP_CONTENT, true);
+ contentWriter.setEncoding("UTF-8");
+ contentWriter.setMimetype(MimetypeMap.MIMETYPE_TEXT_PLAIN);
+ contentWriter.putContent("Updated content for file");
+ return null;
+ }, false, true);
+
+ triggerFixedACLJob();
+ assertEquals("Not all nodes were processed", 0, getNodesCountWithPendingFixedAclAspect());
+ }
+ finally
+ {
+ deleteNodes(folderRef);
+ }
+ }
+
+ /*
+ * Test setting permissions concurrently to actually cause the expected concurrency exception
+ */
+ @Test
+ public void testAsyncConcurrentPermissionsUpdate() throws Throwable
+ {
+ NodeRef folderRef = createFolderHierarchyInRootForFolderTests("testAsyncConcurrentPermissionsUpdateFolder");
+ List subFolders = fileFolderService.listFolders(folderRef);
+ String group_prefix = "TEST_";
+ int concurrentUpdates = 5;
+
+ try
+ {
+ txnHelper.doInTransaction((RetryingTransactionCallback) () -> {
+ Set zones = new HashSet(2, 1.0f);
+ zones.add(AuthorityService.ZONE_APP_DEFAULT);
+ for (int i = 0; i < concurrentUpdates; i++)
+ {
+ if (!authorityService.authorityExists(PermissionService.GROUP_PREFIX + group_prefix + i))
+ {
+ authorityService.createAuthority(AuthorityType.GROUP, group_prefix + i, group_prefix + i, zones);
+ }
+ }
+ return null;
+ }, false, true);
+
+ final Runnable[] runnables = new Runnable[concurrentUpdates];
+ final List threads = new ArrayList();
+ errors = new HashMap>();
+
+ // First thread is setting permissions on top folder
+ runnables[0] = createRunnableToSetPermissions(folderRef, group_prefix + 0, 0);
+ Thread threadBase = new Thread(runnables[0]);
+ threads.add(threadBase);
+ threadBase.start();
+
+ // All remaining threads will set permissions on sub-folders
+ for (int i = 1; i < runnables.length; i++)
+ {
+ NodeRef nodeRef = subFolders.get(i - 1).getNodeRef();
+ runnables[i] = createRunnableToSetPermissions(nodeRef, group_prefix + i, i);
+ Thread thread = new Thread(runnables[i]);
+ threads.add(thread);
+ thread.start();
+ }
+
+ // Wait for the threads to finish
+ for (Thread t : threads)
+ {
+ t.join();
+ }
+
+ // There should only be ConcurrencyFailureException
+ for (Map.Entry> error : errors.entrySet())
+ {
+ assertEquals("Unexpected error on Concurrent Update", ConcurrencyFailureException.class, error.getValue());
+ }
+
+ triggerFixedACLJob();
+ // We expect at least one error to occur when threads were running
+ assertTrue("There were no concurrency errors", errors.entrySet().size() > 0);
+
+ assertEquals("Not all nodes were processed", 0, getNodesCountWithPendingFixedAclAspect());
+ }
+ finally
+ {
+ deleteNodes(folderRef);
+ }
+ }
+
+ /*
+ * Test setting permissions concurrently as the job runs at the same time to actually cause the expected concurrency
+ * exception but the job should be able to recover
+ */
+ @Test
+ public void testAsyncConcurrentUpdateAndJob() throws Throwable
+ {
+ NodeRef folderRef = createFolderHierarchyInRootForFolderTests("testAsyncConcurrentUpdateAndJobFolder");
+ List subFolders = fileFolderService.listFolders(folderRef);
+ String group_prefix = "TEST_";
+ int concurrentUpdates = 5;
+
+ try
+ {
+ txnHelper.doInTransaction((RetryingTransactionCallback) () -> {
+ Set zones = new HashSet(2, 1.0f);
+ zones.add(AuthorityService.ZONE_APP_DEFAULT);
+ for (int i = 0; i < concurrentUpdates; i++)
+ {
+ if (!authorityService.authorityExists(PermissionService.GROUP_PREFIX + group_prefix + i))
+ {
+ authorityService.createAuthority(AuthorityType.GROUP, group_prefix + i, group_prefix + i, zones);
+ }
+
+ }
+ return null;
+ }, false, true);
+
+ final Runnable[] runnables = new Runnable[concurrentUpdates];
+ final List threads = new ArrayList();
+ errors = new HashMap>();
+
+ // Set permissions on top folder
+ setPermissionsOnTree(folderRef, true, true);
+
+ // First thread runs job to process setting permissions on top folder.
+ runnables[0] = createRunnableToRunJob();
+ Thread threadBase = new Thread(runnables[0]);
+ threads.add(threadBase);
+ threadBase.start();
+
+ // Meanwhile other threads are updating permissions on subfolders as the job runs
+ for (int i = 1; i < runnables.length; i++)
+ {
+ NodeRef nodeRef = subFolders.get(i - 1).getNodeRef();
+ runnables[i] = createRunnableToSetPermissions(nodeRef, group_prefix + i, i);
+ Thread thread = new Thread(runnables[i]);
+ threads.add(thread);
+ thread.start();
+ }
+
+ // Wait for the threads to finish
+ for (Thread t : threads)
+ {
+ t.join();
+ }
+
+ // Verify that we only have errors of type ConcurrencyFailureException
+ for (Map.Entry> error : errors.entrySet())
+ {
+ assertEquals("Unexpected error on Concurrent Update", ConcurrencyFailureException.class, error.getValue());
+ }
+ triggerFixedACLJob();
+ assertEquals("Not all nodes were processed", 0, getNodesCountWithPendingFixedAclAspect());
+ }
+ finally
+ {
+ deleteNodes(folderRef);
+ }
+ }
+
+ private Long getChild(Long parentId)
+ {
+ List children = fileFolderService.list(nodeDAO.getNodePair(parentId).getSecond());
+ if (children.size() > 0)
+ {
+ NodeRef childRef = children.get(0).getNodeRef();
+ return nodeDAO.getNodePair(childRef).getFirst();
+ }
+
+ return null;
+ }
+
+ private Long getAclOfFirstChild(Long nodeId)
+ {
+ Long firstChild = getChild(nodeId);
+ if (firstChild != null)
+ {
+ return nodeDAO.getNodeAclId(firstChild);
+ }
+ return null;
+ }
+
+ private void setFixedPermissionsForTestGroup(NodeRef folderRef, String authName, int thread)
+ {
+ txnHelper.doInTransaction((RetryingTransactionCallback) () -> {
+ try
+ {
+ Thread.sleep(200);
+ permissionService.setInheritParentPermissions(folderRef, false, true);
+ permissionService.setPermission(folderRef, authName, PermissionService.COORDINATOR, true);
+ }
+ catch (Exception e)
+ {
+ errors.put(thread, e.getClass());
+ throw e;
+ }
+
+ return null;
+ }, false, true);
+ }
+
+ private Runnable createRunnableToSetPermissions(NodeRef folderRef, String authName, int thread) throws Throwable
+ {
+ return new Runnable()
+ {
+ @Override
+ public synchronized void run()
+ {
+ setFixedPermissionsForTestGroup(folderRef, authName, thread);
+ }
+ };
+
+ }
+
+ private Runnable createRunnableToRunJob() throws Throwable
+ {
+ return new Runnable()
+ {
+ @Override
+ public synchronized void run()
+ {
+ triggerFixedACLJob();
+ }
+ };
}
@@ -129,44 +1250,23 @@ public class FixedAclUpdaterTest extends TestCase
}
}
- private static RetryingTransactionCallback createFolderHierchyCallback(final NodeRef root,
- final FileFolderService fileFolderService, final String rootName, final int[] filesPerLevel)
+ private NodeRef createFolderHierarchyInRoot(String folderName, int[] filesPerLevel)
{
- RetryingTransactionCallback cb = new RetryingTransactionCallback()
- {
- @Override
- public NodeRef execute() throws Throwable
- {
- NodeRef parent = createFile(fileFolderService, root, rootName, ContentModel.TYPE_FOLDER);
- createFolderHierchy(fileFolderService, parent, 0, filesPerLevel);
- return parent;
- }
- };
- return cb;
+ return txnHelper.doInTransaction((RetryingTransactionCallback) () -> {
+ NodeRef parent = createFile(fileFolderService, homeFolderNodeRef, folderName, ContentModel.TYPE_FOLDER);
+ createFolderHierchy(fileFolderService, parent, 0, filesPerLevel);
+ return parent;
+ }, false, true);
}
- @Override
- public void tearDown() throws Exception
+ private NodeRef createFolderHierarchyInRootForFolderTests(String folderName)
{
- // delete created folder hierarchy
- try
- {
- txnHelper.doInTransaction((RetryingTransactionCallback) () -> {
- Set aspect = new HashSet<>();
- aspect.add(ContentModel.ASPECT_TEMPORARY);
- nodeDAO.addNodeAspects(nodeDAO.getNodePair(folderAsyncCallNodeRef).getFirst(), aspect);
- nodeDAO.addNodeAspects(nodeDAO.getNodePair(folderSyncCallNodeRef).getFirst(), aspect);
- nodeDAO.addNodeAspects(nodeDAO.getNodePair(folderAsyncCallWithCreateNodeRef).getFirst(), aspect);
- fileFolderService.delete(folderAsyncCallNodeRef);
- fileFolderService.delete(folderSyncCallNodeRef);
- fileFolderService.delete(folderAsyncCallWithCreateNodeRef);
- return null;
- }, false, true);
- }
- catch (Exception e)
- {
- }
- AuthenticationUtil.clearCurrentSecurityContext();
+ return createFolderHierarchyInRoot(folderName, filesPerLevelMoreFolders);
+ }
+
+ private NodeRef createFolderHierarchyInRootForFileTests(String folderName)
+ {
+ return createFolderHierarchyInRoot(folderName, filesPerLevelMoreFiles);
}
private static NodeRef createFile(FileFolderService fileFolderService, NodeRef parent, String name, QName type)
@@ -188,125 +1288,216 @@ public class FixedAclUpdaterTest extends TestCase
}, true, true);
}
- @Test
- public void testSyncTimeOut()
+ private void setPermissionsOnTree(NodeRef folderRef, boolean asyncCall, boolean shouldHaveNodesPending)
{
- testWork(folderSyncCallNodeRef, false);
- }
-
- @Test
- public void testAsync()
- {
- testWork(folderAsyncCallNodeRef, true);
- }
-
- @Test
- public void testAsyncWithNodeCreation()
- {
- testWorkWithNodeCreation(folderAsyncCallWithCreateNodeRef, true);
- }
-
- private void testWork(NodeRef folderRef, boolean asyncCall)
- {
- setPermissionsOnTree(folderRef, asyncCall);
- triggerFixedACLJob(folderRef);
- }
-
- private void testWorkWithNodeCreation(NodeRef folderRef, boolean asyncCall)
- {
- setPermissionsOnTree(folderRef, asyncCall);
-
- // MNT-21847 - Create a new content in folder that has the aspect applied
txnHelper.doInTransaction((RetryingTransactionCallback) () -> {
- NodeRef folderWithPendingAcl = getFirstFolderWithAclPending(folderRef);
- assertNotNull("No children folders were found with pendingFixACl aspect", folderWithPendingAcl);
- createFile(fileFolderService, folderWithPendingAcl, "NewFile", ContentModel.TYPE_CONTENT);
- return null;
- }, false, true);
- triggerFixedACLJob(folderRef);
- }
-
- private void setPermissionsOnTree(NodeRef folderRef, boolean asyncCall)
- {
- // kick it off by setting inherit parent permissions == false
- txnHelper.doInTransaction((RetryingTransactionCallback) () -> {
+ // create a group for tests
+ Set zones = new HashSet(2, 1.0f);
+ zones.add(AuthorityService.ZONE_APP_DEFAULT);
+ if (!authorityService.authorityExists(TEST_GROUP_NAME_FULL))
+ {
+ authorityService.createAuthority(AuthorityType.GROUP, TEST_GROUP_NAME, TEST_GROUP_NAME, zones);
+ }
+ // kick it off by setting inherit parent permissions == false
permissionService.setInheritParentPermissions(folderRef, false, asyncCall);
- assertTrue("asyncCallRequired should be true", isFixedAclAsyncRequired());
+ permissionService.setPermission(folderRef, TEST_GROUP_NAME_FULL, DEFAULT_PERMISSION, true);
return null;
}, false, true);
// Assert that there are nodes with aspect ASPECT_PENDING_FIX_ACL to be processed
- txnHelper.doInTransaction((RetryingTransactionCallback) () -> {
- assertTrue("There are no nodes to process", getNodesCountWithPendingFixedAclAspect() > 0);
- return null;
- }, false, true);
+ int pendingNodes = getNodesCountWithPendingFixedAclAspect();
+ if (shouldHaveNodesPending)
+ {
+ assertTrue("There are no nodes to process", pendingNodes > 0);
+ }
+ else
+ {
+ assertEquals("There are nodes to process", pendingNodes, 0);
+ }
}
- private void triggerFixedACLJob(NodeRef folder)
+ private void triggerFixedACLJob()
{
// run the fixedAclUpdater until there is nothing more to fix (running the updater may create more to fix up) or
- // the count doesn't change, meaning we have a problem.
+ // the count doesn't change for 3 cycles, meaning we have a problem.
txnHelper.doInTransaction((RetryingTransactionCallback) () -> {
int count = 0;
int previousCount = 0;
+ int rounds = 0;
do
{
previousCount = count;
count = fixedAclUpdater.execute();
- } while (count > 0 && previousCount != count);
- return null;
- }, false, true);
-
- // check if nodes with ASPECT_PENDING_FIX_ACL are processed
- txnHelper.doInTransaction((RetryingTransactionCallback) () -> {
- assertEquals("Not all nodes were processed", 0, getNodesCountWithPendingFixedAclAspect());
- //Remove the tree that failed so it does not influence the other test results
- removeNodesWithPendingAcl(folder);
+ if (count == previousCount)
+ {
+ rounds++;
+ }
+ } while (count > 0 && rounds <= 3);
return null;
}, false, true);
}
- private NodeRef getFirstFolderWithAclPending(NodeRef parentNodeRef)
+ private NodeRef getFirstNodeWithAclPending(QName nodeType, NodeRef parentRef)
{
- NodeRef folderWithPendingFixedAcl = null;
- List primaryChildFolders = fileFolderService.listFolders(parentNodeRef);
- for (int i = 0; i < primaryChildFolders.size(); i++)
- {
- NodeRef thisChildFolder = primaryChildFolders.get(i).getNodeRef();
- Long thisChildNodeId = nodeDAO.getNodePair(thisChildFolder).getFirst();
- if (nodeDAO.hasNodeAspect(thisChildNodeId, ContentModel.ASPECT_PENDING_FIX_ACL))
+ return txnHelper.doInTransaction((RetryingTransactionCallback) () -> {
+ final GetNodesWithAspectCallback getNodesCallback = new GetNodesWithAspectCallback();
+ nodeDAO.getNodesWithAspects(Collections.singleton(ContentModel.ASPECT_PENDING_FIX_ACL), 0l, null, getNodesCallback);
+ List nodesWithAclPendingAspect = getNodesCallback.getNodes();
+ for (int i = 0; i < nodesWithAclPendingAspect.size(); i++)
{
- folderWithPendingFixedAcl = thisChildFolder;
- break;
+ NodeRef nodeRef = nodesWithAclPendingAspect.get(i);
+ boolean isDescendent = false;
+ List path = fileFolderService.getNamePath(homeFolderNodeRef, nodeRef);
+ for (FileInfo element : path)
+ {
+ if (element.getNodeRef().equals(parentRef))
+ {
+ isDescendent = true;
+ }
+ }
+ if (isDescendent && nodeDAO.getNodeType(nodeDAO.getNodePair(nodeRef).getFirst()).equals(nodeType))
+ {
+ return nodeRef;
+ }
}
+ return null;
+ }, false, true);
- if (folderWithPendingFixedAcl == null)
- {
- folderWithPendingFixedAcl = getFirstFolderWithAclPending(thisChildFolder);
- }
- }
- return folderWithPendingFixedAcl;
}
- private void removeNodesWithPendingAcl(NodeRef folder)
+ private NodeRef getFirstNodeWithAclPending(QName nodeType)
+ {
+ return txnHelper.doInTransaction((RetryingTransactionCallback) () -> {
+ final GetNodesWithAspectCallback getNodesCallback = new GetNodesWithAspectCallback();
+ nodeDAO.getNodesWithAspects(Collections.singleton(ContentModel.ASPECT_PENDING_FIX_ACL), 0l, null, getNodesCallback);
+ List nodesWithAclPendingAspect = getNodesCallback.getNodes();
+ for (int i = 0; i < nodesWithAclPendingAspect.size(); i++)
+ {
+ NodeRef nodeRef = nodesWithAclPendingAspect.get(i);
+ if (nodeDAO.getNodeType(nodeDAO.getNodePair(nodeRef).getFirst()).equals(nodeType))
+ {
+ return nodeRef;
+ }
+ }
+ return null;
+ }, false, true);
+
+ }
+
+ private void deleteNodes(NodeRef folder)
{
txnHelper.doInTransaction((RetryingTransactionCallback) () -> {
- Set aspect = new HashSet<>();
- aspect.add(ContentModel.ASPECT_TEMPORARY);
- nodeDAO.addNodeAspects(nodeDAO.getNodePair(folder).getFirst(), aspect);
- fileFolderService.delete(folder);
+ if (nodeDAO.exists(folder))
+ {
+ Set aspect = new HashSet<>();
+ aspect.add(ContentModel.ASPECT_TEMPORARY);
+ nodeDAO.addNodeAspects(nodeDAO.getNodePair(folder).getFirst(), aspect);
+ fileFolderService.delete(folder);
+ }
return null;
}, false, true);
}
- private static boolean isFixedAclAsyncRequired()
+ private class ACLComparator
{
- if (AlfrescoTransactionSupport.getResource(FixedAclUpdater.FIXED_ACL_ASYNC_REQUIRED_KEY) == null)
+ Long parentId;
+ Long oParentACL;
+ Long oFirstChildACL;
+ Long tParentACL;
+ Long tFirstChildACL;
+ String originalPermission = DEFAULT_PERMISSION;
+ String originalAuthority = TEST_GROUP_NAME_FULL;
+ Long pendingInheritFrom = 0L;
+
+ public ACLComparator(NodeRef nodeRef)
{
- return false;
+ Long parentId = nodeDAO.getNodePair(nodeRef).getFirst();
+ this.parentId = parentId;
+ this.oParentACL = nodeDAO.getNodeAclId(parentId);
+ this.oFirstChildACL = getAclOfFirstChild(parentId);
+ updateCurrentACLs();
+ }
+
+ public void updateCurrentACLs()
+ {
+ this.tParentACL = nodeDAO.getNodeAclId(parentId);
+ this.tFirstChildACL = getAclOfFirstChild(parentId);
+
+ if (nodeDAO.hasNodeAspect(parentId, ContentModel.ASPECT_PENDING_FIX_ACL))
+ {
+ this.pendingInheritFrom = (Long) nodeDAO.getNodeProperty(parentId, ContentModel.PROP_INHERIT_FROM_ACL);
+ }
+ else
+ {
+ this.pendingInheritFrom = 0L;
+ }
+ }
+
+ public void compareACLs()
+ {
+ updateCurrentACLs();
+ assertTrue("Permissions were not changed on top folder", !oParentACL.equals(tParentACL));
+ assertTrue("Permissions were not changed on child", !oFirstChildACL.equals(tFirstChildACL));
+ }
+
+ public void setOriginalPermission(String originalAuthority, String originalPermission)
+ {
+ this.originalPermission = originalPermission;
+ this.originalAuthority = originalAuthority;
+ }
+
+ public boolean hasPermission(String authority, String permission)
+ {
+ return hasPermission(parentId, authority, permission);
+ }
+
+ public boolean hasPermission(Long nodeId, String authority, String permission)
+ {
+ boolean hasExpectedPermission = false;
+ Set permissions = permissionService.getAllSetPermissions(nodeDAO.getNodePair(nodeId).getSecond());
+ for (Iterator iterator = permissions.iterator(); iterator.hasNext();)
+ {
+ AccessPermission accessPermission = (AccessPermission) iterator.next();
+ if (accessPermission.getPermission().equalsIgnoreCase(permission)
+ && accessPermission.getAuthority().equalsIgnoreCase(authority))
+ {
+ hasExpectedPermission = true;
+ break;
+ }
+ }
+ return hasExpectedPermission;
+ }
+
+ public boolean firstChildHasPermission(String authority, String permission)
+ {
+ return hasPermission(getChild(parentId), authority, permission);
+ }
+
+ public boolean firstChildHasOriginalPermission()
+ {
+ return hasPermission(getChild(parentId), originalAuthority, originalPermission);
+ }
+
+ public boolean parentHasOriginalPermission()
+ {
+ return hasPermission(originalAuthority, originalPermission);
+ }
+
+ private Long getParentAcl()
+ {
+ return tParentACL;
+ }
+
+ private Long getChildAcl()
+ {
+ return tFirstChildACL;
+ }
+
+ private Long getPendingInheritFromAcl()
+ {
+ return pendingInheritFrom;
}
- return (Boolean) AlfrescoTransactionSupport.getResource(FixedAclUpdater.FIXED_ACL_ASYNC_REQUIRED_KEY);
}
private static class GetNodesCountWithAspectCallback implements NodeRefQueryCallback
@@ -326,6 +1517,23 @@ public class FixedAclUpdaterTest extends TestCase
}
}
+ private static class GetNodesWithAspectCallback implements NodeRefQueryCallback
+ {
+ private List nodes = new ArrayList<>();
+
+ @Override
+ public boolean handle(Pair nodePair)
+ {
+ nodes.add(nodePair.getSecond());
+ return true;
+ }
+
+ public List getNodes()
+ {
+ return nodes;
+ }
+ }
+
/**
* Creates a level in folder/file hierarchy. Intermediate levels will contain folders and last ones files
*
@@ -346,7 +1554,7 @@ public class FixedAclUpdaterTest extends TestCase
int numFiles = filesPerLevel[level];
for (int i = 0; i < numFiles; i++)
{
- NodeRef node = createFile(fileFolderService, parent, "LVL" + level + i, ContentModel.TYPE_FOLDER);
+ NodeRef node = createFile(fileFolderService, parent, "-LVL" + level + i, ContentModel.TYPE_FOLDER);
createFolderHierchy(fileFolderService, node, level + 1, filesPerLevel);
}
}
@@ -356,7 +1564,7 @@ public class FixedAclUpdaterTest extends TestCase
int numFiles = filesPerLevel[level];
for (int i = 0; i < numFiles; i++)
{
- createFile(fileFolderService, parent, "File" + i, ContentModel.TYPE_CONTENT);
+ createFile(fileFolderService, parent, "-File" + i, ContentModel.TYPE_CONTENT);
}
}
}