diff --git a/remote-api/src/main/java/org/alfresco/rest/api/model/rules/CompositeCondition.java b/remote-api/src/main/java/org/alfresco/rest/api/model/rules/CompositeCondition.java new file mode 100644 index 0000000000..f33b862aa7 --- /dev/null +++ b/remote-api/src/main/java/org/alfresco/rest/api/model/rules/CompositeCondition.java @@ -0,0 +1,230 @@ +/* + * #%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 java.util.ArrayList; +import java.util.List; +import java.util.Objects; +import java.util.Optional; +import java.util.stream.Collectors; + +import org.alfresco.service.Experimental; +import org.alfresco.service.cmr.action.ActionCondition; +import org.apache.commons.collections.CollectionUtils; + +@Experimental +public class CompositeCondition +{ + private boolean inverted; + private ConditionOperator booleanMode = ConditionOperator.AND; + private List compositeConditions; + private List simpleConditions; + + /** + * Converts Action conditions (service POJO) list to composite condition (REST model). + * + * @param actionConditions - list of {@link ActionCondition} service POJOs + * @return {@link CompositeCondition} REST model + */ + public static CompositeCondition from(final List actionConditions) + { + if (actionConditions == null) + { + return null; + } + + final CompositeCondition conditions = new CompositeCondition(); + conditions.compositeConditions = new ArrayList<>(); + // group action conditions by inversion flag + actionConditions.stream().filter(Objects::nonNull).collect(Collectors.groupingBy(ActionCondition::getInvertCondition)) + // map action condition sub lists + .forEach((inverted, actionConditionsPart) -> Optional.ofNullable(CompositeCondition.ofActionConditions(actionConditionsPart, inverted, ConditionOperator.AND)) + // if composite condition present add to final list + .ifPresent(compositeCondition -> conditions.compositeConditions.add(compositeCondition))); + + if (conditions.compositeConditions.isEmpty()) { + conditions.compositeConditions = null; + } + + return conditions; + } + + private static CompositeCondition ofActionConditions(final List actionConditions, final boolean inverted, final ConditionOperator conditionOperator) + { + if (actionConditions == null) + { + return null; + } + + return ofSimpleConditions(SimpleCondition.listOf(actionConditions), inverted, conditionOperator); + } + + /** + * Creates a composite condition instance of simple conditions. + * + * @param simpleConditions - list of {@link SimpleCondition} + * @param inverted - determines if condition should be inverted + * @param conditionOperator - determines the operation, see {@link ConditionOperator} + * @return {@link CompositeCondition} + */ + public static CompositeCondition ofSimpleConditions(final List simpleConditions, final boolean inverted, final ConditionOperator conditionOperator) + { + return of(simpleConditions, null, inverted, conditionOperator); + } + + private static CompositeCondition of(final List simpleConditions, final List compositeConditions, + final boolean inverted, final ConditionOperator conditionOperator) + { + if (CollectionUtils.isEmpty(simpleConditions) && CollectionUtils.isEmpty(compositeConditions)) + { + return null; + } + + return builder() + .inverted(inverted) + .booleanMode(conditionOperator) + .simpleConditions(simpleConditions) + .compositeConditions(compositeConditions) + .create(); + } + + public boolean isInverted() + { + return inverted; + } + + public void setInverted(boolean inverted) + { + this.inverted = inverted; + } + + public String getBooleanMode() + { + if (booleanMode == null) + { + return null; + } + return booleanMode.name().toLowerCase(); + } + + public void setBooleanMode(ConditionOperator booleanMode) + { + this.booleanMode = booleanMode; + } + + public List getCompositeConditions() + { + return compositeConditions; + } + + public void setCompositeConditions(List compositeConditions) + { + this.compositeConditions = compositeConditions; + } + + public List getSimpleConditions() + { + return simpleConditions; + } + + public void setSimpleConditions(List simpleConditions) + { + this.simpleConditions = simpleConditions; + } + + @Override + public String toString() + { + return "CompositeCondition{" + "inverted=" + inverted + ", booleanMode=" + booleanMode + ", compositeConditions=" + compositeConditions + ", simpleConditions=" + + simpleConditions + '}'; + } + + @Override + public boolean equals(Object o) + { + if (this == o) + return true; + if (o == null || getClass() != o.getClass()) + return false; + CompositeCondition that = (CompositeCondition) o; + return inverted == that.inverted && booleanMode == that.booleanMode && Objects.equals(compositeConditions, that.compositeConditions) && Objects.equals( + simpleConditions, that.simpleConditions); + } + + @Override + public int hashCode() + { + return Objects.hash(inverted, booleanMode, compositeConditions, simpleConditions); + } + + public static Builder builder() + { + return new Builder(); + } + + public static class Builder + { + private boolean inverted; + private ConditionOperator booleanMode = ConditionOperator.AND; + private List compositeConditions; + private List simpleConditions; + + public Builder inverted(boolean inverted) + { + this.inverted = inverted; + return this; + } + + public Builder booleanMode(ConditionOperator booleanMode) + { + this.booleanMode = booleanMode; + return this; + } + + public Builder compositeConditions(List compositeConditions) + { + this.compositeConditions = compositeConditions; + return this; + } + + public Builder simpleConditions(List simpleConditions) + { + this.simpleConditions = simpleConditions; + return this; + } + + public CompositeCondition create() + { + final CompositeCondition condition = new CompositeCondition(); + condition.setInverted(inverted); + condition.setBooleanMode(booleanMode); + condition.setCompositeConditions(compositeConditions); + condition.setSimpleConditions(simpleConditions); + return condition; + } + } +} diff --git a/remote-api/src/main/java/org/alfresco/rest/api/model/rules/ConditionOperator.java b/remote-api/src/main/java/org/alfresco/rest/api/model/rules/ConditionOperator.java new file mode 100644 index 0000000000..a61d288339 --- /dev/null +++ b/remote-api/src/main/java/org/alfresco/rest/api/model/rules/ConditionOperator.java @@ -0,0 +1,35 @@ +/* + * #%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 enum ConditionOperator +{ + AND, OR +} diff --git a/remote-api/src/main/java/org/alfresco/rest/api/model/rules/Rule.java b/remote-api/src/main/java/org/alfresco/rest/api/model/rules/Rule.java index 61b4b5bd8c..31782fad6a 100644 --- a/remote-api/src/main/java/org/alfresco/rest/api/model/rules/Rule.java +++ b/remote-api/src/main/java/org/alfresco/rest/api/model/rules/Rule.java @@ -49,6 +49,7 @@ public class Rule private boolean shared; private String errorScript; private List triggers; + private CompositeCondition conditions; private List actions; /** @@ -81,6 +82,7 @@ public class Rule } if (ruleModel.getAction() != null) { + builder.conditions(CompositeCondition.from(ruleModel.getAction().getActionConditions())); if (ruleModel.getAction().getCompensatingAction() != null && ruleModel.getAction().getCompensatingAction().getParameterValue(ScriptActionExecuter.PARAM_SCRIPTREF) != null) { builder.errorScript(ruleModel.getAction().getCompensatingAction().getParameterValue(ScriptActionExecuter.PARAM_SCRIPTREF).toString()); @@ -193,9 +195,13 @@ public class Rule this.shared = shared; } - public List getTriggers() + public List getTriggers() { - return triggers; + if (triggers == null) + { + return null; + } + return triggers.stream().map(RuleTrigger::getValue).collect(Collectors.toList()); } public void setTriggers(List triggers) @@ -203,6 +209,16 @@ public class Rule this.triggers = triggers; } + public CompositeCondition getConditions() + { + return conditions; + } + + public void setConditions(CompositeCondition conditions) + { + this.conditions = conditions; + } + public List getActions() { return actions; @@ -217,7 +233,8 @@ public class Rule public String toString() { return "Rule{" + "id='" + id + '\'' + ", name='" + name + '\'' + ", description='" + description + '\'' + ", enabled=" + enabled + ", cascade=" + cascade - + ", asynchronous=" + asynchronous + ", shared=" + shared + ", errorScript='" + errorScript + '\'' + ", triggers=" + triggers + ", actions=" + actions + '}'; + + ", asynchronous=" + asynchronous + ", shared=" + shared + ", errorScript='" + errorScript + '\'' + ", triggers=" + triggers + ", conditions=" + conditions + + ", actions=" + actions + '}'; } @Override @@ -230,13 +247,13 @@ public class Rule Rule rule = (Rule) o; return enabled == rule.enabled && cascade == rule.cascade && asynchronous == rule.asynchronous && shared == rule.shared && Objects.equals(id, rule.id) && Objects.equals( name, rule.name) && Objects.equals(description, rule.description) && Objects.equals(errorScript, rule.errorScript) && Objects.equals(triggers, rule.triggers) - && Objects.equals(actions, rule.actions); + && Objects.equals(conditions, rule.conditions) && Objects.equals(actions, rule.actions); } @Override public int hashCode() { - return Objects.hash(id, name, description, enabled, cascade, asynchronous, shared, errorScript, triggers, actions); + return Objects.hash(id, name, description, enabled, cascade, asynchronous, shared, errorScript, triggers, conditions, actions); } public static Builder builder() @@ -256,6 +273,7 @@ public class Rule private boolean shared; private String errorScript; private List triggers; + private CompositeCondition conditions; private List actions; public Builder id(String id) @@ -312,6 +330,12 @@ public class Rule return this; } + public Builder conditions(CompositeCondition conditions) + { + this.conditions = conditions; + return this; + } + public Builder actions(List actions) { this.actions = actions; @@ -330,6 +354,7 @@ public class Rule rule.setShared(shared); rule.setErrorScript(errorScript); rule.setTriggers(triggers); + rule.setConditions(conditions); rule.setActions(actions); return rule; } diff --git a/remote-api/src/main/java/org/alfresco/rest/api/model/rules/SimpleCondition.java b/remote-api/src/main/java/org/alfresco/rest/api/model/rules/SimpleCondition.java new file mode 100644 index 0000000000..cdd26ddc2b --- /dev/null +++ b/remote-api/src/main/java/org/alfresco/rest/api/model/rules/SimpleCondition.java @@ -0,0 +1,277 @@ +/* + * #%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 java.util.List; +import java.util.Objects; +import java.util.stream.Collectors; + +import org.alfresco.repo.action.evaluator.CompareMimeTypeEvaluator; +import org.alfresco.repo.action.evaluator.ComparePropertyValueEvaluator; +import org.alfresco.repo.action.evaluator.HasAspectEvaluator; +import org.alfresco.repo.action.evaluator.HasChildEvaluator; +import org.alfresco.repo.action.evaluator.HasTagEvaluator; +import org.alfresco.repo.action.evaluator.HasVersionHistoryEvaluator; +import org.alfresco.repo.action.evaluator.InCategoryEvaluator; +import org.alfresco.repo.action.evaluator.IsSubTypeEvaluator; +import org.alfresco.repo.action.evaluator.NoConditionEvaluator; +import org.alfresco.service.Experimental; +import org.alfresco.service.cmr.action.ActionCondition; +import org.apache.commons.collections.CollectionUtils; + +@Experimental +public class SimpleCondition +{ + private static final String COMPARATOR_EQUALS = "equals"; + + private String field; + private String comparator; + private String parameter; + + /** + * Converts list of service POJO action conditions to list of REST model simple conditions. + * + * @param actionConditions - list of {@link ActionCondition} service POJOs + * @return list of {@link SimpleCondition} REST models + */ + public static List listOf(final List actionConditions) + { + if (CollectionUtils.isEmpty(actionConditions)) + { + return null; + } + + return actionConditions.stream() + .map(SimpleCondition::from) + .filter(Objects::nonNull) + .collect(Collectors.toList()); + } + + /** + * Creates simple condition REST model instance from service POJO action condition. + * + * @param actionCondition - {@link ActionCondition} service POJO + * @return {@link SimpleCondition} REST model + */ + public static SimpleCondition from(final ActionCondition actionCondition) + { + if (actionCondition == null || actionCondition.getActionConditionDefinitionName() == null || actionCondition.getParameterValues() == null) + { + return null; + } + + switch (actionCondition.getActionConditionDefinitionName()) + { + case ComparePropertyValueEvaluator.NAME: + return createComparePropertyValueCondition(actionCondition); + case CompareMimeTypeEvaluator.NAME: + return createCompareMimeTypeCondition(actionCondition); + case HasAspectEvaluator.NAME: + return createHasAspectCondition(actionCondition); + case HasChildEvaluator.NAME: + return createHasChildCondition(actionCondition); + case HasTagEvaluator.NAME: + return createHasTagCondition(actionCondition); + case HasVersionHistoryEvaluator.NAME: + return createHasVersionHistoryCondition(actionCondition); + case InCategoryEvaluator.NAME: + return createInCategoryCondition(actionCondition); + case IsSubTypeEvaluator.NAME: + return createIsSubtypeCondition(actionCondition); + case NoConditionEvaluator.NAME: + default: + return null; + } + } + + public String getField() + { + return field; + } + + public void setField(String field) + { + this.field = field; + } + + public String getComparator() + { + return comparator; + } + + public void setComparator(String comparator) + { + this.comparator = comparator; + } + + public String getParameter() + { + return parameter; + } + + public void setParameter(String parameter) + { + this.parameter = parameter; + } + + @Override + public String toString() + { + return "SimpleCondition{" + "field='" + field + '\'' + ", comparator='" + comparator + '\'' + ", parameter='" + parameter + '\'' + '}'; + } + + @Override + public boolean equals(Object o) + { + if (this == o) + return true; + if (o == null || getClass() != o.getClass()) + return false; + SimpleCondition that = (SimpleCondition) o; + return Objects.equals(field, that.field) && Objects.equals(comparator, that.comparator) && Objects.equals(parameter, that.parameter); + } + + @Override + public int hashCode() + { + return Objects.hash(field, comparator, parameter); + } + + private static SimpleCondition createComparePropertyValueCondition(final ActionCondition actionCondition) { + final SimpleCondition.Builder builder = builder(); + if (actionCondition.getParameterValues().get(ComparePropertyValueEvaluator.PARAM_CONTENT_PROPERTY) != null) + { + builder.field(actionCondition.getParameterValues().get(ComparePropertyValueEvaluator.PARAM_CONTENT_PROPERTY).toString().toLowerCase()); + } else { + builder.field(actionCondition.getParameterValues().get(ComparePropertyValueEvaluator.PARAM_PROPERTY).toString().toLowerCase()); + } + return builder + .comparator(actionCondition.getParameterValues().get(ComparePropertyValueEvaluator.PARAM_OPERATION).toString().toLowerCase()) + .parameter(actionCondition.getParameterValues().get(ComparePropertyValueEvaluator.PARAM_VALUE).toString()) + .create(); + } + + private static SimpleCondition createCompareMimeTypeCondition(final ActionCondition actionCondition) { + return builder() + .field(actionCondition.getParameterValues().get(ComparePropertyValueEvaluator.PARAM_PROPERTY).toString().toLowerCase()) + .comparator(COMPARATOR_EQUALS) + .parameter(actionCondition.getParameterValues().get(ComparePropertyValueEvaluator.PARAM_VALUE).toString()) + .create(); + } + + private static SimpleCondition createHasAspectCondition(final ActionCondition actionCondition) { + return builder() + .field(HasAspectEvaluator.PARAM_ASPECT) + .comparator(COMPARATOR_EQUALS) + .parameter(actionCondition.getParameterValues().get(HasAspectEvaluator.PARAM_ASPECT).toString()) + .create(); + } + + private static SimpleCondition createHasChildCondition(final ActionCondition actionCondition) { + final SimpleCondition.Builder builder = builder(); + if (actionCondition.getParameterValues().get(HasChildEvaluator.PARAM_ASSOC_TYPE) != null) + { + builder.field(actionCondition.getParameterValues().get(HasChildEvaluator.PARAM_ASSOC_TYPE).toString().toLowerCase()); + } else { + builder.field(actionCondition.getParameterValues().get(HasChildEvaluator.PARAM_ASSOC_NAME).toString().toLowerCase()); + } + return builder + .comparator(COMPARATOR_EQUALS) + .parameter(actionCondition.getParameterValues().get(ComparePropertyValueEvaluator.PARAM_VALUE).toString()) + .create(); + } + + private static SimpleCondition createHasTagCondition(final ActionCondition actionCondition) { + return builder() + .field(HasTagEvaluator.PARAM_TAG) + .comparator(COMPARATOR_EQUALS) + .parameter(actionCondition.getParameterValues().get(HasTagEvaluator.PARAM_TAG).toString()) + .create(); + } + + private static SimpleCondition createHasVersionHistoryCondition(final ActionCondition actionCondition) { + return builder() + .field(actionCondition.getParameterValues().get(ComparePropertyValueEvaluator.PARAM_PROPERTY).toString().toLowerCase()) + .comparator(actionCondition.getParameterValues().get(ComparePropertyValueEvaluator.PARAM_OPERATION).toString().toLowerCase()) + .parameter(actionCondition.getParameterValues().get(ComparePropertyValueEvaluator.PARAM_VALUE).toString()) + .create(); + } + + private static SimpleCondition createInCategoryCondition(final ActionCondition actionCondition) { + return builder() + .field(actionCondition.getParameterValues().get(InCategoryEvaluator.PARAM_CATEGORY_ASPECT).toString().toLowerCase()) + .comparator(COMPARATOR_EQUALS) + .parameter(actionCondition.getParameterValues().get(InCategoryEvaluator.PARAM_CATEGORY_VALUE).toString()) + .create(); + } + + private static SimpleCondition createIsSubtypeCondition(final ActionCondition actionCondition) { + return builder() + .field(IsSubTypeEvaluator.PARAM_TYPE) + .comparator(COMPARATOR_EQUALS) + .parameter(actionCondition.getParameterValues().get(IsSubTypeEvaluator.PARAM_TYPE).toString()) + .create(); + } + + public static Builder builder() + { + return new Builder(); + } + + public static class Builder + { + private String field; + private String comparator; + private String parameter; + + public Builder field(String field) + { + this.field = field; + return this; + } + + public Builder comparator(String comparator) + { + this.comparator = comparator; + return this; + } + + public Builder parameter(String parameter) + { + this.parameter = parameter; + return this; + } + + public SimpleCondition create() { + final SimpleCondition condition = new SimpleCondition(); + condition.setField(field); + condition.setComparator(comparator); + condition.setParameter(parameter); + return condition; + } + } +} diff --git a/remote-api/src/test/java/org/alfresco/rest/api/RulesUnitTests.java b/remote-api/src/test/java/org/alfresco/rest/api/RulesUnitTests.java index 3b199328d4..cccc62de9b 100644 --- a/remote-api/src/test/java/org/alfresco/rest/api/RulesUnitTests.java +++ b/remote-api/src/test/java/org/alfresco/rest/api/RulesUnitTests.java @@ -28,7 +28,9 @@ package org.alfresco.rest.api; import org.alfresco.rest.api.impl.RulesImplTest; import org.alfresco.rest.api.model.rules.ActionTest; +import org.alfresco.rest.api.model.rules.CompositeConditionTest; import org.alfresco.rest.api.model.rules.RuleTest; +import org.alfresco.rest.api.model.rules.SimpleConditionTest; import org.alfresco.rest.api.nodes.NodeRulesRelationTest; import org.alfresco.service.Experimental; import org.junit.runner.RunWith; @@ -40,7 +42,9 @@ import org.junit.runners.Suite; NodeRulesRelationTest.class, RulesImplTest.class, RuleTest.class, - ActionTest.class + ActionTest.class, + SimpleConditionTest.class, + CompositeConditionTest.class }) public class RulesUnitTests { 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 db64a7d9f1..a2fd5308eb 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 @@ -49,6 +49,7 @@ import java.util.List; import junit.framework.TestCase; import org.alfresco.repo.action.ActionImpl; import org.alfresco.rest.api.Nodes; +import org.alfresco.rest.api.model.rules.CompositeCondition; import org.alfresco.rest.api.model.rules.Rule; import org.alfresco.rest.framework.core.exceptions.EntityNotFoundException; import org.alfresco.rest.framework.core.exceptions.InvalidArgumentException; @@ -463,10 +464,13 @@ public class RulesImplTest extends TestCase then(ruleServiceMock).should().saveRule(folderNodeRef, serviceRuleBody); then(ruleServiceMock).shouldHaveNoMoreInteractions(); + Rule expected = Rule.builder().id(RULE_ID) .enabled(true) .shared(true) - .triggers(emptyList()).create(); + .triggers(emptyList()) + .conditions(CompositeCondition.builder().inverted(false).create()) + .create(); assertThat(updatedRule).isEqualTo(expected); } diff --git a/remote-api/src/test/java/org/alfresco/rest/api/model/rules/CompositeConditionTest.java b/remote-api/src/test/java/org/alfresco/rest/api/model/rules/CompositeConditionTest.java new file mode 100644 index 0000000000..427c208848 --- /dev/null +++ b/remote-api/src/test/java/org/alfresco/rest/api/model/rules/CompositeConditionTest.java @@ -0,0 +1,180 @@ +/* + * #%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 static org.assertj.core.api.Assertions.assertThat; + +import java.io.Serializable; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.alfresco.repo.action.ActionConditionImpl; +import org.alfresco.repo.action.evaluator.ComparePropertyValueEvaluator; +import org.alfresco.service.Experimental; +import org.alfresco.service.cmr.action.ActionCondition; +import org.junit.Test; + +@Experimental +public class CompositeConditionTest +{ + + @Test + public void testFrom() + { + final List actionConditions = List.of( + createActionCondition("value1"), + createActionCondition("value2", true), + createActionCondition("value3") + ); + final CompositeCondition expectedCompositeCondition = createCompositeCondition(List.of( + createCompositeCondition(false, List.of( + createSimpleCondition("value1"), + createSimpleCondition("value3") + )), + createCompositeCondition(true, List.of( + createSimpleCondition("value2") + )) + )); + + // when + final CompositeCondition actualCompositeCondition = CompositeCondition.from(actionConditions); + + assertThat(actualCompositeCondition).isNotNull().usingRecursiveComparison().isEqualTo(expectedCompositeCondition); + } + + @Test + public void testFromEmptyList() + { + final List actionConditions = Collections.emptyList(); + final CompositeCondition expectedCompositeCondition = CompositeCondition.builder().create(); + + // when + final CompositeCondition actualCompositeCondition = CompositeCondition.from(actionConditions); + + assertThat(actualCompositeCondition).isNotNull().usingRecursiveComparison().isEqualTo(expectedCompositeCondition); + } + + @Test + public void testFromNullValue() + { + // when + final CompositeCondition actualCompositeCondition = CompositeCondition.from(null); + + assertThat(actualCompositeCondition).isNull(); + } + + @Test + public void testFromListContainingNull() + { + final List actionConditions = new ArrayList<>(); + actionConditions.add(null); + final CompositeCondition expectedCompositeCondition = CompositeCondition.builder().create(); + + // when + final CompositeCondition actualCompositeCondition = CompositeCondition.from(actionConditions); + + assertThat(actualCompositeCondition).isNotNull().usingRecursiveComparison().isEqualTo(expectedCompositeCondition); + } + + @Test + public void testOfSimpleConditions() + { + final List simpleConditions = List.of(SimpleCondition.builder().field("field").comparator("comparator").parameter("param").create()); + final boolean inverted = true; + final ConditionOperator conditionOperator = ConditionOperator.OR; + final CompositeCondition expectedCondition = createCompositeCondition(inverted, conditionOperator, null, simpleConditions); + + // when + final CompositeCondition actualCompositeCondition = CompositeCondition.ofSimpleConditions(simpleConditions, inverted, conditionOperator); + + assertThat(actualCompositeCondition).isNotNull().usingRecursiveComparison().isEqualTo(expectedCondition); + } + + @Test + public void testOfEmptySimpleConditions() + { + // when + final CompositeCondition actualCompositeCondition = CompositeCondition.ofSimpleConditions(Collections.emptyList(), false, ConditionOperator.AND); + + assertThat(actualCompositeCondition).isNull(); + } + + @Test + public void testOfNullSimpleConditions() + { + // when + final CompositeCondition actualCompositeCondition = CompositeCondition.ofSimpleConditions(null, false, ConditionOperator.AND); + + assertThat(actualCompositeCondition).isNull(); + } + + private static ActionCondition createActionCondition(final String value) + { + return createActionCondition(value, false); + } + + private static ActionCondition createActionCondition(final String value, final boolean inverted) + { + final ActionCondition actionCondition = new ActionConditionImpl("fake-id", ComparePropertyValueEvaluator.NAME); + actionCondition.setInvertCondition(inverted); + final Map parameterValues = new HashMap<>(); + parameterValues.put(ComparePropertyValueEvaluator.PARAM_CONTENT_PROPERTY, "content-property"); + parameterValues.put(ComparePropertyValueEvaluator.PARAM_OPERATION, "operation"); + parameterValues.put(ComparePropertyValueEvaluator.PARAM_VALUE, value); + actionCondition.setParameterValues(parameterValues); + return actionCondition; + } + + private static SimpleCondition createSimpleCondition(final String value) { + return SimpleCondition.builder() + .field("content-property") + .comparator("operation") + .parameter(value) + .create(); + } + + private static CompositeCondition createCompositeCondition(final List compositeConditions) { + return createCompositeCondition(false, ConditionOperator.AND, compositeConditions, null); + } + + private static CompositeCondition createCompositeCondition(final boolean inverted, final List simpleConditions) { + return createCompositeCondition(inverted, ConditionOperator.AND, null, simpleConditions); + } + + private static CompositeCondition createCompositeCondition(final boolean inverted, final ConditionOperator conditionOperator, + final List compositeConditions, final List simpleConditions) { + return CompositeCondition.builder() + .inverted(inverted) + .booleanMode(conditionOperator) + .compositeConditions(compositeConditions) + .simpleConditions(simpleConditions) + .create(); + } +} \ No newline at end of file diff --git a/remote-api/src/test/java/org/alfresco/rest/api/model/rules/RuleTest.java b/remote-api/src/test/java/org/alfresco/rest/api/model/rules/RuleTest.java index 98e3c8c5f2..a21cfe575f 100644 --- a/remote-api/src/test/java/org/alfresco/rest/api/model/rules/RuleTest.java +++ b/remote-api/src/test/java/org/alfresco/rest/api/model/rules/RuleTest.java @@ -28,6 +28,7 @@ package org.alfresco.rest.api.model.rules; import static org.assertj.core.api.Assertions.assertThat; +import java.util.Collections; import java.util.List; import org.alfresco.repo.action.ActionConditionImpl; @@ -110,6 +111,7 @@ public class RuleTest .shared(RULE_SHARED) .triggers(List.of(RuleTrigger.INBOUND, RuleTrigger.UPDATE)) .errorScript(ERROR_SCRIPT) + .conditions(CompositeCondition.from(Collections.emptyList())) .create(); } } \ No newline at end of file diff --git a/remote-api/src/test/java/org/alfresco/rest/api/model/rules/SimpleConditionTest.java b/remote-api/src/test/java/org/alfresco/rest/api/model/rules/SimpleConditionTest.java new file mode 100644 index 0000000000..9c0de39112 --- /dev/null +++ b/remote-api/src/test/java/org/alfresco/rest/api/model/rules/SimpleConditionTest.java @@ -0,0 +1,213 @@ +/* + * #%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 static org.assertj.core.api.Assertions.assertThat; + +import java.io.Serializable; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; + +import org.alfresco.repo.action.ActionConditionImpl; +import org.alfresco.repo.action.evaluator.CompareMimeTypeEvaluator; +import org.alfresco.repo.action.evaluator.ComparePropertyValueEvaluator; +import org.alfresco.repo.action.evaluator.HasAspectEvaluator; +import org.alfresco.repo.action.evaluator.HasChildEvaluator; +import org.alfresco.repo.action.evaluator.HasTagEvaluator; +import org.alfresco.repo.action.evaluator.HasVersionHistoryEvaluator; +import org.alfresco.repo.action.evaluator.InCategoryEvaluator; +import org.alfresco.repo.action.evaluator.IsSubTypeEvaluator; +import org.alfresco.repo.action.evaluator.NoConditionEvaluator; +import org.alfresco.service.Experimental; +import org.alfresco.service.cmr.action.ActionCondition; +import org.junit.Test; + +@Experimental +public class SimpleConditionTest +{ + + private static List getTestData() { + return List.of( + TestData.of(ComparePropertyValueEvaluator.NAME), + TestData.of(CompareMimeTypeEvaluator.NAME), + TestData.of(HasAspectEvaluator.NAME), + TestData.of(HasChildEvaluator.NAME), + TestData.of(HasTagEvaluator.NAME), + TestData.of(HasVersionHistoryEvaluator.NAME), + TestData.of(InCategoryEvaluator.NAME), + TestData.of(IsSubTypeEvaluator.NAME), + TestData.of(NoConditionEvaluator.NAME, true), + TestData.of("fake-definition-name", true), + TestData.of("", true), + TestData.of(null, true) + ); + } + + @Test + public void testFrom() + { + for (TestData testData : getTestData()) + { + final ActionCondition actionCondition = createActionCondition(testData.actionDefinitionName); + + // when + final SimpleCondition actualSimpleCondition = SimpleCondition.from(actionCondition); + + assertThat(Objects.isNull(actualSimpleCondition)).isEqualTo(testData.isNullResult); + if (!testData.isNullResult) + { + assertThat(actualSimpleCondition.getField()).isNotEmpty(); + assertThat(actualSimpleCondition.getComparator()).isNotEmpty(); + assertThat(actualSimpleCondition.getParameter()).isNotEmpty(); + } + } + } + + @Test + public void testFromNullValue() + { + // when + final SimpleCondition actualSimpleCondition = SimpleCondition.from(null); + + assertThat(actualSimpleCondition).isNull(); + } + + @Test + public void testFromActionConditionWithoutDefinitionName() + { + final ActionCondition actionCondition = new ActionConditionImpl("fake-id", null, createParameterValues()); + + // when + final SimpleCondition actualSimpleCondition = SimpleCondition.from(actionCondition); + + assertThat(actualSimpleCondition).isNull(); + } + + @Test + public void testFromActionConditionWithoutParameterValues() + { + final ActionCondition actionCondition = new ActionConditionImpl("fake-id", "fake-def-name", null); + + // when + final SimpleCondition actualSimpleCondition = SimpleCondition.from(actionCondition); + + assertThat(actualSimpleCondition).isNull(); + } + + @Test + public void testListOf() + { + final List actionConditions = List.of( + createActionCondition(ComparePropertyValueEvaluator.NAME), + createActionCondition(CompareMimeTypeEvaluator.NAME) + ); + final List expectedSimpleConditions = List.of( + SimpleCondition.builder().field("content-property").comparator("operation").parameter("value").create(), + SimpleCondition.builder().field("property").comparator("equals").parameter("value").create() + ); + + // when + final List actualSimpleConditions = SimpleCondition.listOf(actionConditions); + + assertThat(actualSimpleConditions) + .isNotNull() + .containsExactlyElementsOf(expectedSimpleConditions); + } + + @Test + public void testListOfEmptyActionConditions() + { + final List actualSimpleConditions = SimpleCondition.listOf(Collections.emptyList()); + + assertThat(actualSimpleConditions).isNull(); + } + + @Test + public void testListOfNullActionConditions() + { + final List actualSimpleConditions = SimpleCondition.listOf(null); + + assertThat(actualSimpleConditions).isNull(); + } + + @Test + public void testListOfActionConditionsContainingNull() + { + final List actionConditions = new ArrayList<>(); + actionConditions.add(null); + + final List actualSimpleConditions = SimpleCondition.listOf(actionConditions); + + assertThat(actualSimpleConditions).isNotNull().isEmpty(); + } + + private static ActionCondition createActionCondition(final String actionDefinitionName) + { + return new ActionConditionImpl("fake-id", actionDefinitionName, createParameterValues()); + } + + private static Map createParameterValues() { + final Map parameterValues = new HashMap<>(); + parameterValues.put(ComparePropertyValueEvaluator.PARAM_CONTENT_PROPERTY, "content-property"); + parameterValues.put(HasChildEvaluator.PARAM_ASSOC_TYPE, "assoc-type"); + parameterValues.put(ComparePropertyValueEvaluator.PARAM_PROPERTY, "property"); + parameterValues.put(ComparePropertyValueEvaluator.PARAM_OPERATION, "operation"); + parameterValues.put(ComparePropertyValueEvaluator.PARAM_VALUE, "value"); + parameterValues.put(HasAspectEvaluator.PARAM_ASPECT, "aspect"); + parameterValues.put(HasChildEvaluator.PARAM_ASSOC_NAME, "assoc-name"); + parameterValues.put(HasTagEvaluator.PARAM_TAG, "tag"); + parameterValues.put(InCategoryEvaluator.PARAM_CATEGORY_ASPECT, "category-aspect"); + parameterValues.put(InCategoryEvaluator.PARAM_CATEGORY_VALUE, "category-value"); + parameterValues.put(IsSubTypeEvaluator.PARAM_TYPE, "type"); + + return parameterValues; + } + + private static class TestData + { + String actionDefinitionName; + boolean isNullResult; + + public TestData(String actionDefinitionName, boolean isNullResult) + { + this.actionDefinitionName = actionDefinitionName; + this.isNullResult = isNullResult; + } + + public static TestData of(String actionDefinitionName) { + return new TestData(actionDefinitionName, false); + } + + public static TestData of(String actionDefinitionName, boolean isNullResult) { + return new TestData(actionDefinitionName, isNullResult); + } + } +} \ No newline at end of file diff --git a/repository/src/test/java/org/alfresco/repo/rule/RuleServiceImplUnitTest.java b/repository/src/test/java/org/alfresco/repo/rule/RuleServiceImplUnitTest.java index 0c660bc4af..4403ac8e01 100644 --- a/repository/src/test/java/org/alfresco/repo/rule/RuleServiceImplUnitTest.java +++ b/repository/src/test/java/org/alfresco/repo/rule/RuleServiceImplUnitTest.java @@ -31,10 +31,12 @@ import static org.alfresco.repo.rule.RuleModel.ASSOC_RULE_FOLDER; import static org.alfresco.repo.rule.RuleModel.TYPE_RULE; import static org.alfresco.service.cmr.security.AccessStatus.ALLOWED; import static org.alfresco.service.cmr.security.AccessStatus.DENIED; +import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatExceptionOfType; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.ArgumentMatchers.nullable; +import static org.mockito.BDDMockito.given; import static org.mockito.BDDMockito.then; import static org.mockito.Mockito.atLeastOnce; import static org.mockito.Mockito.mock; @@ -44,6 +46,7 @@ import static org.mockito.Mockito.when; import static org.mockito.MockitoAnnotations.openMocks; import java.io.Serializable; +import java.util.Collections; import java.util.List; import org.alfresco.repo.action.RuntimeActionService; @@ -187,4 +190,170 @@ public class RuleServiceImplUnitTest assertThatExceptionOfType(RuleServiceException.class).isThrownBy(() -> ruleService.saveRule(FOLDER_NODE, mockRule)); } + + @Test + public void testGetRuleSetNode() + { + given(runtimeNodeService.getChildAssocs(any(), any(), any())).willReturn(List.of(createAssociation(FOLDER_NODE, RULE_SET_NODE))); + + // when + final NodeRef actualNode = ruleService.getRuleSetNode(FOLDER_NODE); + + then(runtimeNodeService).should().getChildAssocs(FOLDER_NODE, ASSOC_RULE_FOLDER, ASSOC_RULE_FOLDER); + then(runtimeNodeService).shouldHaveNoMoreInteractions(); + then(nodeService).shouldHaveNoInteractions(); + assertThat(actualNode).isNotNull(); + } + + @Test + public void testGetRuleSetNode_emptyAssociation() + { + given(runtimeNodeService.getChildAssocs(any(), any(), any())).willReturn(Collections.emptyList()); + + // when + final NodeRef actualNode = ruleService.getRuleSetNode(FOLDER_NODE); + + then(runtimeNodeService).should().getChildAssocs(FOLDER_NODE, ASSOC_RULE_FOLDER, ASSOC_RULE_FOLDER); + then(runtimeNodeService).shouldHaveNoMoreInteractions(); + then(nodeService).shouldHaveNoInteractions(); + assertThat(actualNode).isNull(); + } + + @Test + public void testGetRuleSetNode_notPrimaryAssociation() + { + given(runtimeNodeService.getChildAssocs(any(), any(), any())).willReturn(List.of(createAssociation(FOLDER_NODE, RULE_SET_NODE, false))); + + // when + final NodeRef actualNode = ruleService.getRuleSetNode(FOLDER_NODE); + + then(runtimeNodeService).should().getChildAssocs(FOLDER_NODE, ASSOC_RULE_FOLDER, ASSOC_RULE_FOLDER); + then(runtimeNodeService).shouldHaveNoMoreInteractions(); + then(nodeService).shouldHaveNoInteractions(); + assertThat(actualNode).isNotNull(); + } + + @Test + public void testIsRuleSetAssociatedWithFolder() + { + given(runtimeNodeService.getParentAssocs(any(), any(), any())).willReturn(List.of(createAssociation(FOLDER_NODE, RULE_SET_NODE))); + + // when + boolean associated = ruleService.isRuleSetAssociatedWithFolder(RULE_SET_NODE, FOLDER_NODE); + + then(runtimeNodeService).should().getParentAssocs(RULE_SET_NODE, ASSOC_RULE_FOLDER, ASSOC_RULE_FOLDER); + then(runtimeNodeService).shouldHaveNoMoreInteractions(); + then(nodeService).shouldHaveNoInteractions(); + assertThat(associated).isTrue(); + } + + @Test + public void testIsRuleSetAssociatedWithFolder_emptyAssociation() + { + given(runtimeNodeService.getParentAssocs(any(), any(), any())).willReturn(Collections.emptyList()); + + // when + boolean associated = ruleService.isRuleSetAssociatedWithFolder(RULE_SET_NODE, FOLDER_NODE); + + then(runtimeNodeService).should().getParentAssocs(RULE_SET_NODE, ASSOC_RULE_FOLDER, ASSOC_RULE_FOLDER); + then(runtimeNodeService).shouldHaveNoMoreInteractions(); + then(nodeService).shouldHaveNoInteractions(); + assertThat(associated).isFalse(); + } + + @Test + public void testIsRuleSetAssociatedWithFolder_improperAssociation() + { + final NodeRef fakeFolderNode = new NodeRef("folder://node/fake"); + given(runtimeNodeService.getParentAssocs(any(), any(), any())).willReturn(List.of(createAssociation(fakeFolderNode, RULE_SET_NODE))); + + // when + boolean associated = ruleService.isRuleSetAssociatedWithFolder(RULE_SET_NODE, FOLDER_NODE); + + then(runtimeNodeService).should().getParentAssocs(RULE_SET_NODE, ASSOC_RULE_FOLDER, ASSOC_RULE_FOLDER); + then(runtimeNodeService).shouldHaveNoMoreInteractions(); + then(nodeService).shouldHaveNoInteractions(); + assertThat(associated).isFalse(); + } + + @Test + public void testIsRuleAssociatedWithRuleSet() + { + given(runtimeNodeService.getParentAssocs(any())).willReturn(List.of(createAssociation(RULE_SET_NODE, RULE_NODE))); + + // when + boolean associated = ruleService.isRuleAssociatedWithRuleSet(RULE_NODE, RULE_SET_NODE); + + then(runtimeNodeService).should().getParentAssocs(RULE_NODE); + then(runtimeNodeService).shouldHaveNoMoreInteractions(); + then(nodeService).shouldHaveNoInteractions(); + assertThat(associated).isTrue(); + } + + @Test + public void testIsRuleAssociatedWithRuleSet_emptyAssociation() + { + given(runtimeNodeService.getParentAssocs(any())).willReturn(Collections.emptyList()); + + // when + boolean associated = ruleService.isRuleAssociatedWithRuleSet(RULE_NODE, RULE_SET_NODE); + + then(runtimeNodeService).should().getParentAssocs(RULE_NODE); + then(runtimeNodeService).shouldHaveNoMoreInteractions(); + then(nodeService).shouldHaveNoInteractions(); + assertThat(associated).isFalse(); + } + + @Test + public void testIsRuleAssociatedWithRuleSet_improperAssociation() + { + final NodeRef fakeRuleSetNode = new NodeRef("rule://set/node/fake"); + given(runtimeNodeService.getParentAssocs(any())).willReturn(List.of(createAssociation(fakeRuleSetNode, RULE_NODE))); + + // when + boolean associated = ruleService.isRuleAssociatedWithRuleSet(RULE_NODE, RULE_SET_NODE); + + then(runtimeNodeService).should().getParentAssocs(RULE_NODE); + then(runtimeNodeService).shouldHaveNoMoreInteractions(); + then(nodeService).shouldHaveNoInteractions(); + assertThat(associated).isFalse(); + } + + @Test + public void testIsRuleSetShared() + { + given(runtimeNodeService.getParentAssocs(any())).willReturn(List.of(createAssociation(FOLDER_NODE, RULE_SET_NODE, false))); + + // when + boolean shared = ruleService.isRuleSetShared(RULE_SET_NODE); + + then(runtimeNodeService).should().getParentAssocs(RULE_SET_NODE); + then(runtimeNodeService).shouldHaveNoMoreInteractions(); + then(nodeService).shouldHaveNoInteractions(); + assertThat(shared).isTrue(); + } + + @Test + public void testIsRuleSetShared_notShared() + { + given(runtimeNodeService.getParentAssocs(any())).willReturn(List.of(createAssociation(FOLDER_NODE, RULE_SET_NODE))); + + // when + boolean shared = ruleService.isRuleSetShared(RULE_SET_NODE); + + then(runtimeNodeService).should().getParentAssocs(RULE_SET_NODE); + then(runtimeNodeService).shouldHaveNoMoreInteractions(); + then(nodeService).shouldHaveNoInteractions(); + assertThat(shared).isFalse(); + } + + private static ChildAssociationRef createAssociation(final NodeRef parentRef, final NodeRef childRef) + { + return createAssociation(parentRef, childRef, true); + } + + private static ChildAssociationRef createAssociation(final NodeRef parentRef, final NodeRef childRef, final boolean isPrimary) + { + return new ChildAssociationRef(null, parentRef, null, childRef, isPrimary, 1); + } }