ACS-3229: Rules v1 REST API - Get rule definition (#1205)

ACS-3229: Rules v1 REST API - Get rule definition
This commit is contained in:
krdabrowski
2022-07-13 15:59:29 +02:00
committed by GitHub
parent 75b9388a12
commit 0b7909be40
12 changed files with 505 additions and 123 deletions

View File

@@ -26,7 +26,7 @@
package org.alfresco.rest.api; package org.alfresco.rest.api;
import org.alfresco.rest.api.model.Rule; import org.alfresco.rest.api.model.rules.Rule;
import org.alfresco.rest.framework.resource.parameters.CollectionWithPagingInfo; import org.alfresco.rest.framework.resource.parameters.CollectionWithPagingInfo;
import org.alfresco.rest.framework.resource.parameters.Paging; import org.alfresco.rest.framework.resource.parameters.Paging;
import org.alfresco.service.Experimental; import org.alfresco.service.Experimental;
@@ -41,10 +41,20 @@ public interface Rules
/** /**
* Get rules for node's and rule set's IDs * Get rules for node's and rule set's IDs
* *
* @param folderNodeId node ID * @param folderNodeId - folder node ID
* @param ruleSetId rule set ID * @param ruleSetId - rule set ID
* @param paging {@link Paging} information * @param paging - {@link Paging} information
* @return {@link CollectionWithPagingInfo} containing a list page of folder rules * @return {@link CollectionWithPagingInfo} containing a list page of folder rules
*/ */
CollectionWithPagingInfo<Rule> getRules(String folderNodeId, String ruleSetId, Paging paging); CollectionWithPagingInfo<Rule> getRules(String folderNodeId, String ruleSetId, Paging paging);
/**
* Get rule for rule's ID and check associations with folder node and rule set node
*
* @param folderNodeId - folder node ID
* @param ruleSetId - rule set ID
* @param ruleId - rule ID
* @return {@link Rule} definition
*/
Rule getRuleById(String folderNodeId, String ruleSetId, String ruleId);
} }

View File

@@ -27,9 +27,11 @@
package org.alfresco.rest.api.impl; package org.alfresco.rest.api.impl;
import org.alfresco.model.ContentModel; import org.alfresco.model.ContentModel;
import org.alfresco.repo.rule.RuleModel;
import org.alfresco.rest.api.Nodes; import org.alfresco.rest.api.Nodes;
import org.alfresco.rest.api.Rules; import org.alfresco.rest.api.Rules;
import org.alfresco.rest.api.model.Rule; import org.alfresco.rest.api.model.rules.Rule;
import org.alfresco.rest.api.model.rules.RuleSet;
import org.alfresco.rest.framework.core.exceptions.InvalidArgumentException; import org.alfresco.rest.framework.core.exceptions.InvalidArgumentException;
import org.alfresco.rest.framework.core.exceptions.PermissionDeniedException; import org.alfresco.rest.framework.core.exceptions.PermissionDeniedException;
import org.alfresco.rest.framework.resource.parameters.CollectionWithPagingInfo; import org.alfresco.rest.framework.resource.parameters.CollectionWithPagingInfo;
@@ -49,8 +51,7 @@ import java.util.stream.Collectors;
@Experimental @Experimental
public class RulesImpl implements Rules public class RulesImpl implements Rules
{ {
private static final String RULE_SET_EXPECTED_TYPE_NAME = "rule set";
private static final String DEFAULT_RULE_SET_ID = "-default-";
private Nodes nodes; private Nodes nodes;
@@ -61,15 +62,8 @@ public class RulesImpl implements Rules
@Override @Override
public CollectionWithPagingInfo<Rule> getRules(final String folderNodeId, final String ruleSetId, final Paging paging) public CollectionWithPagingInfo<Rule> getRules(final String folderNodeId, final String ruleSetId, final Paging paging)
{ {
final NodeRef folderNodeRef = validateNode(folderNodeId, ContentModel.TYPE_FOLDER); final NodeRef folderNodeRef = validateFolderNode(folderNodeId);
validateRuleSetNode(ruleSetId, folderNodeRef);
if (isNotDefaultId(ruleSetId)) {
final NodeRef ruleSetNodeRef = validateNode(ruleSetId, ContentModel.TYPE_SYSTEM_FOLDER, "rule set");
if (!ruleService.isRuleSetAssociatedWithFolder(ruleSetNodeRef, folderNodeRef)) {
throw new InvalidArgumentException("Rule set is not associated with folder node!");
}
}
final List<Rule> rules = ruleService.getRules(folderNodeRef).stream() final List<Rule> rules = ruleService.getRules(folderNodeRef).stream()
.map(Rule::from) .map(Rule::from)
@@ -78,6 +72,16 @@ public class RulesImpl implements Rules
return ListPage.of(rules, paging); return ListPage.of(rules, paging);
} }
@Override
public Rule getRuleById(final String folderNodeId, final String ruleSetId, final String ruleId)
{
final NodeRef folderNodeRef = validateFolderNode(folderNodeId);
final NodeRef ruleSetNodeRef = validateRuleSetNode(ruleSetId, folderNodeRef);
final NodeRef ruleNodeRef = validateRuleNode(ruleId, ruleSetNodeRef);
return Rule.from(ruleService.getRule(ruleNodeRef));
}
public void setNodes(Nodes nodes) public void setNodes(Nodes nodes)
{ {
this.nodes = nodes; this.nodes = nodes;
@@ -93,37 +97,81 @@ public class RulesImpl implements Rules
this.ruleService = ruleService; this.ruleService = ruleService;
} }
private NodeRef validateNode(final String nodeId, final QName namespaceType)
{
return validateNode(nodeId, namespaceType, null);
}
/** /**
* Validates if node exists, user have permission to read from it and is of a given type. * Validates if folder node exists and user have permission to read from it.
* *
* @param nodeId - node ID * @param folderNodeId - folder node ID
* @param expectedType - expected type * @return folder node reference
* @param expectedTypeName - expected type local name * @throws InvalidArgumentException if node is not of an expected type
* @return node reference * @throws PermissionDeniedException if user doesn't have right to read from folder
*/ */
private NodeRef validateNode(final String nodeId, final QName expectedType, final String expectedTypeName) private NodeRef validateFolderNode(final String folderNodeId)
{ {
final NodeRef nodeRef = nodes.validateNode(nodeId); final NodeRef nodeRef = nodes.validateOrLookupNode(folderNodeId, null);
if (permissionService.hasReadPermission(nodeRef) != AccessStatus.ALLOWED) { if (permissionService.hasReadPermission(nodeRef) != AccessStatus.ALLOWED) {
throw new PermissionDeniedException("Cannot read from this node!"); throw new PermissionDeniedException("Cannot read from this node!");
} }
verifyNodeType(nodeRef, ContentModel.TYPE_FOLDER, null);
return nodeRef;
}
/**
* Validates if rule set ID is default, node exists and associated folder node matches.
*
* @param ruleSetId - rule set node ID
* @param associatedFolderNodeRef - folder node ref to check the association
* @return rule set node reference
* @throws InvalidArgumentException in case of not matching associated folder node
*/
private NodeRef validateRuleSetNode(final String ruleSetId, final NodeRef associatedFolderNodeRef)
{
if (RuleSet.isDefaultId(ruleSetId))
{
return ruleService.getRuleSetNode(associatedFolderNodeRef);
}
final NodeRef ruleSetNodeRef = validateNode(ruleSetId, ContentModel.TYPE_SYSTEM_FOLDER, RULE_SET_EXPECTED_TYPE_NAME);
if (!ruleService.isRuleSetAssociatedWithFolder(ruleSetNodeRef, associatedFolderNodeRef)) {
throw new InvalidArgumentException("Rule set is not associated with folder node!");
}
return ruleSetNodeRef;
}
/**
* Validates if rule node exists and associated rule set node matches.
*
* @param ruleId - rule node ID
* @param associatedRuleSetNodeRef - rule set node ref to check the association. Can be null
* @return rule node reference
* @throws InvalidArgumentException in case of not matching associated rule set node
*/
private NodeRef validateRuleNode(final String ruleId, final NodeRef associatedRuleSetNodeRef)
{
final NodeRef ruleNodeRef = validateNode(ruleId, RuleModel.TYPE_RULE, null);
if (associatedRuleSetNodeRef != null && !ruleService.isRuleAssociatedWithRuleSet(ruleNodeRef, associatedRuleSetNodeRef))
{
throw new InvalidArgumentException("Rule is not associated with rule set node!");
}
return ruleNodeRef;
}
private NodeRef validateNode(final String nodeId, final QName expectedType, final String expectedTypeName)
{
final NodeRef nodeRef = nodes.validateNode(nodeId);
verifyNodeType(nodeRef, expectedType, expectedTypeName);
return nodeRef;
}
private void verifyNodeType(final NodeRef nodeRef, final QName expectedType, final String expectedTypeName) {
final Set<QName> expectedTypes = Set.of(expectedType); final Set<QName> expectedTypes = Set.of(expectedType);
if (!nodes.nodeMatches(nodeRef, expectedTypes, null)) { if (!nodes.nodeMatches(nodeRef, expectedTypes, null)) {
final String expectedTypeLocalName = (expectedTypeName != null)? expectedTypeName : expectedType.getLocalName(); final String expectedTypeLocalName = (expectedTypeName != null)? expectedTypeName : expectedType.getLocalName();
throw new InvalidArgumentException(String.format("NodeId of a %s is expected!", expectedTypeLocalName)); throw new InvalidArgumentException(String.format("NodeId of a %s is expected!", expectedTypeLocalName));
} }
return nodeRef;
}
private static boolean isNotDefaultId(final String ruleSetId) {
return !DEFAULT_RULE_SET_ID.equals(ruleSetId);
} }
} }

View File

@@ -24,14 +24,19 @@
* #L% * #L%
*/ */
package org.alfresco.rest.api.model; package org.alfresco.rest.api.model.rules;
import org.alfresco.rest.framework.resource.UniqueId; import org.alfresco.rest.framework.resource.UniqueId;
import org.alfresco.service.Experimental; import org.alfresco.service.Experimental;
import org.alfresco.service.cmr.action.CompositeAction;
import java.util.List;
import java.util.stream.Collectors;
@Experimental @Experimental
public class Rule public class Rule
{ {
private String id; private String id;
private String name; private String name;
@@ -67,4 +72,10 @@ public class Rule
{ {
this.name = name; this.name = name;
} }
@Override
public String toString()
{
return "Rule{" + "id='" + id + '\'' + ", name='" + name + '\'' + '}';
}
} }

View File

@@ -0,0 +1,77 @@
/*
* #%L
* Alfresco Remote API
* %%
* Copyright (C) 2005 - 2022 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 <http://www.gnu.org/licenses/>.
* #L%
*/
package org.alfresco.rest.api.model.rules;
import org.alfresco.service.Experimental;
@Experimental
public class RuleSet
{
private static final String DEFAULT_ID = "-default-";
private String id;
public static RuleSet of(String id)
{
final RuleSet ruleSet = new RuleSet();
ruleSet.id = id;
return ruleSet;
}
public boolean isNotDefaultId() {
return isNotDefaultId(this.id);
}
public boolean isDefaultId() {
return isDefaultId(this.id);
}
public static boolean isNotDefaultId(final String id) {
return !isDefaultId(id);
}
public static boolean isDefaultId(final String id) {
return DEFAULT_ID.equals(id);
}
public String getId()
{
return id;
}
public void setId(String id)
{
this.id = id;
}
@Override
public String toString()
{
return "RuleSet{" + "id='" + id + '\'' + '}';
}
}

View File

@@ -27,8 +27,9 @@
package org.alfresco.rest.api.nodes; package org.alfresco.rest.api.nodes;
import org.alfresco.rest.api.Rules; import org.alfresco.rest.api.Rules;
import org.alfresco.rest.api.model.Rule; import org.alfresco.rest.api.model.rules.Rule;
import org.alfresco.rest.framework.WebApiDescription; import org.alfresco.rest.framework.WebApiDescription;
import org.alfresco.rest.framework.core.exceptions.RelationshipResourceNotFoundException;
import org.alfresco.rest.framework.resource.RelationshipResource; import org.alfresco.rest.framework.resource.RelationshipResource;
import org.alfresco.rest.framework.resource.actions.interfaces.RelationshipResourceAction; import org.alfresco.rest.framework.resource.actions.interfaces.RelationshipResourceAction;
import org.alfresco.rest.framework.resource.parameters.CollectionWithPagingInfo; import org.alfresco.rest.framework.resource.parameters.CollectionWithPagingInfo;
@@ -45,7 +46,7 @@ import javax.servlet.http.HttpServletResponse;
*/ */
@Experimental @Experimental
@RelationshipResource(name = "rules", entityResource = NodeRuleSetsRelation.class, title = "Folder node rules") @RelationshipResource(name = "rules", entityResource = NodeRuleSetsRelation.class, title = "Folder node rules")
public class NodeRulesRelation implements RelationshipResourceAction.Read<Rule>, InitializingBean public class NodeRulesRelation implements RelationshipResourceAction.Read<Rule>, RelationshipResourceAction.ReadById<Rule>, InitializingBean
{ {
private Rules rules; private Rules rules;
@@ -57,17 +58,17 @@ public class NodeRulesRelation implements RelationshipResourceAction.Read<Rule>,
} }
/** /**
* List folder node rules for given node's and rule set's IDs as a page. * List folder rules for given folder node's and rule set's IDs as a page.
* *
* - GET /nodes/{folderNodeId}/rulesets/{ruleSetId}/rules * - GET /nodes/{folderNodeId}/rule-sets/{ruleSetId}/rules
* *
* @param folderNodeId - entity resource context for this relationship * @param folderNodeId - entity resource context for this relationship
* @param parameters - will never be null. Contains i.a. paging information and ruleSetId (relationshipId) * @param parameters - will never be null. Contains i.a. paging information and ruleSetId (relationshipId)
* @return a paged list of folder rules * @return {@link CollectionWithPagingInfo} containing a page of folder rules
*/ */
@WebApiDescription( @WebApiDescription(
title = "Get folder node rules", title = "Get folder node rules",
description = "Returns a paged list of folder rules for given node's and rule set's ID", description = "Returns a paged list of folder rules for given node's and rule set's IDs",
successStatus = HttpServletResponse.SC_OK successStatus = HttpServletResponse.SC_OK
) )
@Override @Override
@@ -78,6 +79,30 @@ public class NodeRulesRelation implements RelationshipResourceAction.Read<Rule>,
return rules.getRules(folderNodeId, ruleSetId, parameters.getPaging()); return rules.getRules(folderNodeId, ruleSetId, parameters.getPaging());
} }
/**
* Get single folder rule for given node's, rule set's and rule's IDs.
*
* - GET /nodes/{folderNodeId}/rule-sets/{ruleSetId}/rules/{ruleId}
*
* @param folderNodeId - entity resource context for this relationship
* @param ruleSetId - rule set node ID (associated with folder node)
* @param parameters - will never be null. Contains i.a. ruleId (relationship2Id)
* @return {@link Rule} definition
* @throws RelationshipResourceNotFoundException in case resource was not found
*/
@WebApiDescription(
title="Get folder node rule",
description = "Returns a folder single rule definition for given node's, rule set's and rule's IDs",
successStatus = HttpServletResponse.SC_OK
)
@Override
public Rule readById(String folderNodeId, String ruleSetId, Parameters parameters) throws RelationshipResourceNotFoundException
{
final String ruleId = parameters.getRelationship2Id();
return rules.getRuleById(folderNodeId, ruleSetId, ruleId);
}
public void setRules(Rules rules) public void setRules(Rules rules)
{ {
this.rules = rules; this.rules = rules;

View File

@@ -28,7 +28,7 @@ package org.alfresco.rest.api.impl;
import junit.framework.TestCase; import junit.framework.TestCase;
import org.alfresco.rest.api.Nodes; import org.alfresco.rest.api.Nodes;
import org.alfresco.rest.api.model.Rule; import org.alfresco.rest.api.model.rules.Rule;
import org.alfresco.rest.framework.core.exceptions.InvalidArgumentException; import org.alfresco.rest.framework.core.exceptions.InvalidArgumentException;
import org.alfresco.rest.framework.core.exceptions.PermissionDeniedException; import org.alfresco.rest.framework.core.exceptions.PermissionDeniedException;
import org.alfresco.rest.framework.resource.parameters.CollectionWithPagingInfo; import org.alfresco.rest.framework.resource.parameters.CollectionWithPagingInfo;
@@ -47,6 +47,9 @@ import org.mockito.Mock;
import org.mockito.MockitoAnnotations; import org.mockito.MockitoAnnotations;
import org.mockito.junit.MockitoJUnitRunner; import org.mockito.junit.MockitoJUnitRunner;
import java.util.Collection;
import java.util.List;
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.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.any;
@@ -60,19 +63,22 @@ import static org.mockito.BDDMockito.then;
public class RulesImplTest extends TestCase public class RulesImplTest extends TestCase
{ {
private static final String FOLDER_NODE_ID = "dummy-node-id"; private static final String FOLDER_NODE_ID = "dummy-folder-node-id";
private static final String RULE_SET_ID = "dummy-rule-set-id"; private static final String RULE_SET_ID = "dummy-rule-set-id";
private static final String RULE_ID = "dummy-rule-id";
private static final NodeRef folderNodeRef = new NodeRef(StoreRef.STORE_REF_WORKSPACE_SPACESSTORE, FOLDER_NODE_ID); private static final NodeRef folderNodeRef = new NodeRef(StoreRef.STORE_REF_WORKSPACE_SPACESSTORE, FOLDER_NODE_ID);
private static final NodeRef ruleSetNodeRef = new NodeRef(StoreRef.STORE_REF_WORKSPACE_SPACESSTORE, RULE_SET_ID); private static final NodeRef ruleSetNodeRef = new NodeRef(StoreRef.STORE_REF_WORKSPACE_SPACESSTORE, RULE_SET_ID);
private static final NodeRef ruleNodeRef = new NodeRef(StoreRef.STORE_REF_WORKSPACE_SPACESSTORE, RULE_ID);
private static final Paging paging = Paging.DEFAULT;
@Mock @Mock
private Nodes nodes; private Nodes nodesMock;
@Mock @Mock
private PermissionService permissionService; private PermissionService permissionServiceMock;
@Mock @Mock
private RuleService ruleService; private RuleService ruleServiceMock;
@InjectMocks @InjectMocks
private RulesImpl rules; private RulesImpl rules;
@@ -83,111 +89,202 @@ public class RulesImplTest extends TestCase
{ {
MockitoAnnotations.openMocks(this); MockitoAnnotations.openMocks(this);
given(nodes.validateNode(eq(FOLDER_NODE_ID))).willReturn(folderNodeRef); given(nodesMock.validateOrLookupNode(eq(FOLDER_NODE_ID), any())).willReturn(folderNodeRef);
given(nodes.validateNode(eq(RULE_SET_ID))).willReturn(ruleSetNodeRef); given(nodesMock.validateNode(eq(RULE_SET_ID))).willReturn(ruleSetNodeRef);
given(nodes.nodeMatches(any(), any(), any())).willReturn(true); given(nodesMock.nodeMatches(any(), any(), any())).willReturn(true);
given(permissionService.hasReadPermission(any())).willReturn(AccessStatus.ALLOWED); given(permissionServiceMock.hasReadPermission(any())).willReturn(AccessStatus.ALLOWED);
} }
@Test @Test
public void testGetRules() public void testGetRules()
{ {
final Paging paging = Paging.DEFAULT; given(ruleServiceMock.isRuleSetAssociatedWithFolder(any(), any())).willReturn(true);
given(ruleService.isRuleSetAssociatedWithFolder(any(), any())).willReturn(true); given(ruleServiceMock.getRules(any())).willReturn(List.of(createRule(RULE_ID)));
// when // when
final CollectionWithPagingInfo<Rule> rulesPage = rules.getRules(FOLDER_NODE_ID, RULE_SET_ID, paging); final CollectionWithPagingInfo<Rule> rulesPage = rules.getRules(FOLDER_NODE_ID, RULE_SET_ID, paging);
then(nodes).should().validateNode(eq(FOLDER_NODE_ID)); then(nodesMock).should().validateOrLookupNode(eq(FOLDER_NODE_ID), isNull());
then(nodes).should().validateNode(eq(RULE_SET_ID)); then(nodesMock).should().validateNode(eq(RULE_SET_ID));
then(nodes).should().nodeMatches(eq(folderNodeRef), any(), isNull()); then(nodesMock).should().nodeMatches(eq(folderNodeRef), any(), isNull());
then(nodes).should().nodeMatches(eq(ruleSetNodeRef), any(), isNull()); then(nodesMock).should().nodeMatches(eq(ruleSetNodeRef), any(), isNull());
then(nodes).shouldHaveNoMoreInteractions(); then(nodesMock).shouldHaveNoMoreInteractions();
then(permissionService).should().hasReadPermission(eq(folderNodeRef)); then(permissionServiceMock).should().hasReadPermission(eq(folderNodeRef));
then(permissionService).should().hasReadPermission(eq(ruleSetNodeRef)); then(permissionServiceMock).shouldHaveNoMoreInteractions();
then(permissionService).shouldHaveNoMoreInteractions(); then(ruleServiceMock).should().isRuleSetAssociatedWithFolder(eq(ruleSetNodeRef), eq(folderNodeRef));
then(ruleService).should().isRuleSetAssociatedWithFolder(eq(ruleSetNodeRef), eq(folderNodeRef)); then(ruleServiceMock).should().getRules(eq(folderNodeRef));
then(ruleService).should().getRules(eq(folderNodeRef)); then(ruleServiceMock).shouldHaveNoMoreInteractions();
then(ruleService).shouldHaveNoMoreInteractions();
assertThat(rulesPage) assertThat(rulesPage)
.isNotNull() .isNotNull()
.extracting(CollectionWithPagingInfo::getCollection) .extracting(CollectionWithPagingInfo::getCollection)
.isNotNull(); .isNotNull()
.extracting(Collection::size)
.isEqualTo(1);
assertThat(rulesPage.getCollection().stream().findFirst().orElse(null))
.isNotNull()
.extracting(Rule::getId)
.isEqualTo(RULE_ID);
} }
@Test @Test
public void testGetRulesForDefaultRuleSet() public void testGetRulesForDefaultRuleSet()
{ {
final String defaultRuleSetId = "-default-"; final String defaultRuleSetId = "-default-";
final Paging paging = Paging.DEFAULT; given(ruleServiceMock.getRules(any())).willReturn(List.of(createRule(RULE_ID)));
// when // when
final CollectionWithPagingInfo<Rule> rulesPage = rules.getRules(FOLDER_NODE_ID, defaultRuleSetId, paging); final CollectionWithPagingInfo<Rule> rulesPage = rules.getRules(FOLDER_NODE_ID, defaultRuleSetId, paging);
then(nodes).should().validateNode(eq(FOLDER_NODE_ID)); then(nodesMock).should().validateOrLookupNode(eq(FOLDER_NODE_ID), isNull());
then(nodes).should().nodeMatches(eq(folderNodeRef), any(), isNull()); then(nodesMock).should().nodeMatches(eq(folderNodeRef), any(), isNull());
then(nodes).shouldHaveNoMoreInteractions(); then(nodesMock).shouldHaveNoMoreInteractions();
then(permissionService).should().hasReadPermission(eq(folderNodeRef)); then(permissionServiceMock).should().hasReadPermission(eq(folderNodeRef));
then(permissionService).shouldHaveNoMoreInteractions(); then(permissionServiceMock).shouldHaveNoMoreInteractions();
then(ruleService).should().getRules(eq(folderNodeRef)); then(ruleServiceMock).should().getRuleSetNode(eq(folderNodeRef));
then(ruleService).shouldHaveNoMoreInteractions(); then(ruleServiceMock).should().getRules(eq(folderNodeRef));
then(ruleServiceMock).shouldHaveNoMoreInteractions();
assertThat(rulesPage) assertThat(rulesPage)
.isNotNull() .isNotNull()
.extracting(CollectionWithPagingInfo::getCollection) .extracting(CollectionWithPagingInfo::getCollection)
.isNotNull(); .isNotNull()
.extracting(Collection::size)
.isEqualTo(1);
assertThat(rulesPage.getCollection().stream().findFirst().orElse(null))
.isNotNull()
.extracting(Rule::getId)
.isEqualTo(RULE_ID);
} }
@Test @Test
public void testGetRulesForNotExistingFolderNode() public void testGetRulesForNotExistingFolderNode()
{ {
final Paging paging = Paging.DEFAULT; given(nodesMock.nodeMatches(eq(folderNodeRef), any(), any())).willReturn(false);
given(nodes.nodeMatches(eq(folderNodeRef), any(), any())).willReturn(false);
// when // when
assertThatExceptionOfType(InvalidArgumentException.class).isThrownBy( assertThatExceptionOfType(InvalidArgumentException.class).isThrownBy(
() -> rules.getRules(FOLDER_NODE_ID, RULE_SET_ID, paging)); () -> rules.getRules(FOLDER_NODE_ID, RULE_SET_ID, paging));
then(ruleService).shouldHaveNoInteractions(); then(ruleServiceMock).shouldHaveNoInteractions();
} }
@Test @Test
public void testGetRulesForNotExistingRuleSetNode() public void testGetRulesForNotExistingRuleSetNode()
{ {
final Paging paging = Paging.DEFAULT; given(nodesMock.nodeMatches(eq(folderNodeRef), any(), any())).willReturn(true);
given(nodes.nodeMatches(eq(folderNodeRef), any(), any())).willReturn(true); given(nodesMock.nodeMatches(eq(ruleSetNodeRef), any(), any())).willReturn(false);
given(nodes.nodeMatches(eq(ruleSetNodeRef), any(), any())).willReturn(false);
// when // when
assertThatExceptionOfType(InvalidArgumentException.class).isThrownBy( assertThatExceptionOfType(InvalidArgumentException.class).isThrownBy(
() -> rules.getRules(FOLDER_NODE_ID, RULE_SET_ID, paging)); () -> rules.getRules(FOLDER_NODE_ID, RULE_SET_ID, paging));
then(ruleService).shouldHaveNoInteractions(); then(ruleServiceMock).shouldHaveNoInteractions();
} }
@Test @Test
public void testGetRulesForNotAssociatedRuleSetToFolder() public void testGetRulesForNotAssociatedRuleSetToFolder()
{ {
final Paging paging = Paging.DEFAULT; given(ruleServiceMock.isRuleSetAssociatedWithFolder(any(), any())).willReturn(false);
given(ruleService.isRuleSetAssociatedWithFolder(any(), any())).willReturn(false);
// when // when
assertThatExceptionOfType(InvalidArgumentException.class).isThrownBy( assertThatExceptionOfType(InvalidArgumentException.class).isThrownBy(
() -> rules.getRules(FOLDER_NODE_ID, RULE_SET_ID, paging)); () -> rules.getRules(FOLDER_NODE_ID, RULE_SET_ID, paging));
then(ruleService).should().isRuleSetAssociatedWithFolder(eq(ruleSetNodeRef), eq(folderNodeRef)); then(ruleServiceMock).should().isRuleSetAssociatedWithFolder(eq(ruleSetNodeRef), eq(folderNodeRef));
then(ruleService).shouldHaveNoMoreInteractions(); then(ruleServiceMock).shouldHaveNoMoreInteractions();
} }
@Test @Test
public void testGetRulesWithoutReadPermission() public void testGetRulesWithoutReadPermission()
{ {
final Paging paging = Paging.DEFAULT; given(permissionServiceMock.hasReadPermission(any())).willReturn(AccessStatus.DENIED);
given(permissionService.hasReadPermission(any())).willReturn(AccessStatus.DENIED);
// when // when
assertThatExceptionOfType(PermissionDeniedException.class).isThrownBy( assertThatExceptionOfType(PermissionDeniedException.class).isThrownBy(
() -> rules.getRules(FOLDER_NODE_ID, RULE_SET_ID, paging)); () -> rules.getRules(FOLDER_NODE_ID, RULE_SET_ID, paging));
then(ruleService).shouldHaveNoInteractions(); then(ruleServiceMock).shouldHaveNoInteractions();
}
@Test
public void testGetRuleById()
{
given(nodesMock.validateNode(eq(RULE_ID))).willReturn(ruleNodeRef);
given(ruleServiceMock.isRuleSetAssociatedWithFolder(any(), any())).willReturn(true);
given(ruleServiceMock.isRuleAssociatedWithRuleSet(any(), any())).willReturn(true);
given(ruleServiceMock.getRule(any())).willReturn(createRule(RULE_ID));
// when
final Rule rule = rules.getRuleById(FOLDER_NODE_ID, RULE_SET_ID, RULE_ID);
then(nodesMock).should().validateOrLookupNode(eq(FOLDER_NODE_ID), isNull());
then(nodesMock).should().validateNode(eq(RULE_SET_ID));
then(nodesMock).should().validateNode(eq(RULE_ID));
then(nodesMock).should().nodeMatches(eq(folderNodeRef), any(), isNull());
then(nodesMock).should().nodeMatches(eq(ruleSetNodeRef), any(), isNull());
then(nodesMock).should().nodeMatches(eq(ruleNodeRef), any(), isNull());
then(nodesMock).shouldHaveNoMoreInteractions();
then(permissionServiceMock).should().hasReadPermission(eq(folderNodeRef));
then(permissionServiceMock).shouldHaveNoMoreInteractions();
then(ruleServiceMock).should().isRuleSetAssociatedWithFolder(eq(ruleSetNodeRef), eq(folderNodeRef));
then(ruleServiceMock).should().isRuleAssociatedWithRuleSet(eq(ruleNodeRef), eq(ruleSetNodeRef));
then(ruleServiceMock).should().getRule(eq(ruleNodeRef));
then(ruleServiceMock).shouldHaveNoMoreInteractions();
assertThat(rule)
.isNotNull()
.extracting(Rule::getId)
.isEqualTo(RULE_ID);
}
@Test
public void testGetRuleByIdForDefaultRuleSet()
{
final String defaultRuleSetId = "-default-";
given(nodesMock.validateNode(eq(RULE_ID))).willReturn(ruleNodeRef);
given(ruleServiceMock.getRuleSetNode(any())).willReturn(ruleSetNodeRef);
given(ruleServiceMock.isRuleAssociatedWithRuleSet(any(), any())).willReturn(true);
given(ruleServiceMock.getRule(any())).willReturn(createRule(RULE_ID));
// when
final Rule rule = rules.getRuleById(FOLDER_NODE_ID, defaultRuleSetId, RULE_ID);
then(nodesMock).should().validateOrLookupNode(eq(FOLDER_NODE_ID), isNull());
then(nodesMock).should().validateNode(eq(RULE_ID));
then(nodesMock).should().nodeMatches(eq(folderNodeRef), any(), isNull());
then(nodesMock).should().nodeMatches(eq(ruleNodeRef), any(), isNull());
then(nodesMock).shouldHaveNoMoreInteractions();
then(permissionServiceMock).should().hasReadPermission(eq(folderNodeRef));
then(permissionServiceMock).shouldHaveNoMoreInteractions();
then(ruleServiceMock).should().getRuleSetNode(eq(folderNodeRef));
then(ruleServiceMock).should().isRuleAssociatedWithRuleSet(eq(ruleNodeRef), eq(ruleSetNodeRef));
then(ruleServiceMock).should().getRule(eq(ruleNodeRef));
then(ruleServiceMock).shouldHaveNoMoreInteractions();
assertThat(rule)
.isNotNull()
.extracting(Rule::getId)
.isEqualTo(RULE_ID);
}
@Test
public void testGetRuleByIdForNotAssociatedRuleToRuleSet()
{
given(nodesMock.validateNode(eq(RULE_SET_ID))).willReturn(ruleSetNodeRef);
given(nodesMock.validateNode(eq(RULE_ID))).willReturn(ruleNodeRef);
given(ruleServiceMock.isRuleSetAssociatedWithFolder(any(), any())).willReturn(true);
given(ruleServiceMock.isRuleAssociatedWithRuleSet(any(), any())).willReturn(false);
// when
assertThatExceptionOfType(InvalidArgumentException.class).isThrownBy(
() -> rules.getRuleById(FOLDER_NODE_ID, RULE_SET_ID, RULE_ID));
then(ruleServiceMock).should().isRuleSetAssociatedWithFolder(eq(ruleSetNodeRef), eq(folderNodeRef));
then(ruleServiceMock).should().isRuleAssociatedWithRuleSet(eq(ruleNodeRef), eq(ruleSetNodeRef));
then(ruleServiceMock).shouldHaveNoMoreInteractions();
}
private static org.alfresco.service.cmr.rule.Rule createRule(final String id) {
final org.alfresco.service.cmr.rule.Rule rule = new org.alfresco.service.cmr.rule.Rule();
rule.setNodeRef(new NodeRef(StoreRef.STORE_REF_WORKSPACE_SPACESSTORE, id));
return rule;
} }
} }

View File

@@ -30,7 +30,7 @@ import junit.framework.TestCase;
import org.alfresco.rest.api.Rules; import org.alfresco.rest.api.Rules;
import org.alfresco.rest.framework.resource.parameters.Paging; import org.alfresco.rest.framework.resource.parameters.Paging;
import org.alfresco.rest.framework.resource.parameters.Parameters; import org.alfresco.rest.framework.resource.parameters.Parameters;
import org.alfresco.rest.framework.resource.parameters.Params; import org.alfresco.rest.framework.tests.core.ParamsExtender;
import org.alfresco.service.Experimental; import org.alfresco.service.Experimental;
import org.junit.Before; import org.junit.Before;
import org.junit.Test; import org.junit.Test;
@@ -50,9 +50,10 @@ public class NodeRulesRelationTest extends TestCase
private static final String FOLDER_NODE_ID = "dummy-node-id"; private static final String FOLDER_NODE_ID = "dummy-node-id";
private static final String RULE_SET_ID = "dummy-rule-set-id"; private static final String RULE_SET_ID = "dummy-rule-set-id";
private static final String RULE_ID = "dummy-rule-id";
@Mock @Mock
private Rules rules; private Rules rulesMock;
@InjectMocks @InjectMocks
private NodeRulesRelation nodeRulesRelation; private NodeRulesRelation nodeRulesRelation;
@@ -68,13 +69,24 @@ public class NodeRulesRelationTest extends TestCase
public void testReadAll() public void testReadAll()
{ {
final Paging paging = Paging.DEFAULT; final Paging paging = Paging.DEFAULT;
final Params.RecognizedParams params = new Params.RecognizedParams(null, paging, null, null, null, null, null, null, false); final Parameters parameters = ParamsExtender.valueOf(paging, FOLDER_NODE_ID, RULE_SET_ID, null);
final Parameters parameters = Params.valueOf(FOLDER_NODE_ID, RULE_SET_ID, params, null, null);
// when // when
nodeRulesRelation.readAll(FOLDER_NODE_ID, parameters); nodeRulesRelation.readAll(FOLDER_NODE_ID, parameters);
then(rules).should().getRules(eq(FOLDER_NODE_ID), eq(RULE_SET_ID), eq(paging)); then(rulesMock).should().getRules(eq(FOLDER_NODE_ID), eq(RULE_SET_ID), eq(paging));
then(rules).shouldHaveNoMoreInteractions(); then(rulesMock).shouldHaveNoMoreInteractions();
}
@Test
public void testReadById()
{
final Parameters parameters = ParamsExtender.valueOf(null, FOLDER_NODE_ID, RULE_SET_ID, RULE_ID);
// when
nodeRulesRelation.readById(FOLDER_NODE_ID, RULE_SET_ID, parameters);
then(rulesMock).should().getRuleById(eq(FOLDER_NODE_ID), eq(RULE_SET_ID), eq(RULE_ID));
then(rulesMock).shouldHaveNoMoreInteractions();
} }
} }

View File

@@ -2,7 +2,7 @@
* #%L * #%L
* Alfresco Remote API * Alfresco Remote API
* %% * %%
* Copyright (C) 2005 - 2020 Alfresco Software Limited * Copyright (C) 2005 - 2022 Alfresco Software Limited
* %% * %%
* This file is part of the Alfresco software. * This file is part of the Alfresco software.
* If the software was purchased under a paid Alfresco license, the terms of * If the software was purchased under a paid Alfresco license, the terms of
@@ -43,29 +43,33 @@ import org.springframework.extensions.webscripts.WebScriptRequest;
public class ParamsExtender extends Params public class ParamsExtender extends Params
{ {
private ParamsExtender(String entityId, String relationshipId, Object passedIn, InputStream stream, String addressedProperty, RecognizedParams recognizedParams) private ParamsExtender(String entityId, String relationshipId, String relationship2Id, Object passedIn, InputStream stream, String addressedProperty, RecognizedParams recognizedParams)
{ {
super(null,entityId, relationshipId, null, passedIn, stream, addressedProperty, recognizedParams, null, mock(WebScriptRequest.class)); super(null,entityId, relationshipId, relationship2Id, passedIn, stream, addressedProperty, recognizedParams, null, mock(WebScriptRequest.class));
} }
public static Params valueOf(Map<String, BeanPropertiesFilter> rFilter, String entityId) public static Params valueOf(Map<String, BeanPropertiesFilter> rFilter, String entityId)
{ {
return new ParamsExtender(entityId, null, null, null, null, new Params.RecognizedParams(null, null, null, rFilter, null, null, null, null, false)); return new ParamsExtender(entityId, null, null, null, null, null, new Params.RecognizedParams(null, null, null, rFilter, null, null, null, null, false));
} }
public static Params valueOf(boolean includeSource, String entityId) public static Params valueOf(boolean includeSource, String entityId)
{ {
return new ParamsExtender(entityId, null, null, null, null, new Params.RecognizedParams(null, null, null, null, null, null, null, null, includeSource)); return new ParamsExtender(entityId, null, null, null, null, null, new Params.RecognizedParams(null, null, null, null, null, null, null, null, includeSource));
} }
public static Params valueOf(Paging paging, String entityId) public static Params valueOf(Paging paging, String entityId)
{ {
return new ParamsExtender(entityId, null, null, null, null, new Params.RecognizedParams(null, paging, null, null, null, null, null, null, false)); return new ParamsExtender(entityId, null, null, null, null, null, new Params.RecognizedParams(null, paging, null, null, null, null, null, null, false));
} }
public static Params valueOf(Map<String, String[]> params) public static Params valueOf(Map<String, String[]> params)
{ {
return new ParamsExtender(null, null, null, null, null, new Params.RecognizedParams(params, null, null, null, null, null, null, null, false)); return new ParamsExtender(null, null, null, null, null, null, new Params.RecognizedParams(params, null, null, null, null, null, null, null, false));
} }
public static Params valueOf(Paging paging, String entityId, String relationshipId, String relationship2Id)
{
return new ParamsExtender(entityId, relationshipId, relationship2Id, null, null, null, new Params.RecognizedParams(null, paging, null, null, null, null, null, null, false));
}
} }

View File

@@ -40,6 +40,7 @@ import org.alfresco.repo.security.authentication.AuthenticationUtil;
import org.alfresco.repo.transaction.AlfrescoTransactionSupport; import org.alfresco.repo.transaction.AlfrescoTransactionSupport;
import org.alfresco.repo.transaction.TransactionListener; import org.alfresco.repo.transaction.TransactionListener;
import org.alfresco.repo.transaction.TransactionalResourceHelper; import org.alfresco.repo.transaction.TransactionalResourceHelper;
import org.alfresco.service.Experimental;
import org.alfresco.service.cmr.action.Action; import org.alfresco.service.cmr.action.Action;
import org.alfresco.service.cmr.action.ActionService; import org.alfresco.service.cmr.action.ActionService;
import org.alfresco.service.cmr.action.ActionServiceException; import org.alfresco.service.cmr.action.ActionServiceException;
@@ -1614,13 +1615,41 @@ public class RuleServiceImpl
} }
@Override @Override
public boolean isRuleSetAssociatedWithFolder(final NodeRef ruleSetNodeRef, final NodeRef folderNodeRef) { @Experimental
return findAssociatedParents(ruleSetNodeRef, RuleModel.ASSOC_RULE_FOLDER).stream() public NodeRef getRuleSetNode(final NodeRef folderNodeRef) {
.map(ChildAssociationRef::getParentRef) return getPrimaryChildNode(folderNodeRef, RuleModel.ASSOC_RULE_FOLDER);
.anyMatch(folderNodeRef::equals);
} }
private List<ChildAssociationRef> findAssociatedParents(final NodeRef nodeRef, final QNamePattern pattern) { private NodeRef getPrimaryChildNode(final NodeRef nodeRef, final QNamePattern associationType) {
return runtimeNodeService.getParentAssocs(nodeRef, pattern, pattern); return runtimeNodeService.getChildAssocs(nodeRef, associationType, associationType).stream()
.filter(ChildAssociationRef::isPrimary)
.map(ChildAssociationRef::getChildRef)
.findFirst()
.orElse(null);
}
@Override
@Experimental
public boolean isRuleSetAssociatedWithFolder(final NodeRef ruleSetNodeRef, final NodeRef folderNodeRef) {
return isChildOf(ruleSetNodeRef, RuleModel.ASSOC_RULE_FOLDER, folderNodeRef);
}
@Override
@Experimental
public boolean isRuleAssociatedWithRuleSet(final NodeRef ruleNodeRef, final NodeRef ruleSetNodeRef) {
return isChildOf(ruleNodeRef, null, ruleSetNodeRef);
}
private boolean isChildOf(final NodeRef childNodeRef, final QNamePattern associationType, final NodeRef parentNodeRef) {
final List<ChildAssociationRef> associations;
if (associationType == null) {
associations = runtimeNodeService.getParentAssocs(childNodeRef);
} else {
associations = runtimeNodeService.getParentAssocs(childNodeRef, associationType, associationType);
}
return associations.stream()
.map(ChildAssociationRef::getParentRef)
.anyMatch(parentNodeRef::equals);
} }
} }

View File

@@ -28,6 +28,7 @@ package org.alfresco.service.cmr.rule;
import java.util.List; import java.util.List;
import org.alfresco.service.Auditable; import org.alfresco.service.Auditable;
import org.alfresco.service.Experimental;
import org.alfresco.service.cmr.action.Action; import org.alfresco.service.cmr.action.Action;
import org.alfresco.service.cmr.repository.NodeRef; import org.alfresco.service.cmr.repository.NodeRef;
@@ -331,12 +332,34 @@ public interface RuleService
public List<NodeRef> getLinkedFromRuleNodes(NodeRef nodeRef); public List<NodeRef> getLinkedFromRuleNodes(NodeRef nodeRef);
/** /**
* Check if rule set's associated parent is equal to folder node. * Get rule set node associated with folder
*
* @param folderNodeRef - folder node reference
* @return node reference of a rule set
*/
@Auditable(parameters = {"folderNodeRef"})
@Experimental
NodeRef getRuleSetNode(final NodeRef folderNodeRef);
/**
* Check if rule set's associated parent matches folder node.
* *
* @param ruleSetNodeRef - node reference of a rule set * @param ruleSetNodeRef - node reference of a rule set
* @param folderNodeRef - node reference of a folder * @param folderNodeRef - node reference of a folder
* @return true if rule set is associated with folder * @return true if rule set is associated with folder
*/ */
@Auditable(parameters = {"nodeRef"}) @Auditable(parameters = {"ruleSetNodeRef", "folderNodeRef"})
@Experimental
boolean isRuleSetAssociatedWithFolder(NodeRef ruleSetNodeRef, NodeRef folderNodeRef); boolean isRuleSetAssociatedWithFolder(NodeRef ruleSetNodeRef, NodeRef folderNodeRef);
/**
* Check if rule's associated parent matches rule set node.
*
* @param ruleNodeRef - node reference of a rule
* @param ruleSetNodeRef - node reference of a rule set
* @return true if rule is associated with rule set
*/
@Auditable(parameters = {"ruleNodeRef", "ruleSetNodeRef"})
@Experimental
boolean isRuleAssociatedWithRuleSet(final NodeRef ruleNodeRef, final NodeRef ruleSetNodeRef);
} }

View File

@@ -352,16 +352,24 @@ public class RuleLinkTest extends BaseSpringTest
assertEquals(rules1, rules3); assertEquals(rules1, rules3);
} }
@Test
public void testGetRuleSetNode() {
final Rule rule = createTestRule(false, "luke");
this.ruleService.saveRule(folderOne, rule);
final NodeRef expectedRuleSetNodeRef = getRuleSetNode(folderOne);
final NodeRef ruleSetNodeRef = ruleService.getRuleSetNode(folderOne);
assertNotNull(ruleSetNodeRef);
assertEquals(expectedRuleSetNodeRef, ruleSetNodeRef);
}
@Test @Test
public void testIsRuleSetAssociatedWithFolder() public void testIsRuleSetAssociatedWithFolder()
{ {
final Rule rule = createTestRule(false, "luke"); final Rule rule = createTestRule(false, "luke");
this.ruleService.saveRule(folderOne, rule); this.ruleService.saveRule(folderOne, rule);
final NodeRef ruleSetNodeRef = nodeService.getChildAssocs(folderOne, RuleModel.ASSOC_RULE_FOLDER, RuleModel.ASSOC_RULE_FOLDER).stream() final NodeRef ruleSetNodeRef = getRuleSetNode(folderOne);
.filter(ChildAssociationRef::isPrimary)
.map(ChildAssociationRef::getChildRef)
.findFirst()
.orElse(null);
// when // when
final boolean associated = ruleService.isRuleSetAssociatedWithFolder(ruleSetNodeRef, folderOne); final boolean associated = ruleService.isRuleSetAssociatedWithFolder(ruleSetNodeRef, folderOne);
@@ -374,11 +382,7 @@ public class RuleLinkTest extends BaseSpringTest
{ {
final Rule rule = createTestRule(false, "luke"); final Rule rule = createTestRule(false, "luke");
this.ruleService.saveRule(folderTwo , rule); this.ruleService.saveRule(folderTwo , rule);
final NodeRef ruleSetNodeRef = nodeService.getChildAssocs(folderTwo, RuleModel.ASSOC_RULE_FOLDER, RuleModel.ASSOC_RULE_FOLDER).stream() final NodeRef ruleSetNodeRef = getRuleSetNode(folderTwo);
.filter(ChildAssociationRef::isPrimary)
.map(ChildAssociationRef::getChildRef)
.findFirst()
.orElse(null);
// when // when
final boolean associated = ruleService.isRuleSetAssociatedWithFolder(ruleSetNodeRef, folderOne); final boolean associated = ruleService.isRuleSetAssociatedWithFolder(ruleSetNodeRef, folderOne);
@@ -386,6 +390,41 @@ public class RuleLinkTest extends BaseSpringTest
assertFalse(associated); assertFalse(associated);
} }
@Test
public void testIsRuleAssociatedWithRuleSet()
{
final Rule rule = createTestRule(false, "luke");
this.ruleService.saveRule(folderOne, rule);
final NodeRef ruleSetNodeRef = getRuleSetNode(folderOne);
List<ChildAssociationRef> folderChildes = nodeService.getChildAssocs(folderOne);
List<ChildAssociationRef> ruleSetChildes = nodeService.getChildAssocs(ruleSetNodeRef);
List<ChildAssociationRef> ruleParents = nodeService.getParentAssocs(rule.getNodeRef());
// when
final boolean associated = ruleService.isRuleAssociatedWithRuleSet(rule.getNodeRef(), ruleSetNodeRef);
assertNotNull(folderChildes);
assertNotNull(ruleSetChildes);
assertNotNull(ruleParents);
assertTrue(associated);
}
@Test
public void testIsRuleNotAssociatedWithRuleSet()
{
final Rule rule = createTestRule(false, "luke");
final Rule otherRule = createTestRule(false, "bobs rule");
this.ruleService.saveRule(folderOne, rule);
this.ruleService.saveRule(folderTwo, otherRule);
final NodeRef ruleSetNodeRef = getRuleSetNode(folderOne);
// when
final boolean associated = ruleService.isRuleAssociatedWithRuleSet(otherRule.getNodeRef(), ruleSetNodeRef);
assertFalse(associated);
}
protected Rule createTestRule(boolean isAppliedToChildren, String title) protected Rule createTestRule(boolean isAppliedToChildren, String title)
{ {
// Rule properties // Rule properties
@@ -417,4 +456,11 @@ public class RuleLinkTest extends BaseSpringTest
return rule; return rule;
} }
private NodeRef getRuleSetNode(final NodeRef folderNodeRef) {
return nodeService.getChildAssocs(folderNodeRef, RuleModel.ASSOC_RULE_FOLDER, RuleModel.ASSOC_RULE_FOLDER).stream()
.filter(ChildAssociationRef::isPrimary)
.map(ChildAssociationRef::getChildRef)
.findFirst()
.orElse(null);
}
} }