ACS-3525: API for manual triggering rules on a folder (#1458)

* ACS-3525: API for manual triggering rules on a folder
This commit is contained in:
krdabrowski
2022-10-05 12:19:49 +02:00
committed by GitHub
parent 8afd06a57f
commit 67f7fff0b7
12 changed files with 419 additions and 36 deletions

View File

@@ -29,6 +29,7 @@ package org.alfresco.rest.api;
import java.util.List;
import org.alfresco.rest.api.model.rules.Rule;
import org.alfresco.rest.api.model.rules.RuleExecution;
import org.alfresco.rest.framework.core.exceptions.InvalidArgumentException;
import org.alfresco.rest.framework.resource.parameters.CollectionWithPagingInfo;
import org.alfresco.rest.framework.resource.parameters.Paging;
@@ -65,10 +66,10 @@ public interface Rules
Rule getRuleById(String folderNodeId, String ruleSetId, String ruleId, List<String> includes);
/**
* Create new rules (and potentially a rule set if "_default_" is supplied).
* Create new rules (and potentially a rule set if "-default-" is supplied).
*
* @param folderNodeId The node id of a folder.
* @param ruleSetId The id of a rule set (or "_default_" to use/create the default rule set for the folder).
* @param ruleSetId The id of a rule set (or "-default-" to use/create the default rule set for the folder).
* @param rule The definition of the rule.
* @param includes The list of optional fields to include in the response.
* @return The newly created rules.
@@ -81,7 +82,7 @@ public interface Rules
* Update a rule.
*
* @param folderNodeId The id of a folder.
* @param ruleSetId The id of a rule set within the folder (or "_default_" to use the default rule set for the folder).
* @param ruleSetId The id of a rule set within the folder (or "-default-" to use the default rule set for the folder).
* @param ruleId The rule id.
* @param rule The new version of the rule.
* @param includes The list of optional fields to include in the response.
@@ -94,7 +95,16 @@ public interface Rules
*
* @param folderNodeId - folder node ID
* @param ruleSetId - rule set ID
* @param ruleId - rule ID *
* @param ruleId - rule ID
*/
void deleteRuleById(String folderNodeId, String ruleSetId, String ruleId);
/**
* Execute rules for given folder node.
*
* @param folderNodeId - the ID of a folder
* @param eachSubFolderIncluded - indicates if rules should be executed also on sub-folders
* @param eachInheritedRuleExecuted - indicates if the inherited rules should be also executed
*/
RuleExecution executeRules(final String folderNodeId, final boolean eachSubFolderIncluded, final boolean eachInheritedRuleExecuted);
}

View File

@@ -26,29 +26,30 @@
package org.alfresco.rest.api.impl.rules;
import java.io.Serializable;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import org.alfresco.rest.api.Nodes;
import org.alfresco.repo.action.ActionImpl;
import org.alfresco.repo.action.access.ActionAccessRestriction;
import org.alfresco.repo.action.executer.ExecuteAllRulesActionExecuter;
import org.alfresco.rest.api.Rules;
import org.alfresco.rest.api.model.mapper.RestModelMapper;
import org.alfresco.rest.api.model.rules.CompositeCondition;
import org.alfresco.rest.api.model.rules.Rule;
import org.alfresco.rest.api.model.rules.RuleExecution;
import org.alfresco.rest.api.model.rules.RuleSet;
import org.alfresco.rest.framework.core.exceptions.InvalidArgumentException;
import org.alfresco.rest.api.model.rules.SimpleCondition;
import org.alfresco.rest.framework.core.exceptions.InvalidArgumentException;
import org.alfresco.rest.framework.resource.parameters.CollectionWithPagingInfo;
import org.alfresco.rest.framework.resource.parameters.ListPage;
import org.alfresco.rest.framework.resource.parameters.Paging;
import org.alfresco.service.Experimental;
import org.alfresco.service.cmr.action.ActionCondition;
import org.alfresco.service.cmr.action.CompositeAction;
import org.alfresco.service.cmr.action.ActionService;
import org.alfresco.service.cmr.repository.NodeRef;
import org.alfresco.service.cmr.rule.RuleService;
import org.alfresco.util.collections.CollectionUtils;
import org.alfresco.service.namespace.NamespaceService;
import org.alfresco.util.GUID;
import org.apache.commons.collections.CollectionUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -58,6 +59,7 @@ public class RulesImpl implements Rules
private static final Logger LOGGER = LoggerFactory.getLogger(RulesImpl.class);
private static final String MUST_HAVE_AT_LEAST_ONE_ACTION = "A rule must have at least one action";
private ActionService actionService;
private RuleService ruleService;
private NodeValidator validator;
private RuleLoader ruleLoader;
@@ -130,6 +132,26 @@ public class RulesImpl implements Rules
ruleService.removeRule(folderNodeRef, rule);
}
@Override
public RuleExecution executeRules(final String folderNodeId, final boolean eachSubFolderIncluded, final boolean eachInheritedRuleExecuted)
{
final NodeRef folderNodeRef = validator.validateFolderNode(folderNodeId, false);
final Map<String, Serializable> parameterValues = new HashMap<>();
parameterValues.put(ExecuteAllRulesActionExecuter.PARAM_RUN_ALL_RULES_ON_CHILDREN, eachSubFolderIncluded);
parameterValues.put(ExecuteAllRulesActionExecuter.PARAM_EXECUTE_INHERITED_RULES, eachInheritedRuleExecuted);
final ActionImpl action = new ActionImpl(null, GUID.generate(), ExecuteAllRulesActionExecuter.NAME);
action.setNodeRef(folderNodeRef);
action.setParameterValues(parameterValues);
ActionAccessRestriction.setActionContext(action, ActionAccessRestriction.V1_ACTION_CONTEXT);
actionService.executeAction(action, folderNodeRef, true, false);
return RuleExecution.builder()
.eachSubFolderIncluded(eachSubFolderIncluded)
.eachInheritedRuleExecuted(eachInheritedRuleExecuted)
.create();
}
private org.alfresco.service.cmr.rule.Rule mapToServiceModelAndValidateActions(Rule rule)
{
if (CollectionUtils.isEmpty(rule.getActions()))
@@ -141,6 +163,11 @@ public class RulesImpl implements Rules
return actionPermissionValidator.validateRulePermissions(serviceModelRule);
}
public void setActionService(ActionService actionService)
{
this.actionService = actionService;
}
public void setRuleService(RuleService ruleService)
{
this.ruleService = ruleService;

View File

@@ -0,0 +1,111 @@
/*
* #%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 java.util.Objects;
import org.alfresco.service.Experimental;
@Experimental
public class RuleExecution
{
private boolean eachSubFolderIncluded;
private boolean eachInheritedRuleExecuted;
public boolean getIsEachSubFolderIncluded()
{
return eachSubFolderIncluded;
}
public void setIsEachSubFolderIncluded(boolean eachSubFolderIncluded)
{
this.eachSubFolderIncluded = eachSubFolderIncluded;
}
public boolean getIsEachInheritedRuleExecuted()
{
return eachInheritedRuleExecuted;
}
public void setIsEachInheritedRuleExecuted(boolean eachInheritedRuleExecuted)
{
this.eachInheritedRuleExecuted = eachInheritedRuleExecuted;
}
@Override
public String toString()
{
return "RuleExecution{" + "eachSubFolderIncluded=" + eachSubFolderIncluded + ", eachInheritedRuleExecuted=" + eachInheritedRuleExecuted + '}';
}
@Override
public boolean equals(Object o)
{
if (this == o)
return true;
if (o == null || getClass() != o.getClass())
return false;
RuleExecution that = (RuleExecution) o;
return eachSubFolderIncluded == that.eachSubFolderIncluded && eachInheritedRuleExecuted == that.eachInheritedRuleExecuted;
}
@Override
public int hashCode()
{
return Objects.hash(eachSubFolderIncluded, eachInheritedRuleExecuted);
}
public static Builder builder()
{
return new Builder();
}
public static class Builder
{
private boolean eachSubFolderIncluded;
private boolean eachInheritedRuleExecuted;
public Builder eachSubFolderIncluded(boolean eachSubFolderIncluded)
{
this.eachSubFolderIncluded = eachSubFolderIncluded;
return this;
}
public Builder eachInheritedRuleExecuted(boolean eachInheritedRuleExecuted)
{
this.eachInheritedRuleExecuted = eachInheritedRuleExecuted;
return this;
}
public RuleExecution create()
{
final RuleExecution ruleExecution = new RuleExecution();
ruleExecution.setIsEachSubFolderIncluded(eachSubFolderIncluded);
ruleExecution.setIsEachInheritedRuleExecuted(eachInheritedRuleExecuted);
return ruleExecution;
}
}
}

View File

@@ -0,0 +1,70 @@
/*
* #%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.nodes;
import java.util.List;
import org.alfresco.rest.api.Rules;
import org.alfresco.rest.api.model.rules.RuleExecution;
import org.alfresco.rest.framework.resource.RelationshipResource;
import org.alfresco.rest.framework.resource.actions.interfaces.RelationshipResourceAction;
import org.alfresco.rest.framework.resource.parameters.Parameters;
import org.alfresco.service.Experimental;
import org.alfresco.util.PropertyCheck;
import org.springframework.beans.factory.InitializingBean;
@Experimental
@RelationshipResource(name = "rule-executions", entityResource = NodesEntityResource.class, title = "Executing rules")
public class NodeRuleExecutionsRelation implements RelationshipResourceAction.Create<RuleExecution>, InitializingBean
{
private final Rules rules;
public NodeRuleExecutionsRelation(Rules rules)
{
this.rules = rules;
}
@Override
public void afterPropertiesSet() throws Exception
{
PropertyCheck.mandatory(this, "rules", this.rules);
}
/**
* Execute rules for given folder node.
*
* @param folderNodeId - the ID of a folder
* @param ruleExecutionParameters - rule execution parameters
* @param parameters - additional request parameters
* @return execution details
*/
@Override
public List<RuleExecution> create(String folderNodeId, List<RuleExecution> ruleExecutionParameters, Parameters parameters)
{
final RuleExecution ruleExecution = ruleExecutionParameters.stream().findFirst().orElse(new RuleExecution());
return List.of(rules.executeRules(folderNodeId, ruleExecution.getIsEachSubFolderIncluded(), ruleExecution.getIsEachInheritedRuleExecuted()));
}
}

View File

@@ -906,6 +906,7 @@
</bean>
<bean id="rules" class="org.alfresco.rest.api.impl.rules.RulesImpl">
<property name="actionService" ref="ActionService"/>
<property name="validator" ref="nodeValidator"/>
<property name="ruleService" ref="RuleService" />
<property name="ruleLoader" ref="ruleLoader"/>
@@ -927,6 +928,10 @@
<constructor-arg name="ruleSets" ref="RuleSets" />
</bean>
<bean class="org.alfresco.rest.api.nodes.NodeRuleExecutionsRelation">
<constructor-arg name="rules" ref="Rules" />
</bean>
<bean id="ruleSettings" class="org.alfresco.rest.api.impl.rules.RuleSettingsImpl">
<property name="validator" ref="nodeValidator" />
<property name="nodeService" ref="NodeService" />

View File

@@ -33,6 +33,7 @@ import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.BDDMockito.given;
import static org.mockito.BDDMockito.then;
import static org.mockito.Mockito.mock;
@@ -40,13 +41,18 @@ import static org.mockito.Mockito.mock;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.stream.IntStream;
import junit.framework.TestCase;
import org.alfresco.repo.action.ActionImpl;
import org.alfresco.repo.action.access.ActionAccessRestriction;
import org.alfresco.repo.action.executer.ExecuteAllRulesActionExecuter;
import org.alfresco.rest.api.Nodes;
import org.alfresco.rest.api.model.mapper.RestModelMapper;
import org.alfresco.rest.api.model.rules.Action;
import org.alfresco.rest.api.model.rules.Rule;
import org.alfresco.rest.api.model.rules.RuleExecution;
import org.alfresco.rest.framework.core.exceptions.EntityNotFoundException;
import org.alfresco.rest.framework.core.exceptions.InvalidArgumentException;
import org.alfresco.rest.framework.core.exceptions.PermissionDeniedException;
@@ -54,12 +60,14 @@ import org.alfresco.rest.framework.core.exceptions.RelationshipResourceNotFoundE
import org.alfresco.rest.framework.resource.parameters.CollectionWithPagingInfo;
import org.alfresco.rest.framework.resource.parameters.Paging;
import org.alfresco.service.Experimental;
import org.alfresco.service.cmr.action.ActionService;
import org.alfresco.service.cmr.repository.NodeRef;
import org.alfresco.service.cmr.repository.StoreRef;
import org.alfresco.service.cmr.rule.RuleService;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.Mockito;
@@ -77,10 +85,14 @@ public class RulesImplTest extends TestCase
private static final NodeRef RULE_NODE_REF = new NodeRef(StoreRef.STORE_REF_WORKSPACE_SPACESSTORE, RULE_ID);
private static final Paging PAGING = Paging.DEFAULT;
private static final List<String> INCLUDE = emptyList();
private static final boolean INCLUDE_SUB_FOLDERS = true;
private static final boolean EXECUTE_INHERITED_RULES = true;
@Mock
private Nodes nodesMock;
@Mock
private ActionService actionServiceMock;
@Mock
private RestModelMapper<Rule, org.alfresco.service.cmr.rule.Rule> ruleMapper;
@Mock
private NodeValidator nodeValidatorMock;
@@ -618,6 +630,39 @@ public class RulesImplTest extends TestCase
}
}
@Test
public void testExecuteRule()
{
// when
final RuleExecution actualRuleExecution = rules.executeRules(FOLDER_NODE_ID, INCLUDE_SUB_FOLDERS, EXECUTE_INHERITED_RULES);
final RuleExecution expectedRuleExecution = RuleExecution.builder()
.eachSubFolderIncluded(INCLUDE_SUB_FOLDERS)
.eachInheritedRuleExecuted(EXECUTE_INHERITED_RULES)
.create();
final ActionImpl expectedAction = new ActionImpl(null, null, ExecuteAllRulesActionExecuter.NAME);
expectedAction.setNodeRef(FOLDER_NODE_REF);
expectedAction.setParameterValues(Map.of(
ExecuteAllRulesActionExecuter.PARAM_RUN_ALL_RULES_ON_CHILDREN, INCLUDE_SUB_FOLDERS,
ExecuteAllRulesActionExecuter.PARAM_EXECUTE_INHERITED_RULES, EXECUTE_INHERITED_RULES,
ActionAccessRestriction.ACTION_CONTEXT_PARAM_NAME, ActionAccessRestriction.V1_ACTION_CONTEXT)
);
final ArgumentCaptor<ActionImpl> actionCaptor = ArgumentCaptor.forClass(ActionImpl.class);
then(nodeValidatorMock).should().validateFolderNode(FOLDER_NODE_ID, false);
then(nodeValidatorMock).shouldHaveNoMoreInteractions();
then(actionServiceMock).should().executeAction(actionCaptor.capture(), eq(FOLDER_NODE_REF), eq(true), eq(false));
then(actionServiceMock).shouldHaveNoMoreInteractions();
final ActionImpl actualAction = actionCaptor.getValue();
assertThat(actualAction)
.isNotNull()
.usingRecursiveComparison().ignoringFields("id")
.isEqualTo(expectedAction);
assertThat(actualRuleExecution)
.isNotNull()
.usingRecursiveComparison()
.isEqualTo(expectedRuleExecution);
}
private static org.alfresco.service.cmr.rule.Rule createRule(final String id)
{
final NodeRef nodeRef = new NodeRef(StoreRef.STORE_REF_WORKSPACE_SPACESSTORE, id);