diff --git a/packaging/tests/tas-restapi/src/test/java/org/alfresco/rest/rules/GetInheritedRulesTests.java b/packaging/tests/tas-restapi/src/test/java/org/alfresco/rest/rules/GetInheritedRulesTests.java new file mode 100644 index 0000000000..ebe1017b2c --- /dev/null +++ b/packaging/tests/tas-restapi/src/test/java/org/alfresco/rest/rules/GetInheritedRulesTests.java @@ -0,0 +1,142 @@ +/* + * #%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.rest.rules; + +import static org.alfresco.rest.rules.RulesTestsUtils.createRuleModelWithModifiedValues; +import static org.alfresco.utility.report.log.Step.STEP; +import static org.testng.Assert.assertEquals; + +import java.util.List; +import java.util.stream.Collectors; +import java.util.stream.IntStream; + +import org.alfresco.rest.RestTest; +import org.alfresco.rest.model.RestRuleModel; +import org.alfresco.rest.model.RestRuleModelsCollection; +import org.alfresco.rest.model.RestRuleSetLinkModel; +import org.alfresco.rest.model.RestRuleSetModel; +import org.alfresco.rest.model.RestRuleSetModelsCollection; +import org.alfresco.utility.model.FolderModel; +import org.alfresco.utility.model.SiteModel; +import org.alfresco.utility.model.TestGroup; +import org.alfresco.utility.model.UserModel; +import org.testng.annotations.BeforeClass; +import org.testng.annotations.Test; + +/** + * Tests for GET /nodes/{nodeId}/rule-sets/{ruleSetId}/rules with rule inheritance. + */ +@Test(groups = {TestGroup.RULES}) +public class GetInheritedRulesTests extends RestTest +{ + private UserModel user; + private SiteModel site; + + @BeforeClass(alwaysRun = true) + public void dataPreparation() + { + STEP("Create a user and site"); + user = dataUser.createRandomTestUser(); + site = dataSite.usingUser(user).createPublicRandomSite(); + } + + /** + * Check we can get all the rules for the folder by providing the different rule set ids. + */ + @Test (groups = { TestGroup.REST_API, TestGroup.RULES, TestGroup.SANITY }) + public void getInheritedRules() + { + STEP("Create a parent and child folder, each with inheriting rules"); + FolderModel parent = dataContent.usingUser(user).usingSite(site).createFolder(); + FolderModel child = dataContent.usingUser(user).usingResource(parent).createFolder(); + RestRuleModel parentRule = createRuleModelWithModifiedValues(); + parentRule = restClient.authenticateUser(user).withCoreAPI().usingNode(parent).usingDefaultRuleSet().createSingleRule(parentRule); + RestRuleModel childRule = createRuleModelWithModifiedValues(); + childRule = restClient.authenticateUser(user).withCoreAPI().usingNode(child).usingDefaultRuleSet().createSingleRule(childRule); + + STEP("Get the rules in the default rule set for the child folder"); + RestRuleModelsCollection rules = restClient.authenticateUser(user).withCoreAPI().usingNode(child).usingDefaultRuleSet().getListOfRules(); + rules.assertThat().entriesListContains("id", childRule.getId()) + .and().entriesListCountIs(1); + + STEP("Get the rules in the inherited rule set for the child folder"); + RestRuleSetModelsCollection ruleSets = restClient.authenticateUser(user).withCoreAPI().usingNode(child).include("inclusionType").getListOfRuleSets(); + String inheritedRuleSetId = ruleSets.getEntries().stream() + .filter(ruleSet -> ruleSet.onModel().getInclusionType().equals("inherited")) + .findFirst().get().onModel().getId(); + RestRuleModelsCollection inheritedRules = restClient.authenticateUser(user).withCoreAPI().usingNode(child).usingRuleSet(inheritedRuleSetId).getListOfRules(); + inheritedRules.assertThat().entriesListContains("id", parentRule.getId()) + .and().entriesListCountIs(1); + } + + /** + * Check that we only get each rule once with linking and inheritance, and the order is correct. + *

+ * The folder structure for this test is as follows: + *

+     *      A --[links]-> DRuleSet
+     *      +-B --[owns]-> BRuleSet
+     *        +-C --[owns]-> CRuleSet
+     *          +-D --[owns]--> DRuleSet
+     * 
+ */ + @Test (groups = { TestGroup.REST_API, TestGroup.RULES, TestGroup.SANITY }) + public void rulesReturnedAreUnique() + { + STEP("Create four folders with rules"); + FolderModel folderA = dataContent.usingUser(user).usingSite(site).createFolder(); + FolderModel folderB = dataContent.usingUser(user).usingResource(folderA).createFolder(); + FolderModel folderC = dataContent.usingUser(user).usingResource(folderB).createFolder(); + FolderModel folderD = dataContent.usingUser(user).usingResource(folderC).createFolder(); + RestRuleModel ruleB = restClient.authenticateUser(user).withCoreAPI().usingNode(folderB).usingDefaultRuleSet().createSingleRule(createRuleModelWithModifiedValues()); + RestRuleModel ruleC = restClient.authenticateUser(user).withCoreAPI().usingNode(folderC).usingDefaultRuleSet().createSingleRule(createRuleModelWithModifiedValues()); + RestRuleModel ruleD = restClient.authenticateUser(user).withCoreAPI().usingNode(folderD).usingDefaultRuleSet().createSingleRule(createRuleModelWithModifiedValues()); + STEP("Link folderA to ruleSetD"); + RestRuleSetLinkModel linkModel = new RestRuleSetLinkModel(); + linkModel.setId(folderD.getNodeRef()); + restClient.authenticateUser(user).withCoreAPI().usingNode(folderA).createRuleLink(linkModel); + + STEP("Get the rule sets for the folderD"); + List ruleSets = restClient.authenticateUser(user).withCoreAPI().usingNode(folderD).getListOfRuleSets().getEntries(); + + STEP("Check the rules for each rule set are as expected"); + List expectedRuleIds = List.of(ruleD, ruleB, ruleC); + IntStream.range(0, 2).forEach(index -> { + String ruleSetId = ruleSets.get(index).onModel().getId(); + List rules = restClient.authenticateUser(user) + .withCoreAPI() + .usingNode(folderD) + .usingRuleSet(ruleSetId) + .getListOfRules() + .getEntries() + .stream() + .map(RestRuleModel::onModel) + .collect(Collectors.toList()); + assertEquals(rules, List.of(expectedRuleIds.get(index)), "Unexpected rules found for rule set " + ruleSetId); + }); + assertEquals(ruleSets.size(), 3, "Expected three unique rule sets to be returned but got " + ruleSets); + } +} diff --git a/remote-api/src/main/java/org/alfresco/rest/api/impl/rules/RulesImpl.java b/remote-api/src/main/java/org/alfresco/rest/api/impl/rules/RulesImpl.java index 5f63d37cd7..81efa535e8 100644 --- a/remote-api/src/main/java/org/alfresco/rest/api/impl/rules/RulesImpl.java +++ b/remote-api/src/main/java/org/alfresco/rest/api/impl/rules/RulesImpl.java @@ -63,9 +63,10 @@ public class RulesImpl implements Rules final Paging paging) { final NodeRef folderNodeRef = validator.validateFolderNode(folderNodeId, false); - validator.validateRuleSetNode(ruleSetId, folderNodeRef); + NodeRef ruleSetNode = validator.validateRuleSetNode(ruleSetId, folderNodeRef); + NodeRef owningFolder = ruleService.getOwningNodeRef(ruleSetNode); - final List rules = ruleService.getRules(folderNodeRef).stream() + final List rules = ruleService.getRules(owningFolder, false).stream() .map(ruleModel -> loadRuleAndConvertActionParams(ruleModel, includes)) .collect(Collectors.toList()); diff --git a/remote-api/src/test/java/org/alfresco/rest/api/impl/rules/RulesImplTest.java b/remote-api/src/test/java/org/alfresco/rest/api/impl/rules/RulesImplTest.java index f7c4aad7d5..cd6c8b67f4 100644 --- a/remote-api/src/test/java/org/alfresco/rest/api/impl/rules/RulesImplTest.java +++ b/remote-api/src/test/java/org/alfresco/rest/api/impl/rules/RulesImplTest.java @@ -115,7 +115,8 @@ public class RulesImplTest extends TestCase given(nodeValidatorMock.validateRuleNode(any(), any())).willReturn(RULE_NODE_REF); given(ruleServiceMock.getRule(RULE_NODE_REF)).willReturn(ruleModel); - given(ruleServiceMock.getRules(FOLDER_NODE_REF)).willReturn(List.of(ruleModel)); + given(ruleServiceMock.getRules(FOLDER_NODE_REF, false)).willReturn(List.of(ruleModel)); + given(ruleServiceMock.getOwningNodeRef(RULE_SET_NODE_REF)).willReturn(FOLDER_NODE_REF); given(ruleLoaderMock.loadRule(ruleModel, INCLUDE)).willReturn(ruleMock); @@ -126,13 +127,15 @@ public class RulesImplTest extends TestCase public void testGetRules() { given(ruleLoaderMock.loadRule(ruleModel, emptyList())).willReturn(ruleMock); + // when final CollectionWithPagingInfo rulesPage = rules.getRules(FOLDER_NODE_ID, RULE_SET_ID, INCLUDE, PAGING); then(nodeValidatorMock).should().validateFolderNode(FOLDER_NODE_ID, false); then(nodeValidatorMock).should().validateRuleSetNode(RULE_SET_ID, FOLDER_NODE_REF); then(nodeValidatorMock).shouldHaveNoMoreInteractions(); - then(ruleServiceMock).should().getRules(FOLDER_NODE_REF); + then(ruleServiceMock).should().getOwningNodeRef(RULE_SET_NODE_REF); + then(ruleServiceMock).should().getRules(FOLDER_NODE_REF, false); then(ruleServiceMock).shouldHaveNoMoreInteractions(); then(ruleLoaderMock).should().loadRule(ruleModel, emptyList()); then(ruleLoaderMock).shouldHaveNoMoreInteractions(); @@ -148,12 +151,13 @@ public class RulesImplTest extends TestCase @Test public void testGetRules_emptyResult() { - given(ruleServiceMock.getRules(any())).willReturn(emptyList()); + given(ruleServiceMock.getRules(FOLDER_NODE_REF, false)).willReturn(emptyList()); // when final CollectionWithPagingInfo rulesPage = rules.getRules(FOLDER_NODE_ID, RULE_SET_ID, INCLUDE, PAGING); - then(ruleServiceMock).should().getRules(FOLDER_NODE_REF); + then(ruleServiceMock).should().getOwningNodeRef(RULE_SET_NODE_REF); + then(ruleServiceMock).should().getRules(FOLDER_NODE_REF, false); then(ruleServiceMock).shouldHaveNoMoreInteractions(); assertThat(rulesPage) .isNotNull() diff --git a/repository/src/main/java/org/alfresco/repo/rule/RuleServiceImpl.java b/repository/src/main/java/org/alfresco/repo/rule/RuleServiceImpl.java index 646ef4352a..fa92c02204 100644 --- a/repository/src/main/java/org/alfresco/repo/rule/RuleServiceImpl.java +++ b/repository/src/main/java/org/alfresco/repo/rule/RuleServiceImpl.java @@ -26,6 +26,7 @@ package org.alfresco.repo.rule; import static org.alfresco.repo.rule.RuleModel.ASPECT_IGNORE_INHERITED_RULES; +import static org.alfresco.repo.rule.RuleModel.ASSOC_RULE_FOLDER; import java.io.Serializable; import java.util.ArrayList; @@ -35,6 +36,9 @@ import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; +import java.util.stream.Collectors; + +import com.google.common.collect.Sets; import org.alfresco.model.ContentModel; import org.alfresco.repo.action.ActionImpl; @@ -257,7 +261,7 @@ public class RuleServiceImpl policyComponent.bindAssociationBehaviour( NodeServicePolicies.OnCreateChildAssociationPolicy.QNAME, RuleModel.ASPECT_RULES, - RuleModel.ASSOC_RULE_FOLDER, + ASSOC_RULE_FOLDER, new JavaBehaviour(this, "onCreateChildAssociation")); policyComponent.bindClassBehaviour( NodeServicePolicies.OnAddAspectPolicy.QNAME, @@ -349,8 +353,8 @@ public class RuleServiceImpl List assocs = this.runtimeNodeService.getChildAssocs( nodeRef, - RuleModel.ASSOC_RULE_FOLDER, - RuleModel.ASSOC_RULE_FOLDER); + ASSOC_RULE_FOLDER, + ASSOC_RULE_FOLDER); if (assocs.size() > 1) { throw new ActionServiceException("There is more than one rule folder, which is invalid."); @@ -1568,6 +1572,12 @@ public class RuleServiceImpl return this.nodeService.getPrimaryParent(systemFolder).getParentRef(); } + @Override + public NodeRef getOwningNodeRef(NodeRef ruleSet) + { + return nodeService.getPrimaryParent(ruleSet).getParentRef(); + } + @Override public NodeRef getOwningNodeRef(final Action action) { @@ -1660,7 +1670,7 @@ public class RuleServiceImpl @Experimental public NodeRef getRuleSetNode(final NodeRef folderNodeRef) { - return runtimeNodeService.getChildAssocs(folderNodeRef, RuleModel.ASSOC_RULE_FOLDER, RuleModel.ASSOC_RULE_FOLDER).stream() + return runtimeNodeService.getChildAssocs(folderNodeRef, ASSOC_RULE_FOLDER, ASSOC_RULE_FOLDER).stream() .map(ChildAssociationRef::getChildRef) .findFirst() .orElse(null); @@ -1670,7 +1680,10 @@ public class RuleServiceImpl @Experimental public boolean isRuleSetAssociatedWithFolder(final NodeRef ruleSetNodeRef, final NodeRef folderNodeRef) { - return isChildOf(ruleSetNodeRef, RuleModel.ASSOC_RULE_FOLDER, folderNodeRef); + List associations = runtimeNodeService.getParentAssocs(ruleSetNodeRef, ASSOC_RULE_FOLDER, ASSOC_RULE_FOLDER); + Set associatedFolders = associations.stream().map(ChildAssociationRef::getParentRef).collect(Collectors.toSet()); + Set supplyingFolders = new HashSet<>(getNodesSupplyingRuleSets(folderNodeRef)); + return !Sets.intersection(associatedFolders, supplyingFolders).isEmpty(); } @Override diff --git a/repository/src/main/java/org/alfresco/service/cmr/rule/RuleService.java b/repository/src/main/java/org/alfresco/service/cmr/rule/RuleService.java index 6434dfc07b..03a82e80df 100644 --- a/repository/src/main/java/org/alfresco/service/cmr/rule/RuleService.java +++ b/repository/src/main/java/org/alfresco/service/cmr/rule/RuleService.java @@ -311,7 +311,17 @@ public interface RuleService */ @Auditable(parameters = {"action"}) public NodeRef getOwningNodeRef(Action action); - + + /** + * Returns the owning node reference for a rule. + * + * @param ruleSet The rule set node. + * @return the owning node reference + */ + @Auditable (parameters = { "ruleSet" }) + @Experimental + NodeRef getOwningNodeRef(NodeRef ruleSet); + /** * Indicates whether the passed rule node reference is linked to another * rule node. @@ -353,7 +363,7 @@ public interface RuleService NodeRef getRuleSetNode(final NodeRef folderNodeRef); /** - * Check if rule set's associated parent matches folder node. + * Check if rule set is associated (owned/linked/inherited) with the given folder node. * * @param ruleSetNodeRef - node reference of a rule set * @param folderNodeRef - node reference of a folder diff --git a/repository/src/test/java/org/alfresco/repo/rule/RuleServiceImplUnitTest.java b/repository/src/test/java/org/alfresco/repo/rule/RuleServiceImplUnitTest.java index 868f355506..11f7caebc2 100644 --- a/repository/src/test/java/org/alfresco/repo/rule/RuleServiceImplUnitTest.java +++ b/repository/src/test/java/org/alfresco/repo/rule/RuleServiceImplUnitTest.java @@ -254,6 +254,8 @@ public class RuleServiceImplUnitTest boolean associated = ruleService.isRuleSetAssociatedWithFolder(RULE_SET_NODE, FOLDER_NODE); then(runtimeNodeService).should().getParentAssocs(RULE_SET_NODE, ASSOC_RULE_FOLDER, ASSOC_RULE_FOLDER); + then(runtimeNodeService).should().hasAspect(FOLDER_NODE, ASPECT_IGNORE_INHERITED_RULES); + then(runtimeNodeService).should().getParentAssocs(FOLDER_NODE); then(runtimeNodeService).shouldHaveNoMoreInteractions(); then(nodeService).shouldHaveNoInteractions(); assertThat(associated).isTrue(); @@ -268,6 +270,8 @@ public class RuleServiceImplUnitTest boolean associated = ruleService.isRuleSetAssociatedWithFolder(RULE_SET_NODE, FOLDER_NODE); then(runtimeNodeService).should().getParentAssocs(RULE_SET_NODE, ASSOC_RULE_FOLDER, ASSOC_RULE_FOLDER); + then(runtimeNodeService).should().hasAspect(FOLDER_NODE, ASPECT_IGNORE_INHERITED_RULES); + then(runtimeNodeService).should().getParentAssocs(FOLDER_NODE); then(runtimeNodeService).shouldHaveNoMoreInteractions(); then(nodeService).shouldHaveNoInteractions(); assertThat(associated).isFalse(); @@ -283,11 +287,45 @@ public class RuleServiceImplUnitTest boolean associated = ruleService.isRuleSetAssociatedWithFolder(RULE_SET_NODE, FOLDER_NODE); then(runtimeNodeService).should().getParentAssocs(RULE_SET_NODE, ASSOC_RULE_FOLDER, ASSOC_RULE_FOLDER); + then(runtimeNodeService).should().hasAspect(FOLDER_NODE, ASPECT_IGNORE_INHERITED_RULES); + then(runtimeNodeService).should().getParentAssocs(FOLDER_NODE); then(runtimeNodeService).shouldHaveNoMoreInteractions(); then(nodeService).shouldHaveNoInteractions(); assertThat(associated).isFalse(); } + /** + * Check that a rule set is associated with the folder in the following case: + *
+     *     parent --[link]-> rule set <-[owned]-- owningFolder
+     *     +- child
+     * 
+ */ + @Test + public void testIsRuleSetAssociatedWithFolder_inheritedLinkedAssociation() + { + // The rule is owned by one node. + NodeRef owningFolder = new NodeRef("owning://node/"); + // The rule is linked to by the parent node. + NodeRef parent = new NodeRef("parent://node/"); + List ruleAssociations = List.of(createAssociation(owningFolder, RULE_SET_NODE), createAssociation(parent, RULE_SET_NODE)); + given(runtimeNodeService.getParentAssocs(RULE_SET_NODE, ASSOC_RULE_FOLDER, ASSOC_RULE_FOLDER)).willReturn(ruleAssociations); + // The parent and the child both supply rule sets. + given(runtimeNodeService.getParentAssocs(FOLDER_NODE)).willReturn(List.of(createAssociation(parent, FOLDER_NODE))); + + // when + boolean associated = ruleService.isRuleSetAssociatedWithFolder(RULE_SET_NODE, FOLDER_NODE); + + then(runtimeNodeService).should().getParentAssocs(RULE_SET_NODE, ASSOC_RULE_FOLDER, ASSOC_RULE_FOLDER); + then(runtimeNodeService).should().hasAspect(FOLDER_NODE, ASPECT_IGNORE_INHERITED_RULES); + then(runtimeNodeService).should().getParentAssocs(FOLDER_NODE); + then(runtimeNodeService).should().hasAspect(parent, ASPECT_IGNORE_INHERITED_RULES); + then(runtimeNodeService).should().getParentAssocs(parent); + then(runtimeNodeService).shouldHaveNoMoreInteractions(); + then(nodeService).shouldHaveNoInteractions(); + assertThat(associated).isTrue(); + } + @Test public void testIsRuleAssociatedWithRuleSet() {