ACS-3572 V1 API for action constraints (#1469)

* ACS-3572: Action constraints endpoint + logic + unit tests.

* ACS-3572: Action constraints endpoint + logic + unit tests.

* ACS-3572: Action constraints endpoint fixes.

* ACS-3572: Cleanup after removal of GET all Action constraints.

* ACS-3572: Fixing formatting in tests.

* ACS-3572: Loosening V1 constraints restrictions for aspects, types and properties.

* ACS-3572: Removing unnecessary code.

* ACS-3572: Removing unnecessary field.

* ACS-3572: Removing unnecessary field - fixing the tests.

* ACS-3572: Renaming the endpoint and fixing license headers.

* ACS-3572: Fixing license headers.

* ACS-3572: Fixing tas-restapi dependency version.

* ACS-3649: Bumping tes-restapi version.

* ACS-3649: Bumping tes-restapi version.
This commit is contained in:
Maciej Pichura
2022-10-10 10:14:47 +02:00
committed by GitHub
parent 98cb7762c4
commit 4d6b4c2ecf
14 changed files with 702 additions and 161 deletions

View File

@@ -2,7 +2,7 @@
* #%L
* Alfresco Remote API
* %%
* Copyright (C) 2005 - 2018 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
@@ -29,8 +29,10 @@ package org.alfresco.rest.api;
import org.alfresco.rest.api.model.Action;
import org.alfresco.rest.api.model.ActionDefinition;
import org.alfresco.rest.api.model.ActionParameterConstraint;
import org.alfresco.rest.framework.resource.parameters.CollectionWithPagingInfo;
import org.alfresco.rest.framework.resource.parameters.Parameters;
import org.alfresco.service.Experimental;
import org.alfresco.service.cmr.repository.NodeRef;
public interface Actions
@@ -45,7 +47,10 @@ public interface Actions
{
NAME,
TITLE
};
}
Action executeAction(Action action, Parameters parameters);
@Experimental
ActionParameterConstraint getActionConstraint(String constraintName);
}

View File

@@ -0,0 +1,59 @@
/*
* #%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.actions;
import javax.servlet.http.HttpServletResponse;
import org.alfresco.rest.api.Actions;
import org.alfresco.rest.api.model.ActionParameterConstraint;
import org.alfresco.rest.framework.WebApiDescription;
import org.alfresco.rest.framework.core.exceptions.EntityNotFoundException;
import org.alfresco.rest.framework.resource.EntityResource;
import org.alfresco.rest.framework.resource.actions.interfaces.EntityResourceAction;
import org.alfresco.rest.framework.resource.parameters.Parameters;
import org.alfresco.service.Experimental;
@EntityResource(name="action-parameter-constraints", title = "Action parameter constraints")
@Experimental
public class ActionParameterConstraintsEntityResource implements EntityResourceAction.ReadById<ActionParameterConstraint>
{
private final Actions actions;
public ActionParameterConstraintsEntityResource(Actions actions)
{
this.actions = actions;
}
@WebApiDescription(title = "Get single action parameter constraint",
description = "Retrieves a single action parameter constraint by constraint name",
successStatus = HttpServletResponse.SC_OK)
@Experimental
@Override
public ActionParameterConstraint readById(String constraintName, Parameters parameters) throws EntityNotFoundException
{
return actions.getActionConstraint(constraintName);
}
}

View File

@@ -27,15 +27,22 @@ package org.alfresco.rest.api.impl;
import org.alfresco.error.AlfrescoRuntimeException;
import org.alfresco.repo.action.access.ActionAccessRestriction;
import org.alfresco.repo.action.constraint.FolderContentsParameterConstraint;
import org.alfresco.rest.api.Actions;
import org.alfresco.rest.api.impl.rules.ActionParameterConverter;
import org.alfresco.rest.api.model.Action;
import org.alfresco.rest.api.model.ActionDefinition;
import org.alfresco.rest.api.model.ActionParameterConstraint;
import org.alfresco.rest.framework.core.exceptions.EntityNotFoundException;
import org.alfresco.rest.framework.core.exceptions.InvalidArgumentException;
import org.alfresco.rest.framework.core.exceptions.NotFoundException;
import org.alfresco.rest.framework.resource.parameters.CollectionWithPagingInfo;
import org.alfresco.rest.framework.resource.parameters.ListPage;
import org.alfresco.rest.framework.resource.parameters.Parameters;
import org.alfresco.rest.framework.resource.parameters.SortColumn;
import org.alfresco.service.Experimental;
import org.alfresco.service.cmr.action.ActionService;
import org.alfresco.service.cmr.action.ParameterConstraint;
import org.alfresco.service.cmr.action.ParameterDefinition;
import org.alfresco.service.cmr.dictionary.DataTypeDefinition;
import org.alfresco.service.cmr.dictionary.DictionaryException;
@@ -59,6 +66,7 @@ import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.stream.Collectors;
@@ -68,11 +76,14 @@ import static java.util.Comparator.nullsFirst;
public class ActionsImpl implements Actions
{
static final String CONSTRAINT_NOT_EXISTS = "Action parameter constraints for name %s do not exist.";
private ActionService actionService;
private DictionaryService dictionaryService;
private NamespaceService namespaceService;
private NodeService nodeService;
private NamespacePrefixResolver prefixResolver;
private ActionParameterConverter actionParameterConverter;
public void setActionService(ActionService actionService)
{
@@ -99,6 +110,11 @@ public class ActionsImpl implements Actions
this.prefixResolver = prefixResolver;
}
public void setActionParameterConverter(ActionParameterConverter actionParameterConverter)
{
this.actionParameterConverter = actionParameterConverter;
}
@Override
public ActionDefinition getActionDefinitionById(String actionDefinitionId)
{
@@ -125,10 +141,10 @@ public class ActionsImpl implements Actions
return result;
}
private ActionDefinition getActionDefinition(
private ActionDefinition getActionDefinition(
org.alfresco.service.cmr.action.ActionDefinition actionDefinitionId)
{
{
List<ActionDefinition.ParameterDefinition> paramDefs =
actionDefinitionId.
getParameterDefinitions().
@@ -145,7 +161,7 @@ public class ActionsImpl implements Actions
actionDefinitionId.getTrackStatus(),
paramDefs);
}
@Override
public CollectionWithPagingInfo<ActionDefinition> getActionDefinitions(NodeRef nodeRef, Parameters params)
{
@@ -174,7 +190,7 @@ public class ActionsImpl implements Actions
sortKey = Actions.SortKey.valueOf(sorting.get(0).column.toUpperCase());
sortAsc = sorting.get(0).asc;
}
Comparator<? super ActionDefinition> comparator;
switch (sortKey)
{
@@ -187,7 +203,7 @@ public class ActionsImpl implements Actions
default:
throw new IllegalArgumentException("Invalid sort key, must be either 'title' or 'name'.");
}
if (!sortAsc)
{
comparator = comparator.reversed();
@@ -220,7 +236,7 @@ public class ActionsImpl implements Actions
skip(skip).
limit(maxItems).
collect(Collectors.toList());
boolean hasMoreItems = actionDefinitions.size() > (skip + maxItems);
return CollectionWithPagingInfo.asPaged(
@@ -297,6 +313,52 @@ public class ActionsImpl implements Actions
return result;
}
@Override
@Experimental
public ActionParameterConstraint getActionConstraint(String constraintName)
{
final ParameterConstraint parameterConstraint = actionService.getParameterConstraint(constraintName);
if (Objects.isNull(parameterConstraint))
{
throw new NotFoundException(String.format(CONSTRAINT_NOT_EXISTS, constraintName));
}
return mapToActionConstraint(parameterConstraint);
}
@Experimental
private ActionParameterConstraint mapToActionConstraint(ParameterConstraint parameterConstraint)
{
final ActionParameterConstraint constraint = new ActionParameterConstraint();
constraint.setConstraintName(parameterConstraint.getName());
constraint.setConstraintValues(getConstraintDataList(parameterConstraint));
return constraint;
}
@Experimental
private List<ActionParameterConstraint.ConstraintData> getConstraintDataList(final ParameterConstraint parameterConstraint)
{
final Map<String, String> constraintValues = parameterConstraint.getValues();
if (parameterConstraint instanceof FolderContentsParameterConstraint)
{
return convertNodeRefConstraintValues(constraintValues).entrySet().stream()
.map(e -> new ActionParameterConstraint.ConstraintData(e.getKey(), e.getValue()))
.collect(Collectors.toList());
} else
{
return constraintValues.entrySet().stream()
.map(e -> new ActionParameterConstraint.ConstraintData(e.getKey(), e.getValue()))
.collect(Collectors.toList());
}
}
@Experimental
private Map<String, String> convertNodeRefConstraintValues(final Map<String, String> inputValues)
{
return inputValues.entrySet().stream()
.collect(Collectors.toMap(e -> actionParameterConverter.convertParamFromServiceModel(new NodeRef(e.getKey())).toString(),
Map.Entry::getValue));
}
private Map<String, Serializable> extractActionParams(org.alfresco.service.cmr.action.ActionDefinition actionDefinition, Map<String, String> params)
{
Map<String, Serializable> parameterValues = new HashMap<>();
@@ -391,7 +453,7 @@ public class ActionsImpl implements Actions
map(this::toShortQName).
collect(Collectors.toList());
}
private String toShortQName(QName type)
{
return type.toPrefixString(prefixResolver);

View File

@@ -57,7 +57,6 @@ public class RuleSetLoader
private static final int MAX_LINKED_TO_BY_SIZE = 100;
private NodeService nodeService;
private RuleService ruleService;
private RestRuleModelMapper restRuleModelMapper;
/**
* Load a rule set for the given node ref.
@@ -167,8 +166,4 @@ public class RuleSetLoader
this.ruleService = ruleService;
}
public void setRestRuleModelMapper(RestRuleModelMapper restRuleModelMapper)
{
this.restRuleModelMapper = restRuleModelMapper;
}
}

View File

@@ -0,0 +1,99 @@
/*
* #%L
* Alfresco Remote API
* %%
* Copyright (C) 2005 - 2021 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;
import java.util.List;
import org.alfresco.service.Experimental;
/**
* Representation of action parameter constraint.
* Helps to constraint the list of allowable values for an action parameter.
* When action parameter has constraints defined (@see ActionDefinition.ParameterDefinition#getParameterConstraintName())
* they will be listed here.
*
* @author mpichura
*/
@Experimental
public class ActionParameterConstraint
{
/**
* Constraint name.
*/
private String constraintName;
/**
* List of objects representing constraint values along with additional data
*/
private List<ConstraintData> constraintValues;
public List<ConstraintData> getConstraintValues()
{
return constraintValues;
}
public void setConstraintValues(List<ConstraintData> constraintValues)
{
this.constraintValues = constraintValues;
}
public String getConstraintName()
{
return constraintName;
}
public void setConstraintName(String constraintName)
{
this.constraintName = constraintName;
}
public static class ConstraintData
{
public ConstraintData(final String value, final String label)
{
this.value = value;
this.label = label;
}
/**
* Actual constraint value
*/
private String value;
/**
* A label associated to constraint's value
*/
private String label;
public String getValue()
{
return value;
}
public String getLabel()
{
return label;
}
}
}

View File

@@ -567,6 +567,7 @@
<property name="namespaceService" ref="NamespaceService"/>
<property name="nodeService" ref="NodeService"/>
<property name="prefixResolver" ref="namespaceService"/>
<property name="actionParameterConverter" ref="actionParameterConverter"/>
</bean>
<bean id="Actions" class="org.springframework.aop.framework.ProxyFactoryBean">
<property name="proxyInterfaces" value="org.alfresco.rest.api.Actions"/>
@@ -584,6 +585,9 @@
<bean class="org.alfresco.rest.api.actions.ActionExecutionsEntityResource">
<property name="actions" ref="Actions"/>
</bean>
<bean class="org.alfresco.rest.api.actions.ActionParameterConstraintsEntityResource">
<constructor-arg name="actions" ref="Actions"/>
</bean>
<bean id="Downloads" class="org.springframework.aop.framework.ProxyFactoryBean">
<property name="proxyInterfaces">
@@ -865,7 +869,6 @@
<bean id="ruleSetLoader" class="org.alfresco.rest.api.impl.rules.RuleSetLoader">
<property name="nodeService" ref="NodeService" />
<property name="ruleService" ref="RuleService" />
<property name="restRuleModelMapper" ref="ruleMapper" />
</bean>
<bean id="ruleSets" class="org.alfresco.rest.api.impl.rules.RuleSetsImpl">

View File

@@ -0,0 +1,66 @@
/*
* #%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.actions;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.BDDMockito.given;
import static org.mockito.BDDMockito.then;
import org.alfresco.rest.api.Actions;
import org.alfresco.rest.api.model.ActionParameterConstraint;
import org.alfresco.rest.framework.resource.parameters.Parameters;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.MockitoJUnitRunner;
@RunWith(MockitoJUnitRunner.class)
public class ActionParameterConstraintsEntityResourceTest
{
@Mock
private Actions actionsMock;
@Mock
private Parameters parametersMock;
@InjectMocks
private ActionParameterConstraintsEntityResource objectUnderTest;
@Test
public void testReadById() {
final String name = "name";
final ActionParameterConstraint dummyConstraint = new ActionParameterConstraint();
given(actionsMock.getActionConstraint(name)).willReturn(dummyConstraint);
//when
ActionParameterConstraint result = objectUnderTest.readById(name, parametersMock);
then(actionsMock).should().getActionConstraint(name);
then(actionsMock).shouldHaveNoMoreInteractions();
assertThat(result).isNotNull().usingRecursiveComparison().isEqualTo(dummyConstraint);
}
}

View File

@@ -0,0 +1,165 @@
/*
* #%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.impl;
import static org.alfresco.rest.api.impl.ActionsImpl.CONSTRAINT_NOT_EXISTS;
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.BDDMockito.given;
import static org.mockito.BDDMockito.then;
import static org.mockito.Mockito.mock;
import java.util.Map;
import org.alfresco.repo.action.constraint.FolderContentsParameterConstraint;
import org.alfresco.rest.api.impl.rules.ActionParameterConverter;
import org.alfresco.rest.api.model.ActionParameterConstraint;
import org.alfresco.rest.framework.core.exceptions.NotFoundException;
import org.alfresco.rest.framework.resource.parameters.Parameters;
import org.alfresco.service.cmr.action.ActionService;
import org.alfresco.service.cmr.action.ParameterConstraint;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.MockitoJUnitRunner;
@RunWith(MockitoJUnitRunner.class)
public class ActionsImplTest
{
private static final String NAME = "name";
private static final String CONSTRAINT = "constraint";
private static final String LABEL = "label";
private static final String DISPLAY = "display ";
@Mock
private ActionService actionServiceMock;
@Mock
private Parameters parametersMock;
@Mock
private ActionParameterConverter parameterConverterMock;
@InjectMocks
private ActionsImpl objectUnderTest;
@Test
public void testGetSingleActionConstraint()
{
final String name = "name";
final String value = CONSTRAINT;
final String label = LABEL;
final Map<String, String> values = Map.of(value, label);
final ParameterConstraint testConstraint = createTestConstraint(name, values);
given(actionServiceMock.getParameterConstraint(name)).willReturn(testConstraint);
//when
final ActionParameterConstraint actualConstraint = objectUnderTest.getActionConstraint(name);
then(parametersMock).shouldHaveNoInteractions();
then(actionServiceMock).should().getParameterConstraint(name);
then(actionServiceMock).shouldHaveNoMoreInteractions();
assertThat(actualConstraint).isNotNull();
assertThat(actualConstraint.getConstraintName()).isEqualTo(testConstraint.getName());
ActionParameterConstraint.ConstraintData expectedConstraintData = new ActionParameterConstraint.ConstraintData(value, label);
assertThat(actualConstraint.getConstraintValues()).isNotNull().hasSize(1);
ActionParameterConstraint.ConstraintData actualConstraintData = actualConstraint.getConstraintValues().get(0);
assertThat(actualConstraintData).usingRecursiveComparison().isEqualTo(expectedConstraintData);
}
@Test
public void testGetSingleActionNodeConstraint()
{
final String name = "name1";
final String dummyNodeId = "dummy-node-id";
final String value = "workspace://DummyStore/" + dummyNodeId;
final Map<String, String> values = Map.of(value, LABEL);
final FolderContentsParameterConstraint testConstraint = mock(FolderContentsParameterConstraint.class);
given(testConstraint.getName()).willReturn(name);
given(testConstraint.getValues()).willReturn(values);
given(actionServiceMock.getParameterConstraint(name)).willReturn(testConstraint);
given(parameterConverterMock.convertParamFromServiceModel(any())).willReturn(dummyNodeId);
//when
final ActionParameterConstraint actualConstraint = objectUnderTest.getActionConstraint(name);
then(parametersMock).shouldHaveNoInteractions();
then(actionServiceMock).should().getParameterConstraint(name);
then(actionServiceMock).shouldHaveNoMoreInteractions();
assertThat(actualConstraint).isNotNull();
assertThat(actualConstraint.getConstraintName()).isEqualTo(testConstraint.getName());
ActionParameterConstraint.ConstraintData expectedConstraintData = new ActionParameterConstraint.ConstraintData(dummyNodeId, LABEL);
assertThat(actualConstraint.getConstraintValues()).isNotNull().hasSize(1);
ActionParameterConstraint.ConstraintData actualConstraintData = actualConstraint.getConstraintValues().get(0);
assertThat(actualConstraintData).usingRecursiveComparison().isEqualTo(expectedConstraintData);
}
@Test
public void testGetActionConstraintsWithNameFilterNonExistingConstraint()
{
final String name = "name";
given(actionServiceMock.getParameterConstraint(name)).willReturn(null);
//when
assertThatExceptionOfType(NotFoundException.class).isThrownBy(() -> objectUnderTest.getActionConstraint(name))
.withMessageContaining(String.format(CONSTRAINT_NOT_EXISTS, name));
then(parametersMock).shouldHaveNoInteractions();
then(actionServiceMock).should().getParameterConstraint(name);
then(actionServiceMock).shouldHaveNoMoreInteractions();
}
private ParameterConstraint createTestConstraint(final String name, final Map<String, String> values)
{
return new ParameterConstraint()
{
@Override
public String getName()
{
return name;
}
@Override
public boolean isValidValue(String value)
{
return true;
}
@Override
public String getValueDisplayLabel(String value)
{
return DISPLAY + name;
}
@Override
public Map<String, String> getAllowableValues()
{
return values;
}
};
}
}