diff --git a/data-model/src/main/java/org/alfresco/service/namespace/NamespaceService.java b/data-model/src/main/java/org/alfresco/service/namespace/NamespaceService.java index 2e11398706..ab4be2d1a7 100644 --- a/data-model/src/main/java/org/alfresco/service/namespace/NamespaceService.java +++ b/data-model/src/main/java/org/alfresco/service/namespace/NamespaceService.java @@ -146,7 +146,7 @@ public interface NamespaceService extends NamespacePrefixResolver /** Email Server Application Model Prefix */ static final String EMAILSERVER_MODEL_PREFIX = "emailserver"; - + /** * Register a prefix for namespace uri. * diff --git a/remote-api/src/main/java/org/alfresco/rest/api/Rules.java b/remote-api/src/main/java/org/alfresco/rest/api/Rules.java index 87a3e98d73..2580936c55 100644 --- a/remote-api/src/main/java/org/alfresco/rest/api/Rules.java +++ b/remote-api/src/main/java/org/alfresco/rest/api/Rules.java @@ -26,7 +26,7 @@ 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.Paging; import org.alfresco.service.Experimental; @@ -41,10 +41,20 @@ public interface Rules /** * Get rules for node's and rule set's IDs * - * @param folderNodeId node ID - * @param ruleSetId rule set ID - * @param paging {@link Paging} information + * @param folderNodeId - folder node ID + * @param ruleSetId - rule set ID + * @param paging - {@link Paging} information * @return {@link CollectionWithPagingInfo} containing a list page of folder rules */ CollectionWithPagingInfo 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); } diff --git a/remote-api/src/main/java/org/alfresco/rest/api/impl/RulesImpl.java b/remote-api/src/main/java/org/alfresco/rest/api/impl/RulesImpl.java index 8eaf7cba0c..5f0f38d069 100644 --- a/remote-api/src/main/java/org/alfresco/rest/api/impl/RulesImpl.java +++ b/remote-api/src/main/java/org/alfresco/rest/api/impl/RulesImpl.java @@ -27,9 +27,11 @@ package org.alfresco.rest.api.impl; import org.alfresco.model.ContentModel; +import org.alfresco.repo.rule.RuleModel; import org.alfresco.rest.api.Nodes; 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.PermissionDeniedException; import org.alfresco.rest.framework.resource.parameters.CollectionWithPagingInfo; @@ -49,8 +51,7 @@ import java.util.stream.Collectors; @Experimental public class RulesImpl implements Rules { - - private static final String DEFAULT_RULE_SET_ID = "-default-"; + private static final String RULE_SET_EXPECTED_TYPE_NAME = "rule set"; private Nodes nodes; @@ -61,15 +62,8 @@ public class RulesImpl implements Rules @Override public CollectionWithPagingInfo getRules(final String folderNodeId, final String ruleSetId, final Paging paging) { - final NodeRef folderNodeRef = validateNode(folderNodeId, ContentModel.TYPE_FOLDER); - - 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 NodeRef folderNodeRef = validateFolderNode(folderNodeId); + validateRuleSetNode(ruleSetId, folderNodeRef); final List rules = ruleService.getRules(folderNodeRef).stream() .map(Rule::from) @@ -78,6 +72,16 @@ public class RulesImpl implements Rules 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) { this.nodes = nodes; @@ -93,37 +97,81 @@ public class RulesImpl implements Rules 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 expectedType - expected type - * @param expectedTypeName - expected type local name - * @return node reference + * @param folderNodeId - folder node ID + * @return folder node reference + * @throws InvalidArgumentException if node is not of an expected type + * @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) { 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 expectedTypes = Set.of(expectedType); if (!nodes.nodeMatches(nodeRef, expectedTypes, null)) { final String expectedTypeLocalName = (expectedTypeName != null)? expectedTypeName : expectedType.getLocalName(); 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); } } diff --git a/remote-api/src/main/java/org/alfresco/rest/api/model/Rule.java b/remote-api/src/main/java/org/alfresco/rest/api/model/rules/Rule.java similarity index 85% rename from remote-api/src/main/java/org/alfresco/rest/api/model/Rule.java rename to remote-api/src/main/java/org/alfresco/rest/api/model/rules/Rule.java index 13812bcafd..d44291abd5 100644 --- a/remote-api/src/main/java/org/alfresco/rest/api/model/Rule.java +++ b/remote-api/src/main/java/org/alfresco/rest/api/model/rules/Rule.java @@ -24,14 +24,19 @@ * #L% */ -package org.alfresco.rest.api.model; +package org.alfresco.rest.api.model.rules; import org.alfresco.rest.framework.resource.UniqueId; import org.alfresco.service.Experimental; +import org.alfresco.service.cmr.action.CompositeAction; + +import java.util.List; +import java.util.stream.Collectors; @Experimental public class Rule { + private String id; private String name; @@ -67,4 +72,10 @@ public class Rule { this.name = name; } + + @Override + public String toString() + { + return "Rule{" + "id='" + id + '\'' + ", name='" + name + '\'' + '}'; + } } diff --git a/remote-api/src/main/java/org/alfresco/rest/api/model/rules/RuleSet.java b/remote-api/src/main/java/org/alfresco/rest/api/model/rules/RuleSet.java new file mode 100644 index 0000000000..5202dfbbcd --- /dev/null +++ b/remote-api/src/main/java/org/alfresco/rest/api/model/rules/RuleSet.java @@ -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 . + * #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 + '\'' + '}'; + } +} diff --git a/remote-api/src/main/java/org/alfresco/rest/api/nodes/NodeRulesRelation.java b/remote-api/src/main/java/org/alfresco/rest/api/nodes/NodeRulesRelation.java index 3cb37dbf21..2345825573 100644 --- a/remote-api/src/main/java/org/alfresco/rest/api/nodes/NodeRulesRelation.java +++ b/remote-api/src/main/java/org/alfresco/rest/api/nodes/NodeRulesRelation.java @@ -27,8 +27,9 @@ package org.alfresco.rest.api.nodes; 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.core.exceptions.RelationshipResourceNotFoundException; import org.alfresco.rest.framework.resource.RelationshipResource; import org.alfresco.rest.framework.resource.actions.interfaces.RelationshipResourceAction; import org.alfresco.rest.framework.resource.parameters.CollectionWithPagingInfo; @@ -45,7 +46,7 @@ import javax.servlet.http.HttpServletResponse; */ @Experimental @RelationshipResource(name = "rules", entityResource = NodeRuleSetsRelation.class, title = "Folder node rules") -public class NodeRulesRelation implements RelationshipResourceAction.Read, InitializingBean +public class NodeRulesRelation implements RelationshipResourceAction.Read, RelationshipResourceAction.ReadById, InitializingBean { private Rules rules; @@ -57,17 +58,17 @@ public class NodeRulesRelation implements RelationshipResourceAction.Read, } /** - * 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 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( 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 ) @Override @@ -78,6 +79,30 @@ public class NodeRulesRelation implements RelationshipResourceAction.Read, 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) { this.rules = rules; diff --git a/remote-api/src/test/java/org/alfresco/rest/api/impl/RulesImplTest.java b/remote-api/src/test/java/org/alfresco/rest/api/impl/RulesImplTest.java index 52e70fe181..ad50bf30a5 100644 --- a/remote-api/src/test/java/org/alfresco/rest/api/impl/RulesImplTest.java +++ b/remote-api/src/test/java/org/alfresco/rest/api/impl/RulesImplTest.java @@ -28,7 +28,7 @@ package org.alfresco.rest.api.impl; import junit.framework.TestCase; 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.PermissionDeniedException; import org.alfresco.rest.framework.resource.parameters.CollectionWithPagingInfo; @@ -47,6 +47,9 @@ import org.mockito.Mock; import org.mockito.MockitoAnnotations; 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.assertThatExceptionOfType; import static org.mockito.ArgumentMatchers.any; @@ -60,19 +63,22 @@ import static org.mockito.BDDMockito.then; 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_ID = "dummy-rule-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 ruleNodeRef = new NodeRef(StoreRef.STORE_REF_WORKSPACE_SPACESSTORE, RULE_ID); + private static final Paging paging = Paging.DEFAULT; @Mock - private Nodes nodes; + private Nodes nodesMock; @Mock - private PermissionService permissionService; + private PermissionService permissionServiceMock; @Mock - private RuleService ruleService; + private RuleService ruleServiceMock; @InjectMocks private RulesImpl rules; @@ -83,111 +89,202 @@ public class RulesImplTest extends TestCase { MockitoAnnotations.openMocks(this); - given(nodes.validateNode(eq(FOLDER_NODE_ID))).willReturn(folderNodeRef); - given(nodes.validateNode(eq(RULE_SET_ID))).willReturn(ruleSetNodeRef); - given(nodes.nodeMatches(any(), any(), any())).willReturn(true); - given(permissionService.hasReadPermission(any())).willReturn(AccessStatus.ALLOWED); + given(nodesMock.validateOrLookupNode(eq(FOLDER_NODE_ID), any())).willReturn(folderNodeRef); + given(nodesMock.validateNode(eq(RULE_SET_ID))).willReturn(ruleSetNodeRef); + given(nodesMock.nodeMatches(any(), any(), any())).willReturn(true); + given(permissionServiceMock.hasReadPermission(any())).willReturn(AccessStatus.ALLOWED); } @Test public void testGetRules() { - final Paging paging = Paging.DEFAULT; - given(ruleService.isRuleSetAssociatedWithFolder(any(), any())).willReturn(true); + given(ruleServiceMock.isRuleSetAssociatedWithFolder(any(), any())).willReturn(true); + given(ruleServiceMock.getRules(any())).willReturn(List.of(createRule(RULE_ID))); // when final CollectionWithPagingInfo rulesPage = rules.getRules(FOLDER_NODE_ID, RULE_SET_ID, paging); - then(nodes).should().validateNode(eq(FOLDER_NODE_ID)); - then(nodes).should().validateNode(eq(RULE_SET_ID)); - then(nodes).should().nodeMatches(eq(folderNodeRef), any(), isNull()); - then(nodes).should().nodeMatches(eq(ruleSetNodeRef), any(), isNull()); - then(nodes).shouldHaveNoMoreInteractions(); - then(permissionService).should().hasReadPermission(eq(folderNodeRef)); - then(permissionService).should().hasReadPermission(eq(ruleSetNodeRef)); - then(permissionService).shouldHaveNoMoreInteractions(); - then(ruleService).should().isRuleSetAssociatedWithFolder(eq(ruleSetNodeRef), eq(folderNodeRef)); - then(ruleService).should().getRules(eq(folderNodeRef)); - then(ruleService).shouldHaveNoMoreInteractions(); + then(nodesMock).should().validateOrLookupNode(eq(FOLDER_NODE_ID), isNull()); + then(nodesMock).should().validateNode(eq(RULE_SET_ID)); + then(nodesMock).should().nodeMatches(eq(folderNodeRef), any(), isNull()); + then(nodesMock).should().nodeMatches(eq(ruleSetNodeRef), 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().getRules(eq(folderNodeRef)); + then(ruleServiceMock).shouldHaveNoMoreInteractions(); assertThat(rulesPage) .isNotNull() .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 public void testGetRulesForDefaultRuleSet() { final String defaultRuleSetId = "-default-"; - final Paging paging = Paging.DEFAULT; + given(ruleServiceMock.getRules(any())).willReturn(List.of(createRule(RULE_ID))); // when final CollectionWithPagingInfo rulesPage = rules.getRules(FOLDER_NODE_ID, defaultRuleSetId, paging); - then(nodes).should().validateNode(eq(FOLDER_NODE_ID)); - then(nodes).should().nodeMatches(eq(folderNodeRef), any(), isNull()); - then(nodes).shouldHaveNoMoreInteractions(); - then(permissionService).should().hasReadPermission(eq(folderNodeRef)); - then(permissionService).shouldHaveNoMoreInteractions(); - then(ruleService).should().getRules(eq(folderNodeRef)); - then(ruleService).shouldHaveNoMoreInteractions(); + then(nodesMock).should().validateOrLookupNode(eq(FOLDER_NODE_ID), isNull()); + then(nodesMock).should().nodeMatches(eq(folderNodeRef), any(), isNull()); + then(nodesMock).shouldHaveNoMoreInteractions(); + then(permissionServiceMock).should().hasReadPermission(eq(folderNodeRef)); + then(permissionServiceMock).shouldHaveNoMoreInteractions(); + then(ruleServiceMock).should().getRuleSetNode(eq(folderNodeRef)); + then(ruleServiceMock).should().getRules(eq(folderNodeRef)); + then(ruleServiceMock).shouldHaveNoMoreInteractions(); assertThat(rulesPage) - .isNotNull() + .isNotNull() .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 public void testGetRulesForNotExistingFolderNode() { - final Paging paging = Paging.DEFAULT; - given(nodes.nodeMatches(eq(folderNodeRef), any(), any())).willReturn(false); + given(nodesMock.nodeMatches(eq(folderNodeRef), any(), any())).willReturn(false); // when assertThatExceptionOfType(InvalidArgumentException.class).isThrownBy( () -> rules.getRules(FOLDER_NODE_ID, RULE_SET_ID, paging)); - then(ruleService).shouldHaveNoInteractions(); + then(ruleServiceMock).shouldHaveNoInteractions(); } @Test public void testGetRulesForNotExistingRuleSetNode() { - final Paging paging = Paging.DEFAULT; - given(nodes.nodeMatches(eq(folderNodeRef), any(), any())).willReturn(true); - given(nodes.nodeMatches(eq(ruleSetNodeRef), any(), any())).willReturn(false); + given(nodesMock.nodeMatches(eq(folderNodeRef), any(), any())).willReturn(true); + given(nodesMock.nodeMatches(eq(ruleSetNodeRef), any(), any())).willReturn(false); // when assertThatExceptionOfType(InvalidArgumentException.class).isThrownBy( () -> rules.getRules(FOLDER_NODE_ID, RULE_SET_ID, paging)); - then(ruleService).shouldHaveNoInteractions(); + then(ruleServiceMock).shouldHaveNoInteractions(); } @Test public void testGetRulesForNotAssociatedRuleSetToFolder() { - final Paging paging = Paging.DEFAULT; - given(ruleService.isRuleSetAssociatedWithFolder(any(), any())).willReturn(false); + given(ruleServiceMock.isRuleSetAssociatedWithFolder(any(), any())).willReturn(false); // when assertThatExceptionOfType(InvalidArgumentException.class).isThrownBy( () -> rules.getRules(FOLDER_NODE_ID, RULE_SET_ID, paging)); - then(ruleService).should().isRuleSetAssociatedWithFolder(eq(ruleSetNodeRef), eq(folderNodeRef)); - then(ruleService).shouldHaveNoMoreInteractions(); + then(ruleServiceMock).should().isRuleSetAssociatedWithFolder(eq(ruleSetNodeRef), eq(folderNodeRef)); + then(ruleServiceMock).shouldHaveNoMoreInteractions(); } @Test public void testGetRulesWithoutReadPermission() { - final Paging paging = Paging.DEFAULT; - given(permissionService.hasReadPermission(any())).willReturn(AccessStatus.DENIED); + given(permissionServiceMock.hasReadPermission(any())).willReturn(AccessStatus.DENIED); // when assertThatExceptionOfType(PermissionDeniedException.class).isThrownBy( () -> 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; } } \ No newline at end of file diff --git a/remote-api/src/test/java/org/alfresco/rest/api/nodes/NodeRulesRelationTest.java b/remote-api/src/test/java/org/alfresco/rest/api/nodes/NodeRulesRelationTest.java index 0c776ad7d5..6f0dff5332 100644 --- a/remote-api/src/test/java/org/alfresco/rest/api/nodes/NodeRulesRelationTest.java +++ b/remote-api/src/test/java/org/alfresco/rest/api/nodes/NodeRulesRelationTest.java @@ -30,7 +30,7 @@ import junit.framework.TestCase; import org.alfresco.rest.api.Rules; import org.alfresco.rest.framework.resource.parameters.Paging; 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.junit.Before; 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 RULE_SET_ID = "dummy-rule-set-id"; + private static final String RULE_ID = "dummy-rule-id"; @Mock - private Rules rules; + private Rules rulesMock; @InjectMocks private NodeRulesRelation nodeRulesRelation; @@ -68,13 +69,24 @@ public class NodeRulesRelationTest extends TestCase public void testReadAll() { final Paging paging = Paging.DEFAULT; - final Params.RecognizedParams params = new Params.RecognizedParams(null, paging, null, null, null, null, null, null, false); - final Parameters parameters = Params.valueOf(FOLDER_NODE_ID, RULE_SET_ID, params, null, null); + final Parameters parameters = ParamsExtender.valueOf(paging, FOLDER_NODE_ID, RULE_SET_ID, null); // when nodeRulesRelation.readAll(FOLDER_NODE_ID, parameters); - then(rules).should().getRules(eq(FOLDER_NODE_ID), eq(RULE_SET_ID), eq(paging)); - then(rules).shouldHaveNoMoreInteractions(); + then(rulesMock).should().getRules(eq(FOLDER_NODE_ID), eq(RULE_SET_ID), eq(paging)); + 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(); } } \ No newline at end of file diff --git a/remote-api/src/test/java/org/alfresco/rest/framework/tests/core/ParamsExtender.java b/remote-api/src/test/java/org/alfresco/rest/framework/tests/core/ParamsExtender.java index 2d6ba9229b..ce26c504d4 100755 --- a/remote-api/src/test/java/org/alfresco/rest/framework/tests/core/ParamsExtender.java +++ b/remote-api/src/test/java/org/alfresco/rest/framework/tests/core/ParamsExtender.java @@ -2,7 +2,7 @@ * #%L * 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. * 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 { - 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 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) { - 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) { - 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 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)); + } } 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 7cc3e777f8..0a108ab7e7 100644 --- a/repository/src/main/java/org/alfresco/repo/rule/RuleServiceImpl.java +++ b/repository/src/main/java/org/alfresco/repo/rule/RuleServiceImpl.java @@ -40,6 +40,7 @@ import org.alfresco.repo.security.authentication.AuthenticationUtil; import org.alfresco.repo.transaction.AlfrescoTransactionSupport; import org.alfresco.repo.transaction.TransactionListener; import org.alfresco.repo.transaction.TransactionalResourceHelper; +import org.alfresco.service.Experimental; import org.alfresco.service.cmr.action.Action; import org.alfresco.service.cmr.action.ActionService; import org.alfresco.service.cmr.action.ActionServiceException; @@ -1614,13 +1615,41 @@ public class RuleServiceImpl } @Override - public boolean isRuleSetAssociatedWithFolder(final NodeRef ruleSetNodeRef, final NodeRef folderNodeRef) { - return findAssociatedParents(ruleSetNodeRef, RuleModel.ASSOC_RULE_FOLDER).stream() - .map(ChildAssociationRef::getParentRef) - .anyMatch(folderNodeRef::equals); + @Experimental + public NodeRef getRuleSetNode(final NodeRef folderNodeRef) { + return getPrimaryChildNode(folderNodeRef, RuleModel.ASSOC_RULE_FOLDER); } - private List findAssociatedParents(final NodeRef nodeRef, final QNamePattern pattern) { - return runtimeNodeService.getParentAssocs(nodeRef, pattern, pattern); + private NodeRef getPrimaryChildNode(final NodeRef nodeRef, final QNamePattern associationType) { + 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 associations; + if (associationType == null) { + associations = runtimeNodeService.getParentAssocs(childNodeRef); + } else { + associations = runtimeNodeService.getParentAssocs(childNodeRef, associationType, associationType); + } + + return associations.stream() + .map(ChildAssociationRef::getParentRef) + .anyMatch(parentNodeRef::equals); } } 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 175c2e2347..36e09cdd74 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 @@ -28,6 +28,7 @@ package org.alfresco.service.cmr.rule; import java.util.List; import org.alfresco.service.Auditable; +import org.alfresco.service.Experimental; import org.alfresco.service.cmr.action.Action; import org.alfresco.service.cmr.repository.NodeRef; @@ -175,7 +176,7 @@ public interface RuleService * @return a list of the rules associated with the node */ @Auditable(parameters = {"nodeRef"}) - public List getRules(NodeRef nodeRef); + public List getRules(NodeRef nodeRef); /** * Get the rules associated with an actionable node. @@ -331,12 +332,34 @@ public interface RuleService public List 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 folderNodeRef - node reference of a folder * @return true if rule set is associated with folder */ - @Auditable(parameters = {"nodeRef"}) + @Auditable(parameters = {"ruleSetNodeRef", "folderNodeRef"}) + @Experimental 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); } diff --git a/repository/src/test/java/org/alfresco/repo/rule/RuleLinkTest.java b/repository/src/test/java/org/alfresco/repo/rule/RuleLinkTest.java index b1db17102d..d0d1c26d84 100644 --- a/repository/src/test/java/org/alfresco/repo/rule/RuleLinkTest.java +++ b/repository/src/test/java/org/alfresco/repo/rule/RuleLinkTest.java @@ -352,16 +352,24 @@ public class RuleLinkTest extends BaseSpringTest 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 public void testIsRuleSetAssociatedWithFolder() { final Rule rule = createTestRule(false, "luke"); this.ruleService.saveRule(folderOne, rule); - final NodeRef ruleSetNodeRef = nodeService.getChildAssocs(folderOne, RuleModel.ASSOC_RULE_FOLDER, RuleModel.ASSOC_RULE_FOLDER).stream() - .filter(ChildAssociationRef::isPrimary) - .map(ChildAssociationRef::getChildRef) - .findFirst() - .orElse(null); + final NodeRef ruleSetNodeRef = getRuleSetNode(folderOne); // when final boolean associated = ruleService.isRuleSetAssociatedWithFolder(ruleSetNodeRef, folderOne); @@ -374,11 +382,7 @@ public class RuleLinkTest extends BaseSpringTest { final Rule rule = createTestRule(false, "luke"); this.ruleService.saveRule(folderTwo , rule); - final NodeRef ruleSetNodeRef = nodeService.getChildAssocs(folderTwo, RuleModel.ASSOC_RULE_FOLDER, RuleModel.ASSOC_RULE_FOLDER).stream() - .filter(ChildAssociationRef::isPrimary) - .map(ChildAssociationRef::getChildRef) - .findFirst() - .orElse(null); + final NodeRef ruleSetNodeRef = getRuleSetNode(folderTwo); // when final boolean associated = ruleService.isRuleSetAssociatedWithFolder(ruleSetNodeRef, folderOne); @@ -386,6 +390,41 @@ public class RuleLinkTest extends BaseSpringTest assertFalse(associated); } + @Test + public void testIsRuleAssociatedWithRuleSet() + { + final Rule rule = createTestRule(false, "luke"); + this.ruleService.saveRule(folderOne, rule); + final NodeRef ruleSetNodeRef = getRuleSetNode(folderOne); + + List folderChildes = nodeService.getChildAssocs(folderOne); + List ruleSetChildes = nodeService.getChildAssocs(ruleSetNodeRef); + List 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) { // Rule properties @@ -416,5 +455,12 @@ public class RuleLinkTest extends BaseSpringTest 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); + } }