diff --git a/packaging/tests/tas-restapi/src/test/java/org/alfresco/rest/rules/GetRuleSetsTests.java b/packaging/tests/tas-restapi/src/test/java/org/alfresco/rest/rules/GetRuleSetsTests.java index a774966424..77e5855894 100644 --- a/packaging/tests/tas-restapi/src/test/java/org/alfresco/rest/rules/GetRuleSetsTests.java +++ b/packaging/tests/tas-restapi/src/test/java/org/alfresco/rest/rules/GetRuleSetsTests.java @@ -35,6 +35,7 @@ import org.alfresco.rest.RestTest; import org.alfresco.rest.model.RestRuleModel; import org.alfresco.rest.model.RestRuleSetModel; import org.alfresco.rest.model.RestRuleSetModelsCollection; +import org.alfresco.rest.model.RestRuleSettingsModel; import org.alfresco.utility.model.FolderModel; import org.alfresco.utility.model.SiteModel; import org.alfresco.utility.model.TestGroup; @@ -51,6 +52,8 @@ public class GetRuleSetsTests extends RestTest private UserModel user; private SiteModel site; private FolderModel ruleFolder; + private FolderModel inheritingChildFolder; + private FolderModel notInheritingChildFolder; private RestRuleModel rule; private String ruleSetId; @@ -62,6 +65,14 @@ public class GetRuleSetsTests extends RestTest site = dataSite.usingUser(user).createPublicRandomSite(); ruleFolder = dataContent.usingUser(user).usingSite(site).createFolder(); + STEP("Create two children of the folder - one that inherits rules and one that doesn't"); + inheritingChildFolder = dataContent.usingUser(user).usingResource(ruleFolder).createFolder(); + notInheritingChildFolder = dataContent.usingUser(user).usingResource(ruleFolder).createFolder(); + RestRuleSettingsModel doesntInherit = new RestRuleSettingsModel(); + doesntInherit.setValue(false); + restClient.authenticateUser(user).withCoreAPI().usingNode(notInheritingChildFolder) + .usingIsInheritanceEnabledRuleSetting().updateSetting(doesntInherit); + STEP("Create a rule in the folder."); RestRuleModel ruleModel = createRuleModel("ruleName"); rule = restClient.authenticateUser(user).withCoreAPI().usingNode(ruleFolder).usingDefaultRuleSet() @@ -133,7 +144,7 @@ public class GetRuleSetsTests extends RestTest /** Check we can get the reason that a rule set is included in the list. */ @Test (groups = { TestGroup.REST_API, TestGroup.RULES }) - public void getRuleSetsAndInclusionType() + public void getRuleSetsAndOwnedInclusionType() { STEP("Get the rule sets and inclusion type"); RestRuleSetModelsCollection ruleSets = restClient.authenticateUser(user).withCoreAPI() @@ -148,6 +159,36 @@ public class GetRuleSetsTests extends RestTest ruleSets.assertThat().entriesListCountIs(1); } + /** Check we can tell that a rule set has been inherited. */ + @Test (groups = { TestGroup.REST_API, TestGroup.RULES }) + public void getRuleSetsAndInheritedInclusionType() + { + STEP("Get the rule sets and inclusion type"); + RestRuleSetModelsCollection ruleSets = restClient.authenticateUser(user).withCoreAPI() + .usingNode(inheritingChildFolder) + .include("inclusionType") + .getListOfRuleSets(); + + restClient.assertStatusCodeIs(OK); + ruleSets.getEntries().get(0).onModel() + .assertThat().field("inclusionType").is("inherited") + .assertThat().field("id").is(ruleSetId); + ruleSets.assertThat().entriesListCountIs(1); + } + + /** Check that a rule set is not inherited if inheriting is disabled. */ + @Test (groups = { TestGroup.REST_API, TestGroup.RULES }) + public void getRuleSetsWithoutInheriting() + { + STEP("Get the rule sets and inclusion type"); + RestRuleSetModelsCollection ruleSets = restClient.authenticateUser(user).withCoreAPI() + .usingNode(notInheritingChildFolder) + .getListOfRuleSets(); + + restClient.assertStatusCodeIs(OK); + ruleSets.assertThat().entriesListCountIs(0); + } + /** Check we can get a rule set by its id. */ @Test (groups = { TestGroup.REST_API, TestGroup.RULES, TestGroup.SANITY }) public void getRuleSetById() diff --git a/remote-api/src/main/java/org/alfresco/rest/api/impl/rules/RuleSetsImpl.java b/remote-api/src/main/java/org/alfresco/rest/api/impl/rules/RuleSetsImpl.java index 4492dd7ed4..55ccf5a1ea 100644 --- a/remote-api/src/main/java/org/alfresco/rest/api/impl/rules/RuleSetsImpl.java +++ b/remote-api/src/main/java/org/alfresco/rest/api/impl/rules/RuleSetsImpl.java @@ -29,7 +29,7 @@ package org.alfresco.rest.api.impl.rules; import static java.util.stream.Collectors.toList; import java.util.List; -import java.util.Optional; +import java.util.Objects; import org.alfresco.repo.rule.RuleModel; import org.alfresco.repo.rule.RuntimeRuleService; @@ -59,10 +59,13 @@ public class RuleSetsImpl implements RuleSets { NodeRef folderNode = validator.validateFolderNode(folderNodeId, false); - NodeRef ruleSetNode = ruleService.getRuleSetNode(folderNode); - List ruleSets = Optional.ofNullable(ruleSetNode) - .map(nodeRef -> ruleSetLoader.loadRuleSet(nodeRef, folderNode, includes)) - .stream().collect(toList()); + List ruleSets = ruleService.getNodesSupplyingRuleSets(folderNode) + .stream() + .map(ruleService::getRuleSetNode) + .filter(Objects::nonNull) + .map(nodeRef -> ruleSetLoader.loadRuleSet(nodeRef, folderNode, includes)) + .distinct() + .collect(toList()); return ListPage.of(ruleSets, paging); } diff --git a/remote-api/src/test/java/org/alfresco/rest/api/impl/rules/RuleSetsImplTest.java b/remote-api/src/test/java/org/alfresco/rest/api/impl/rules/RuleSetsImplTest.java index d52f5f2d52..026c1d2ef5 100644 --- a/remote-api/src/test/java/org/alfresco/rest/api/impl/rules/RuleSetsImplTest.java +++ b/remote-api/src/test/java/org/alfresco/rest/api/impl/rules/RuleSetsImplTest.java @@ -33,6 +33,7 @@ import static org.mockito.ArgumentMatchers.anyBoolean; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.BDDMockito.given; import static org.mockito.BDDMockito.then; +import static org.mockito.Mockito.mock; import java.util.Collection; import java.util.List; @@ -101,6 +102,8 @@ public class RuleSetsImplTest extends TestCase given(nodeValidatorMock.validateRuleSetNode(RULE_SET_ID, FOLDER_NODE)).willReturn(RULE_SET_NODE); given(ruleServiceMock.getRuleSetNode(FOLDER_NODE)).willReturn(RULE_SET_NODE); + given(ruleServiceMock.getNodesSupplyingRuleSets(FOLDER_NODE)).willReturn(List.of(FOLDER_NODE)); + given(ruleSetLoaderMock.loadRuleSet(RULE_SET_NODE, FOLDER_NODE, INCLUDES)).willReturn(ruleSetMock); } @@ -113,6 +116,7 @@ public class RuleSetsImplTest extends TestCase then(nodeValidatorMock).should().validateFolderNode(FOLDER_ID, false); then(nodeValidatorMock).shouldHaveNoMoreInteractions(); + then(ruleServiceMock).should().getNodesSupplyingRuleSets(FOLDER_NODE); then(ruleServiceMock).should().getRuleSetNode(FOLDER_NODE); then(ruleServiceMock).shouldHaveNoMoreInteractions(); @@ -133,6 +137,7 @@ public class RuleSetsImplTest extends TestCase then(nodeValidatorMock).should().validateFolderNode(FOLDER_ID, false); then(nodeValidatorMock).shouldHaveNoMoreInteractions(); + then(ruleServiceMock).should().getNodesSupplyingRuleSets(FOLDER_NODE); then(ruleServiceMock).should().getRuleSetNode(FOLDER_NODE); then(ruleServiceMock).shouldHaveNoMoreInteractions(); @@ -140,6 +145,62 @@ public class RuleSetsImplTest extends TestCase assertEquals(PAGING, actual.getPaging()); } + /** Check that a folder with a parent and grandparent can inherit rule sets from the grandparent, even if the parent has no rules. */ + @Test + public void testGetInheritedRuleSets() + { + // Simulate a parent node without a rule set. + NodeRef parentNode = new NodeRef("parent://node/"); + // Simulate a grandparent node providing a rule set. + NodeRef grandparentNode = new NodeRef("grandparent://node/"); + RuleSet grandparentRuleSet = mock(RuleSet.class); + NodeRef grandparentRuleSetNode = new NodeRef("grandparent://rule-set/"); + given(ruleServiceMock.getRuleSetNode(grandparentNode)).willReturn(grandparentRuleSetNode); + given(ruleSetLoaderMock.loadRuleSet(grandparentRuleSetNode, FOLDER_NODE, INCLUDES)).willReturn(grandparentRuleSet); + // These should be returned with the highest in hierarchy first. + given(ruleServiceMock.getNodesSupplyingRuleSets(FOLDER_NODE)).willReturn(List.of(grandparentNode, parentNode, FOLDER_NODE)); + + // Call the method under test. + CollectionWithPagingInfo actual = ruleSets.getRuleSets(FOLDER_ID, INCLUDES, PAGING); + + then(nodeValidatorMock).should().validateFolderNode(FOLDER_ID, false); + then(nodeValidatorMock).shouldHaveNoMoreInteractions(); + + then(ruleServiceMock).should().getNodesSupplyingRuleSets(FOLDER_NODE); + then(ruleServiceMock).should().getRuleSetNode(grandparentNode); + then(ruleServiceMock).should().getRuleSetNode(parentNode); + then(ruleServiceMock).should().getRuleSetNode(FOLDER_NODE); + then(ruleServiceMock).shouldHaveNoMoreInteractions(); + + Collection expected = List.of(grandparentRuleSet, ruleSetMock); + assertEquals(expected, actual.getCollection()); + assertEquals(PAGING, actual.getPaging()); + } + + /** When getting rule sets then only the first instance of each rule set should be included (ancestor first). */ + @Test + public void testGetDuplicateRuleSets() + { + // Simulate a grandparent, parent and child with the grandparent linking to the child's rule set. + NodeRef grandparentNode = new NodeRef("grandparent://node/"); + given(ruleServiceMock.getRuleSetNode(grandparentNode)).willReturn(RULE_SET_NODE); + NodeRef parentNode = new NodeRef("parent://node/"); + RuleSet parentRuleSet = mock(RuleSet.class); + NodeRef parentRuleSetNode = new NodeRef("parent://rule-set/"); + given(ruleServiceMock.getRuleSetNode(parentNode)).willReturn(parentRuleSetNode); + given(ruleSetLoaderMock.loadRuleSet(parentRuleSetNode, FOLDER_NODE, INCLUDES)).willReturn(parentRuleSet); + // These should be returned with the highest in hierarchy first. + given(ruleServiceMock.getNodesSupplyingRuleSets(FOLDER_NODE)).willReturn(List.of(grandparentNode, parentNode, FOLDER_NODE)); + + // Call the method under test. + CollectionWithPagingInfo actual = ruleSets.getRuleSets(FOLDER_ID, INCLUDES, PAGING); + + // The grandparent's linked rule set should be first and only appear once. + Collection expected = List.of(ruleSetMock, parentRuleSet); + assertEquals(expected, actual.getCollection()); + assertEquals(PAGING, actual.getPaging()); + } + @Test public void testGetRuleSetById() { 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 5b7e6ce461..646ef4352a 100644 --- a/repository/src/main/java/org/alfresco/repo/rule/RuleServiceImpl.java +++ b/repository/src/main/java/org/alfresco/repo/rule/RuleServiceImpl.java @@ -25,6 +25,8 @@ */ package org.alfresco.repo.rule; +import static org.alfresco.repo.rule.RuleModel.ASPECT_IGNORE_INHERITED_RULES; + import java.io.Serializable; import java.util.ArrayList; import java.util.Collections; @@ -479,7 +481,7 @@ public class RuleServiceImpl // Node has gone or is not the correct type return rules; } - if (includeInherited == true && runtimeNodeService.hasAspect(nodeRef, RuleModel.ASPECT_IGNORE_INHERITED_RULES) == false) + if (includeInherited && !runtimeNodeService.hasAspect(nodeRef, ASPECT_IGNORE_INHERITED_RULES)) { // Get any inherited rules for (Rule rule : getInheritedRules(nodeRef, ruleTypeName, null)) @@ -597,10 +599,53 @@ public class RuleServiceImpl return result; } - + + /** {@inheritDoc} */ + @Override + @Experimental + public List getNodesSupplyingRuleSets(NodeRef nodeRef) + { + return getNodesSupplyingRuleSets(nodeRef, new ArrayList<>()); + } + + /** + * Traverse the folder hierarchy find all the folder nodes that could supply rules by inheritance. + *

+ * The order of nodes returned by this methods has to match the order used by {@link #getInheritedRules}. + * + * @param nodeRef The starting node ref. + * @param visitedNodeRefs All the visited node refs (will be modified). + * @return A list of node refs, starting with the first parent of the first parent of ... and ending with the object generated by the + * given node ref. + */ + private List getNodesSupplyingRuleSets(NodeRef nodeRef, List visitedNodeRefs) + { + List returnList = new ArrayList<>(); + // This check prevents stack over flow when we have a cyclic node graph + if (!visitedNodeRefs.contains(nodeRef)) + { + visitedNodeRefs.add(nodeRef); + if (!runtimeNodeService.hasAspect(nodeRef, ASPECT_IGNORE_INHERITED_RULES)) + { + List parents = runtimeNodeService.getParentAssocs(nodeRef); + for (ChildAssociationRef parent : parents) + { + // We are not interested in following potentially massive person group membership trees! + if (!IGNORE_PARENT_ASSOC_TYPES.contains(parent.getTypeQName())) + { + // Update visitedNodeRefs with all the ancestors. + returnList.addAll(getNodesSupplyingRuleSets(parent.getParentRef(), visitedNodeRefs)); + } + } + } + returnList.add(nodeRef); + } + return returnList; + } + /** * Gets the inherited rules for a given node reference - * + * * @param nodeRef the nodeRef * @param ruleTypeName the rule type (null if all applicable) * @return a list of inherited rules (empty if none) @@ -608,20 +653,20 @@ public class RuleServiceImpl private List getInheritedRules(NodeRef nodeRef, String ruleTypeName, Set visitedNodeRefs) { List inheritedRules = new ArrayList(); - + if (this.runtimeNodeService.hasAspect(nodeRef, RuleModel.ASPECT_IGNORE_INHERITED_RULES) == false) - { + { // Create the visited nodes set if it has not already been created if (visitedNodeRefs == null) { visitedNodeRefs = new HashSet(); } - + // This check prevents stack over flow when we have a cyclic node graph if (visitedNodeRefs.contains(nodeRef) == false) { visitedNodeRefs.add(nodeRef); - + List allInheritedRules = new ArrayList(); List parents = this.runtimeNodeService.getParentAssocs(nodeRef); for (ChildAssociationRef parent : parents) @@ -641,7 +686,7 @@ public class RuleServiceImpl allInheritedRules.add(rule); } } - + List rules = getRules(parent.getParentRef(), false); for (Rule rule : rules) { @@ -652,7 +697,7 @@ public class RuleServiceImpl } } } - + if (ruleTypeName == null) { inheritedRules = allInheritedRules; @@ -670,7 +715,7 @@ public class RuleServiceImpl } } } - + return inheritedRules; } 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 c7b3cc6a6c..6434dfc07b 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 @@ -215,7 +215,18 @@ public interface RuleService */ @Auditable(parameters = {"nodeRef"}) public int countRules(NodeRef nodeRef); - + + /** + * Traverse the folder hierarchy find all the folder nodes that could supply rules by inheritance. + * + * @param nodeRef The starting node ref. + * @return A list of node refs, starting with the first parent of the first parent of ... and ending with the object generated by the + * given node ref. + */ + @Auditable (parameters = { "nodeRef" }) + @Experimental + List getNodesSupplyingRuleSets(NodeRef nodeRef); + /** * Get the rule given its node reference * 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 4403ac8e01..868f355506 100644 --- a/repository/src/test/java/org/alfresco/repo/rule/RuleServiceImplUnitTest.java +++ b/repository/src/test/java/org/alfresco/repo/rule/RuleServiceImplUnitTest.java @@ -25,7 +25,12 @@ */ package org.alfresco.repo.rule; +import static java.util.stream.Collectors.joining; +import static java.util.stream.Collectors.toList; + import static org.alfresco.model.ContentModel.ASSOC_CONTAINS; +import static org.alfresco.model.ContentModel.ASSOC_MEMBER; +import static org.alfresco.repo.rule.RuleModel.ASPECT_IGNORE_INHERITED_RULES; import static org.alfresco.repo.rule.RuleModel.ASSOC_ACTION; import static org.alfresco.repo.rule.RuleModel.ASSOC_RULE_FOLDER; import static org.alfresco.repo.rule.RuleModel.TYPE_RULE; @@ -33,6 +38,7 @@ import static org.alfresco.service.cmr.security.AccessStatus.ALLOWED; import static org.alfresco.service.cmr.security.AccessStatus.DENIED; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatExceptionOfType; +import static org.junit.Assert.assertEquals; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.ArgumentMatchers.nullable; @@ -47,8 +53,13 @@ import static org.mockito.MockitoAnnotations.openMocks; import java.io.Serializable; import java.util.Collections; +import java.util.HashSet; import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.stream.Collectors; +import org.alfresco.model.ContentModel; import org.alfresco.repo.action.RuntimeActionService; import org.alfresco.repo.cache.SimpleCache; import org.alfresco.service.cmr.action.Action; @@ -61,6 +72,7 @@ import org.alfresco.service.cmr.rule.RuleService; import org.alfresco.service.cmr.rule.RuleServiceException; import org.alfresco.service.cmr.security.PermissionService; import org.alfresco.service.namespace.QName; +import org.apache.commons.collections.MapUtils; import org.junit.Before; import org.junit.Test; import org.mockito.InjectMocks; @@ -356,4 +368,174 @@ public class RuleServiceImplUnitTest { return new ChildAssociationRef(null, parentRef, null, childRef, isPrimary, 1); } + + /** Check that a straight chain of nodes is traversed correctly. */ + @Test + public void testGetNodesSupplyingRuleSets_chain() + { + Map nodes = createParentChildHierarchy("A,B", "B,C", "C,D", "D,E"); + + List actual = ruleService.getNodesSupplyingRuleSets(nodes.get("E")); + + Map invertedMap = MapUtils.invertMap(nodes); + String nodeNames = actual.stream().map(invertedMap::get).collect(joining(",")); + + assertEquals("A,B,C,D,E", nodeNames); + } + + /** Check that ordered parents are returned in the correct order. */ + @Test + public void testGetNodesSupplyingRuleSets_multipleParents() + { + Map nodes = createParentChildHierarchy("A,E", "B,E", "C,E", "D,E"); + + List actual = ruleService.getNodesSupplyingRuleSets(nodes.get("E")); + + Map invertedMap = MapUtils.invertMap(nodes); + String nodeNames = actual.stream().map(invertedMap::get).collect(joining(",")); + + assertEquals("A,B,C,D,E", nodeNames); + } + + /** Check that the ASPECT_IGNORE_INHERITED_RULES aspect breaks the chain. */ + @Test + public void testGetNodesSupplyingRuleSets_brokenChain() + { + Map nodes = createParentChildHierarchy("A,B", "B,C", "C,D", "D,E"); + given(runtimeNodeService.hasAspect(nodes.get("C"), ASPECT_IGNORE_INHERITED_RULES)).willReturn(true); + + List actual = ruleService.getNodesSupplyingRuleSets(nodes.get("E")); + + Map invertedMap = MapUtils.invertMap(nodes); + String nodeNames = actual.stream().map(invertedMap::get).collect(joining(",")); + + assertEquals("C,D,E", nodeNames); + } + + /** Check that the user group hierarchy is not traversed. */ + @Test + public void testGetNodesSupplyingRuleSets_userGroupHierarchy() + { + Map nodes = createParentChildHierarchy("A,B", "B,C", "C,D", "D,E"); + // Replace the B,C association with a user group membership association. + ChildAssociationRef memberAssoc = new ChildAssociationRef(ASSOC_MEMBER, nodes.get("B"), ContentModel.TYPE_FOLDER, nodes.get("C")); + given(runtimeNodeService.getParentAssocs(nodes.get("C"))).willReturn(List.of(memberAssoc)); + + List actual = ruleService.getNodesSupplyingRuleSets(nodes.get("E")); + + Map invertedMap = MapUtils.invertMap(nodes); + String nodeNames = actual.stream().map(invertedMap::get).collect(joining(",")); + + assertEquals("C,D,E", nodeNames); + } + + /** Check that a cycle doesn't cause a problem. */ + @Test + public void testGetNodesSupplyingRuleSets_infiniteCycle() + { + Map nodes = createParentChildHierarchy("A,B", "B,C", "C,A"); + + List actual = ruleService.getNodesSupplyingRuleSets(nodes.get("C")); + + Map invertedMap = MapUtils.invertMap(nodes); + String nodeNames = actual.stream().map(invertedMap::get).collect(joining(",")); + + assertEquals("A,B,C", nodeNames); + } + + /** Check that a diamond of nodes is traversed correctly. */ + @Test + public void testGetNodesSupplyingRuleSets_diamond() + { + Map nodes = createParentChildHierarchy("A,B", "A,C", "B,D", "C,D"); + + List actual = ruleService.getNodesSupplyingRuleSets(nodes.get("D")); + + Map invertedMap = MapUtils.invertMap(nodes); + String nodeNames = actual.stream().map(invertedMap::get).collect(joining(",")); + + assertEquals("A,B,C,D", nodeNames); + } + + /** + * Check that hierarchy of nodes is traversed correctly. Parent-child associations are created in alphabetical order. + *

+     *     A
+     *    /|\
+     *   B C D
+     *   | |\|
+     *   E | F
+     *    \|/
+     *     G
+     * 
+ */ + @Test + public void testGetNodesSupplyingRuleSets_alphabetical() + { + Map nodes = createParentChildHierarchy("A,B", "A,C", "A,D", "B,E", "C,F", "C,G", "D,F", "E,G", "F,G"); + + List actual = ruleService.getNodesSupplyingRuleSets(nodes.get("G")); + + Map invertedMap = MapUtils.invertMap(nodes); + String nodeNames = actual.stream().map(invertedMap::get).collect(joining(",")); + + assertEquals("A,C,B,E,D,F,G", nodeNames); + } + + /** + * Check that hierarchy of nodes is traversed correctly. Parent-child associations are created in reverse alphabetical order. + *
+     *     A
+     *    /|\
+     *   B C D
+     *   | |\|
+     *   E | F
+     *    \|/
+     *     G
+     * 
+ */ + @Test + public void testGetNodesSupplyingRuleSets_reversedAssociationOrder() + { + Map nodes = createParentChildHierarchy("F,G", "E,G", "D,F", "C,G", "C,F", "B,E", "A,D", "A,C", "A,B"); + + List actual = ruleService.getNodesSupplyingRuleSets(nodes.get("G")); + + Map invertedMap = MapUtils.invertMap(nodes); + String nodeNames = actual.stream().map(invertedMap::get).collect(joining(",")); + + assertEquals("A,D,C,F,B,E,G", nodeNames); + } + + /** + * Create a mock hierarchy of nodes using the supplied parent child associations. + * + * @param parentChildAssociations A list of strings of the form "Parent,Child". Associations will be created in this order. + * @return A map from the node name to the new NodeRef object. + */ + private Map createParentChildHierarchy(String... parentChildAssociations) + { + // Find all the node names mentioned. + Set nodeNames = new HashSet<>(); + List.of(parentChildAssociations).forEach(parentChildAssociation -> { + String[] parentChildPair = parentChildAssociation.split(","); + nodeNames.addAll(List.of(parentChildPair)); + }); + // Create the NodeRefs. + Map nodeRefMap = nodeNames.stream().collect( + Collectors.toMap(nodeName -> nodeName, nodeName -> new NodeRef("node://" + nodeName + "/"))); + // Mock the associations. + nodeNames.forEach(nodeName -> { + NodeRef nodeRef = nodeRefMap.get(nodeName); + List parentAssocs = List.of(parentChildAssociations) + .stream() + .filter(assoc -> assoc.endsWith(nodeName)) + .map(assoc -> assoc.split(",")[0]) + .map(nodeRefMap::get) + .map(parentRef -> new ChildAssociationRef(ASSOC_CONTAINS, parentRef, ContentModel.TYPE_FOLDER, nodeRef)) + .collect(toList()); + given(runtimeNodeService.getParentAssocs(nodeRef)).willReturn(parentAssocs); + }); + return nodeRefMap; + } }