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); } } }