ACS-3280 Get inherited rule sets. [tas] (#1323)

* ACS-3280 Get inherited rule sets. [tas]

This needs to work the exact same way as get inherited rules.

* ACS-3280 Replace LinkedList with ArrayList.

* ACS-3280 Don't return duplicated rule sets when there are links.
This commit is contained in:
Tom Page
2022-09-12 11:44:42 +01:00
committed by GitHub
parent 5cf7c1934a
commit 80ccf64df8
6 changed files with 360 additions and 17 deletions

View File

@@ -35,6 +35,7 @@ import org.alfresco.rest.RestTest;
import org.alfresco.rest.model.RestRuleModel; import org.alfresco.rest.model.RestRuleModel;
import org.alfresco.rest.model.RestRuleSetModel; import org.alfresco.rest.model.RestRuleSetModel;
import org.alfresco.rest.model.RestRuleSetModelsCollection; import org.alfresco.rest.model.RestRuleSetModelsCollection;
import org.alfresco.rest.model.RestRuleSettingsModel;
import org.alfresco.utility.model.FolderModel; import org.alfresco.utility.model.FolderModel;
import org.alfresco.utility.model.SiteModel; import org.alfresco.utility.model.SiteModel;
import org.alfresco.utility.model.TestGroup; import org.alfresco.utility.model.TestGroup;
@@ -51,6 +52,8 @@ public class GetRuleSetsTests extends RestTest
private UserModel user; private UserModel user;
private SiteModel site; private SiteModel site;
private FolderModel ruleFolder; private FolderModel ruleFolder;
private FolderModel inheritingChildFolder;
private FolderModel notInheritingChildFolder;
private RestRuleModel rule; private RestRuleModel rule;
private String ruleSetId; private String ruleSetId;
@@ -62,6 +65,14 @@ public class GetRuleSetsTests extends RestTest
site = dataSite.usingUser(user).createPublicRandomSite(); site = dataSite.usingUser(user).createPublicRandomSite();
ruleFolder = dataContent.usingUser(user).usingSite(site).createFolder(); 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."); STEP("Create a rule in the folder.");
RestRuleModel ruleModel = createRuleModel("ruleName"); RestRuleModel ruleModel = createRuleModel("ruleName");
rule = restClient.authenticateUser(user).withCoreAPI().usingNode(ruleFolder).usingDefaultRuleSet() 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. */ /** Check we can get the reason that a rule set is included in the list. */
@Test (groups = { TestGroup.REST_API, TestGroup.RULES }) @Test (groups = { TestGroup.REST_API, TestGroup.RULES })
public void getRuleSetsAndInclusionType() public void getRuleSetsAndOwnedInclusionType()
{ {
STEP("Get the rule sets and inclusion type"); STEP("Get the rule sets and inclusion type");
RestRuleSetModelsCollection ruleSets = restClient.authenticateUser(user).withCoreAPI() RestRuleSetModelsCollection ruleSets = restClient.authenticateUser(user).withCoreAPI()
@@ -148,6 +159,36 @@ public class GetRuleSetsTests extends RestTest
ruleSets.assertThat().entriesListCountIs(1); 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. */ /** Check we can get a rule set by its id. */
@Test (groups = { TestGroup.REST_API, TestGroup.RULES, TestGroup.SANITY }) @Test (groups = { TestGroup.REST_API, TestGroup.RULES, TestGroup.SANITY })
public void getRuleSetById() public void getRuleSetById()

View File

@@ -29,7 +29,7 @@ package org.alfresco.rest.api.impl.rules;
import static java.util.stream.Collectors.toList; import static java.util.stream.Collectors.toList;
import java.util.List; import java.util.List;
import java.util.Optional; import java.util.Objects;
import org.alfresco.repo.rule.RuleModel; import org.alfresco.repo.rule.RuleModel;
import org.alfresco.repo.rule.RuntimeRuleService; import org.alfresco.repo.rule.RuntimeRuleService;
@@ -59,10 +59,13 @@ public class RuleSetsImpl implements RuleSets
{ {
NodeRef folderNode = validator.validateFolderNode(folderNodeId, false); NodeRef folderNode = validator.validateFolderNode(folderNodeId, false);
NodeRef ruleSetNode = ruleService.getRuleSetNode(folderNode); List<RuleSet> ruleSets = ruleService.getNodesSupplyingRuleSets(folderNode)
List<RuleSet> ruleSets = Optional.ofNullable(ruleSetNode) .stream()
.map(ruleService::getRuleSetNode)
.filter(Objects::nonNull)
.map(nodeRef -> ruleSetLoader.loadRuleSet(nodeRef, folderNode, includes)) .map(nodeRef -> ruleSetLoader.loadRuleSet(nodeRef, folderNode, includes))
.stream().collect(toList()); .distinct()
.collect(toList());
return ListPage.of(ruleSets, paging); return ListPage.of(ruleSets, paging);
} }

View File

@@ -33,6 +33,7 @@ import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.eq; import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.BDDMockito.given; import static org.mockito.BDDMockito.given;
import static org.mockito.BDDMockito.then; import static org.mockito.BDDMockito.then;
import static org.mockito.Mockito.mock;
import java.util.Collection; import java.util.Collection;
import java.util.List; 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(nodeValidatorMock.validateRuleSetNode(RULE_SET_ID, FOLDER_NODE)).willReturn(RULE_SET_NODE);
given(ruleServiceMock.getRuleSetNode(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); 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).should().validateFolderNode(FOLDER_ID, false);
then(nodeValidatorMock).shouldHaveNoMoreInteractions(); then(nodeValidatorMock).shouldHaveNoMoreInteractions();
then(ruleServiceMock).should().getNodesSupplyingRuleSets(FOLDER_NODE);
then(ruleServiceMock).should().getRuleSetNode(FOLDER_NODE); then(ruleServiceMock).should().getRuleSetNode(FOLDER_NODE);
then(ruleServiceMock).shouldHaveNoMoreInteractions(); then(ruleServiceMock).shouldHaveNoMoreInteractions();
@@ -133,6 +137,7 @@ public class RuleSetsImplTest extends TestCase
then(nodeValidatorMock).should().validateFolderNode(FOLDER_ID, false); then(nodeValidatorMock).should().validateFolderNode(FOLDER_ID, false);
then(nodeValidatorMock).shouldHaveNoMoreInteractions(); then(nodeValidatorMock).shouldHaveNoMoreInteractions();
then(ruleServiceMock).should().getNodesSupplyingRuleSets(FOLDER_NODE);
then(ruleServiceMock).should().getRuleSetNode(FOLDER_NODE); then(ruleServiceMock).should().getRuleSetNode(FOLDER_NODE);
then(ruleServiceMock).shouldHaveNoMoreInteractions(); then(ruleServiceMock).shouldHaveNoMoreInteractions();
@@ -140,6 +145,62 @@ public class RuleSetsImplTest extends TestCase
assertEquals(PAGING, actual.getPaging()); 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<RuleSet> 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<RuleSet> 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<RuleSet> actual = ruleSets.getRuleSets(FOLDER_ID, INCLUDES, PAGING);
// The grandparent's linked rule set should be first and only appear once.
Collection<RuleSet> expected = List.of(ruleSetMock, parentRuleSet);
assertEquals(expected, actual.getCollection());
assertEquals(PAGING, actual.getPaging());
}
@Test @Test
public void testGetRuleSetById() public void testGetRuleSetById()
{ {

View File

@@ -25,6 +25,8 @@
*/ */
package org.alfresco.repo.rule; package org.alfresco.repo.rule;
import static org.alfresco.repo.rule.RuleModel.ASPECT_IGNORE_INHERITED_RULES;
import java.io.Serializable; import java.io.Serializable;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
@@ -479,7 +481,7 @@ public class RuleServiceImpl
// Node has gone or is not the correct type // Node has gone or is not the correct type
return rules; 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 // Get any inherited rules
for (Rule rule : getInheritedRules(nodeRef, ruleTypeName, null)) for (Rule rule : getInheritedRules(nodeRef, ruleTypeName, null))
@@ -598,6 +600,49 @@ public class RuleServiceImpl
return result; return result;
} }
/** {@inheritDoc} */
@Override
@Experimental
public List<NodeRef> getNodesSupplyingRuleSets(NodeRef nodeRef)
{
return getNodesSupplyingRuleSets(nodeRef, new ArrayList<>());
}
/**
* Traverse the folder hierarchy find all the folder nodes that could supply rules by inheritance.
* <p>
* 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<NodeRef> getNodesSupplyingRuleSets(NodeRef nodeRef, List<NodeRef> visitedNodeRefs)
{
List<NodeRef> 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<ChildAssociationRef> 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 * Gets the inherited rules for a given node reference
* *

View File

@@ -216,6 +216,17 @@ public interface RuleService
@Auditable(parameters = {"nodeRef"}) @Auditable(parameters = {"nodeRef"})
public int countRules(NodeRef 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<NodeRef> getNodesSupplyingRuleSets(NodeRef nodeRef);
/** /**
* Get the rule given its node reference * Get the rule given its node reference
* *

View File

@@ -25,7 +25,12 @@
*/ */
package org.alfresco.repo.rule; 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_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_ACTION;
import static org.alfresco.repo.rule.RuleModel.ASSOC_RULE_FOLDER; import static org.alfresco.repo.rule.RuleModel.ASSOC_RULE_FOLDER;
import static org.alfresco.repo.rule.RuleModel.TYPE_RULE; 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.alfresco.service.cmr.security.AccessStatus.DENIED;
import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType; 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.any;
import static org.mockito.ArgumentMatchers.eq; import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.ArgumentMatchers.nullable; import static org.mockito.ArgumentMatchers.nullable;
@@ -47,8 +53,13 @@ import static org.mockito.MockitoAnnotations.openMocks;
import java.io.Serializable; import java.io.Serializable;
import java.util.Collections; import java.util.Collections;
import java.util.HashSet;
import java.util.List; 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.action.RuntimeActionService;
import org.alfresco.repo.cache.SimpleCache; import org.alfresco.repo.cache.SimpleCache;
import org.alfresco.service.cmr.action.Action; 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.rule.RuleServiceException;
import org.alfresco.service.cmr.security.PermissionService; import org.alfresco.service.cmr.security.PermissionService;
import org.alfresco.service.namespace.QName; import org.alfresco.service.namespace.QName;
import org.apache.commons.collections.MapUtils;
import org.junit.Before; import org.junit.Before;
import org.junit.Test; import org.junit.Test;
import org.mockito.InjectMocks; import org.mockito.InjectMocks;
@@ -356,4 +368,174 @@ public class RuleServiceImplUnitTest
{ {
return new ChildAssociationRef(null, parentRef, null, childRef, isPrimary, 1); 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<String, NodeRef> nodes = createParentChildHierarchy("A,B", "B,C", "C,D", "D,E");
List<NodeRef> actual = ruleService.getNodesSupplyingRuleSets(nodes.get("E"));
Map<NodeRef, String> 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<String, NodeRef> nodes = createParentChildHierarchy("A,E", "B,E", "C,E", "D,E");
List<NodeRef> actual = ruleService.getNodesSupplyingRuleSets(nodes.get("E"));
Map<NodeRef, String> 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<String, NodeRef> nodes = createParentChildHierarchy("A,B", "B,C", "C,D", "D,E");
given(runtimeNodeService.hasAspect(nodes.get("C"), ASPECT_IGNORE_INHERITED_RULES)).willReturn(true);
List<NodeRef> actual = ruleService.getNodesSupplyingRuleSets(nodes.get("E"));
Map<NodeRef, String> 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<String, NodeRef> 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<NodeRef> actual = ruleService.getNodesSupplyingRuleSets(nodes.get("E"));
Map<NodeRef, String> 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<String, NodeRef> nodes = createParentChildHierarchy("A,B", "B,C", "C,A");
List<NodeRef> actual = ruleService.getNodesSupplyingRuleSets(nodes.get("C"));
Map<NodeRef, String> 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<String, NodeRef> nodes = createParentChildHierarchy("A,B", "A,C", "B,D", "C,D");
List<NodeRef> actual = ruleService.getNodesSupplyingRuleSets(nodes.get("D"));
Map<NodeRef, String> 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.
* <pre>
* A
* /|\
* B C D
* | |\|
* E | F
* \|/
* G
* </pre>
*/
@Test
public void testGetNodesSupplyingRuleSets_alphabetical()
{
Map<String, NodeRef> nodes = createParentChildHierarchy("A,B", "A,C", "A,D", "B,E", "C,F", "C,G", "D,F", "E,G", "F,G");
List<NodeRef> actual = ruleService.getNodesSupplyingRuleSets(nodes.get("G"));
Map<NodeRef, String> 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.
* <pre>
* A
* /|\
* B C D
* | |\|
* E | F
* \|/
* G
* </pre>
*/
@Test
public void testGetNodesSupplyingRuleSets_reversedAssociationOrder()
{
Map<String, NodeRef> nodes = createParentChildHierarchy("F,G", "E,G", "D,F", "C,G", "C,F", "B,E", "A,D", "A,C", "A,B");
List<NodeRef> actual = ruleService.getNodesSupplyingRuleSets(nodes.get("G"));
Map<NodeRef, String> 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<String, NodeRef> createParentChildHierarchy(String... parentChildAssociations)
{
// Find all the node names mentioned.
Set<String> nodeNames = new HashSet<>();
List.of(parentChildAssociations).forEach(parentChildAssociation -> {
String[] parentChildPair = parentChildAssociation.split(",");
nodeNames.addAll(List.of(parentChildPair));
});
// Create the NodeRefs.
Map<String, NodeRef> 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<ChildAssociationRef> 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;
}
} }