ACS-4027: Introduce changes to support removing tag from all associated nodes (#1709)

* ACS-4027: Introduce changes to support removing tag from all associated nodes

* ACS-4027: fix implementation and add tests
This commit is contained in:
George Evangelopoulos
2023-02-07 18:32:52 +02:00
committed by GitHub
parent c9522f299e
commit a59234f47f
6 changed files with 270 additions and 109 deletions

View File

@@ -82,4 +82,15 @@ public class Tags extends ModelRequest<Tags>
return restWrapper.processModel(RestTagModel.class, request); return restWrapper.processModel(RestTagModel.class, request);
} }
/**
* Delete tag.
* - DELETE /tags/{tagId}
*/
public void deleteTag()
{
RestRequest request = RestRequest.
simpleRequest(HttpMethod.DELETE, "/tags/{tagId}", tag.getId());
restWrapper.processEmptyModel(request);
}
} }

View File

@@ -23,22 +23,24 @@
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>. * along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
* #L% * #L%
*/ */
package org.alfresco.rest.api; package org.alfresco.rest.api;
import java.util.List; import java.util.List;
import org.alfresco.rest.api.model.Tag; import org.alfresco.rest.api.model.Tag;
import org.alfresco.rest.framework.resource.parameters.CollectionWithPagingInfo; import org.alfresco.rest.framework.resource.parameters.CollectionWithPagingInfo;
import org.alfresco.rest.framework.resource.parameters.Paging; import org.alfresco.rest.framework.resource.parameters.Paging;
import org.alfresco.rest.framework.resource.parameters.Parameters; import org.alfresco.rest.framework.resource.parameters.Parameters;
import org.alfresco.service.cmr.repository.StoreRef; import org.alfresco.service.cmr.repository.StoreRef;
public interface Tags public interface Tags
{ {
public List<Tag> addTags(String nodeId, List<Tag> tags); public List<Tag> addTags(String nodeId, List<Tag> tags);
public Tag getTag(StoreRef storeRef, String tagId); public Tag getTag(StoreRef storeRef, String tagId);
public void deleteTag(String nodeId, String tagId); public void deleteTag(String nodeId, String tagId);
public CollectionWithPagingInfo<Tag> getTags(StoreRef storeRef, Parameters params); public CollectionWithPagingInfo<Tag> getTags(StoreRef storeRef, Parameters params);
public Tag changeTag(StoreRef storeRef, String tagId, Tag tag); public Tag changeTag(StoreRef storeRef, String tagId, Tag tag);
public CollectionWithPagingInfo<Tag> getTags(String nodeId, Parameters params); public CollectionWithPagingInfo<Tag> getTags(String nodeId, Parameters params);
}
void deleteTagById(StoreRef storeRef, String tagId);
}

View File

@@ -1,36 +1,36 @@
/* /*
* #%L * #%L
* Alfresco Remote API * Alfresco Remote API
* %% * %%
* Copyright (C) 2005 - 2016 Alfresco Software Limited * Copyright (C) 2005 - 2016 Alfresco Software Limited
* %% * %%
* This file is part of the Alfresco software. * This file is part of the Alfresco software.
* If the software was purchased under a paid Alfresco license, the terms of * If the software was purchased under a paid Alfresco license, the terms of
* the paid license agreement will prevail. Otherwise, the software is * the paid license agreement will prevail. Otherwise, the software is
* provided under the following open source license terms: * provided under the following open source license terms:
* *
* Alfresco is free software: you can redistribute it and/or modify * 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 * 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 * the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version. * (at your option) any later version.
* *
* Alfresco is distributed in the hope that it will be useful, * Alfresco is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of * but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details. * GNU Lesser General Public License for more details.
* *
* You should have received a copy of the GNU Lesser General Public License * You should have received a copy of the GNU Lesser General Public License
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>. * along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
* #L% * #L%
*/ */
package org.alfresco.rest.api.impl; package org.alfresco.rest.api.impl;
import java.util.AbstractList; import java.util.AbstractList;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.HashMap; import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import org.alfresco.query.PagingResults; import org.alfresco.query.PagingResults;
import org.alfresco.repo.tagging.NonExistentTagException; import org.alfresco.repo.tagging.NonExistentTagException;
import org.alfresco.repo.tagging.TagExistsException; import org.alfresco.repo.tagging.TagExistsException;
@@ -42,12 +42,14 @@ import org.alfresco.rest.framework.core.exceptions.ConstraintViolatedException;
import org.alfresco.rest.framework.core.exceptions.EntityNotFoundException; import org.alfresco.rest.framework.core.exceptions.EntityNotFoundException;
import org.alfresco.rest.framework.core.exceptions.InvalidArgumentException; import org.alfresco.rest.framework.core.exceptions.InvalidArgumentException;
import org.alfresco.rest.framework.core.exceptions.NotFoundException; import org.alfresco.rest.framework.core.exceptions.NotFoundException;
import org.alfresco.rest.framework.core.exceptions.PermissionDeniedException;
import org.alfresco.rest.framework.core.exceptions.UnsupportedResourceOperationException; import org.alfresco.rest.framework.core.exceptions.UnsupportedResourceOperationException;
import org.alfresco.rest.framework.resource.parameters.CollectionWithPagingInfo; import org.alfresco.rest.framework.resource.parameters.CollectionWithPagingInfo;
import org.alfresco.rest.framework.resource.parameters.Paging; import org.alfresco.rest.framework.resource.parameters.Paging;
import org.alfresco.rest.framework.resource.parameters.Parameters; import org.alfresco.rest.framework.resource.parameters.Parameters;
import org.alfresco.service.cmr.repository.NodeRef; import org.alfresco.service.cmr.repository.NodeRef;
import org.alfresco.service.cmr.repository.StoreRef; import org.alfresco.service.cmr.repository.StoreRef;
import org.alfresco.service.cmr.security.AuthorityService;
import org.alfresco.service.cmr.tagging.TaggingService; import org.alfresco.service.cmr.tagging.TaggingService;
import org.alfresco.util.Pair; import org.alfresco.util.Pair;
import org.alfresco.util.TypeConstraint; import org.alfresco.util.TypeConstraint;
@@ -60,11 +62,14 @@ import org.alfresco.util.TypeConstraint;
*/ */
public class TagsImpl implements Tags public class TagsImpl implements Tags
{ {
private static final Object PARAM_INCLUDE_COUNT = "count"; private static final Object PARAM_INCLUDE_COUNT = "count";
private Nodes nodes; private Nodes nodes;
private TaggingService taggingService; private TaggingService taggingService;
private TypeConstraint typeConstraint; private TypeConstraint typeConstraint;
private AuthorityService authorityService;
static final String NO_PERMISSION_TO_MANAGE_A_TAG = "Current user does not have permission to manage a tag";
public void setTypeConstraint(TypeConstraint typeConstraint) public void setTypeConstraint(TypeConstraint typeConstraint)
{ {
this.typeConstraint = typeConstraint; this.typeConstraint = typeConstraint;
@@ -80,6 +85,11 @@ public class TagsImpl implements Tags
this.taggingService = taggingService; this.taggingService = taggingService;
} }
public void setAuthorityService(AuthorityService authorityService)
{
this.authorityService = authorityService;
}
public List<Tag> addTags(String nodeId, final List<Tag> tags) public List<Tag> addTags(String nodeId, final List<Tag> tags)
{ {
NodeRef nodeRef = nodes.validateNode(nodeId); NodeRef nodeRef = nodes.validateNode(nodeId);
@@ -128,37 +138,49 @@ public class TagsImpl implements Tags
taggingService.removeTag(nodeRef, tagValue); taggingService.removeTag(nodeRef, tagValue);
} }
public CollectionWithPagingInfo<Tag> getTags(StoreRef storeRef, Parameters params) @Override
{ public void deleteTagById(StoreRef storeRef, String tagId) {
Paging paging = params.getPaging(); if (!authorityService.hasAdminAuthority())
PagingResults<Pair<NodeRef, String>> results = taggingService.getTags(storeRef, Util.getPagingRequest(paging)); {
taggingService.getPagedTags(storeRef, 0, paging.getMaxItems()); throw new PermissionDeniedException(NO_PERMISSION_TO_MANAGE_A_TAG);
Integer totalItems = results.getTotalResultCount().getFirst(); }
List<Pair<NodeRef, String>> page = results.getPage();
List<Tag> tags = new ArrayList<Tag>(page.size()); NodeRef tagNodeRef = validateTag(storeRef, tagId);
List<Pair<String, Integer>> tagsByCount = null; String tagValue = taggingService.getTagName(tagNodeRef);
Map<String, Integer> tagsByCountMap = new HashMap<String, Integer>(); taggingService.deleteTag(storeRef, tagValue);
}
if (params.getInclude().contains(PARAM_INCLUDE_COUNT))
{ public CollectionWithPagingInfo<Tag> getTags(StoreRef storeRef, Parameters params)
tagsByCount = taggingService.findTaggedNodesAndCountByTagName(storeRef); {
if (tagsByCount != null) Paging paging = params.getPaging();
{ PagingResults<Pair<NodeRef, String>> results = taggingService.getTags(storeRef, Util.getPagingRequest(paging));
for (Pair<String, Integer> tagByCountElem : tagsByCount) taggingService.getPagedTags(storeRef, 0, paging.getMaxItems());
{ Integer totalItems = results.getTotalResultCount().getFirst();
tagsByCountMap.put(tagByCountElem.getFirst(), tagByCountElem.getSecond()); List<Pair<NodeRef, String>> page = results.getPage();
} List<Tag> tags = new ArrayList<Tag>(page.size());
} List<Pair<String, Integer>> tagsByCount = null;
} Map<String, Integer> tagsByCountMap = new HashMap<String, Integer>();
for (Pair<NodeRef, String> pair : page)
{ if (params.getInclude().contains(PARAM_INCLUDE_COUNT))
Tag selectedTag = new Tag(pair.getFirst(), pair.getSecond()); {
selectedTag.setCount(tagsByCountMap.get(selectedTag.getTag())); tagsByCount = taggingService.findTaggedNodesAndCountByTagName(storeRef);
tags.add(selectedTag); if (tagsByCount != null)
} {
for (Pair<String, Integer> tagByCountElem : tagsByCount)
return CollectionWithPagingInfo.asPaged(paging, tags, results.hasMoreItems(), (totalItems == null ? null : totalItems.intValue())); {
} tagsByCountMap.put(tagByCountElem.getFirst(), tagByCountElem.getSecond());
}
}
}
for (Pair<NodeRef, String> pair : page)
{
Tag selectedTag = new Tag(pair.getFirst(), pair.getSecond());
selectedTag.setCount(tagsByCountMap.get(selectedTag.getTag()));
tags.add(selectedTag);
}
return CollectionWithPagingInfo.asPaged(paging, tags, results.hasMoreItems(), (totalItems == null ? null : totalItems.intValue()));
}
public NodeRef validateTag(String tagId) public NodeRef validateTag(String tagId)
{ {

View File

@@ -1,28 +1,28 @@
/* /*
* #%L * #%L
* Alfresco Remote API * Alfresco Remote API
* %% * %%
* Copyright (C) 2005 - 2016 Alfresco Software Limited * Copyright (C) 2005 - 2016 Alfresco Software Limited
* %% * %%
* This file is part of the Alfresco software. * This file is part of the Alfresco software.
* If the software was purchased under a paid Alfresco license, the terms of * If the software was purchased under a paid Alfresco license, the terms of
* the paid license agreement will prevail. Otherwise, the software is * the paid license agreement will prevail. Otherwise, the software is
* provided under the following open source license terms: * provided under the following open source license terms:
* *
* Alfresco is free software: you can redistribute it and/or modify * 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 * 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 * the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version. * (at your option) any later version.
* *
* Alfresco is distributed in the hope that it will be useful, * Alfresco is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of * but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details. * GNU Lesser General Public License for more details.
* *
* You should have received a copy of the GNU Lesser General Public License * You should have received a copy of the GNU Lesser General Public License
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>. * along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
* #L% * #L%
*/ */
package org.alfresco.rest.api.tags; package org.alfresco.rest.api.tags;
import org.alfresco.rest.api.Tags; import org.alfresco.rest.api.Tags;
@@ -38,7 +38,7 @@ import org.alfresco.util.ParameterCheck;
import org.springframework.beans.factory.InitializingBean; import org.springframework.beans.factory.InitializingBean;
@EntityResource(name="tags", title = "Tags") @EntityResource(name="tags", title = "Tags")
public class TagsEntityResource implements EntityResourceAction.Read<Tag>, EntityResourceAction.ReadById<Tag>, EntityResourceAction.Update<Tag>, InitializingBean public class TagsEntityResource implements EntityResourceAction.Read<Tag>, EntityResourceAction.ReadById<Tag>, EntityResourceAction.Update<Tag>, EntityResourceAction.Delete, InitializingBean
{ {
private Tags tags; private Tags tags;
@@ -77,4 +77,10 @@ public class TagsEntityResource implements EntityResourceAction.Read<Tag>, Entit
{ {
return tags.getTag(StoreRef.STORE_REF_WORKSPACE_SPACESSTORE, id); return tags.getTag(StoreRef.STORE_REF_WORKSPACE_SPACESSTORE, id);
} }
@Override
public void delete(String id, Parameters parameters)
{
tags.deleteTagById(StoreRef.STORE_REF_WORKSPACE_SPACESSTORE, id);
}
} }

View File

@@ -856,6 +856,7 @@
<bean id="tags" class="org.alfresco.rest.api.impl.TagsImpl"> <bean id="tags" class="org.alfresco.rest.api.impl.TagsImpl">
<property name="nodes" ref="nodes" /> <property name="nodes" ref="nodes" />
<property name="taggingService" ref="TaggingService" /> <property name="taggingService" ref="TaggingService" />
<property name="authorityService" ref="AuthorityService" />
<property name="typeConstraint" ref="nodeTypeConstraint" /> <property name="typeConstraint" ref="nodeTypeConstraint" />
</bean> </bean>

View File

@@ -0,0 +1,119 @@
/*
* #%L
* Alfresco Remote API
* %%
* Copyright (C) 2005 - 2023 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 org.alfresco.rest.api.Nodes;
import org.alfresco.rest.framework.core.exceptions.EntityNotFoundException;
import org.alfresco.rest.framework.core.exceptions.PermissionDeniedException;
import org.alfresco.service.cmr.repository.NodeRef;
import org.alfresco.service.cmr.repository.StoreRef;
import org.alfresco.service.cmr.security.AuthorityService;
import org.alfresco.service.cmr.tagging.TaggingService;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.MockitoJUnitRunner;
import static org.junit.Assert.assertThrows;
import static org.mockito.BDDMockito.given;
import static org.mockito.BDDMockito.then;
@RunWith(MockitoJUnitRunner.class)
public class TagsImplTest
{
private static final String TAG_ID = "tag-node-id";
private static final String TAG_NAME = "tag-dummy-name";
private static final NodeRef TAG_NODE_REF = new NodeRef(StoreRef.STORE_REF_WORKSPACE_SPACESSTORE,TAG_ID);
@Mock
private Nodes nodesMock;
@Mock
private AuthorityService authorityServiceMock;
@Mock
private TaggingService taggingServiceMock;
@InjectMocks
private TagsImpl objectUnderTest;
@Before
public void setup()
{
given(authorityServiceMock.hasAdminAuthority()).willReturn(true);
given(nodesMock.validateNode(StoreRef.STORE_REF_WORKSPACE_SPACESSTORE, TAG_ID)).willReturn(TAG_NODE_REF);
given(taggingServiceMock.getTagName(TAG_NODE_REF)).willReturn(TAG_NAME);
}
@Test
public void testDeleteTagById()
{
//when
objectUnderTest.deleteTagById(StoreRef.STORE_REF_WORKSPACE_SPACESSTORE, TAG_ID);
then(authorityServiceMock).should().hasAdminAuthority();
then(authorityServiceMock).shouldHaveNoMoreInteractions();
then(nodesMock).should().validateNode(StoreRef.STORE_REF_WORKSPACE_SPACESSTORE, TAG_ID);
then(nodesMock).shouldHaveNoMoreInteractions();
then(taggingServiceMock).should().getTagName(TAG_NODE_REF);
then(taggingServiceMock).should().deleteTag(StoreRef.STORE_REF_WORKSPACE_SPACESSTORE, TAG_NAME);
then(taggingServiceMock).shouldHaveNoMoreInteractions();
}
@Test
public void testDeleteTagById_asNonAdminUser()
{
given(authorityServiceMock.hasAdminAuthority()).willReturn(false);
//when
assertThrows(PermissionDeniedException.class, () -> objectUnderTest.deleteTagById(StoreRef.STORE_REF_WORKSPACE_SPACESSTORE, TAG_ID));
then(authorityServiceMock).should().hasAdminAuthority();
then(authorityServiceMock).shouldHaveNoMoreInteractions();
then(nodesMock).shouldHaveNoInteractions();
then(taggingServiceMock).shouldHaveNoInteractions();
}
@Test
public void testDeleteTagById_nonExistentTag()
{
//when
assertThrows(EntityNotFoundException.class, () -> objectUnderTest.deleteTagById(StoreRef.STORE_REF_WORKSPACE_SPACESSTORE, "dummy-id"));
then(authorityServiceMock).should().hasAdminAuthority();
then(authorityServiceMock).shouldHaveNoMoreInteractions();
then(nodesMock).should().validateNode(StoreRef.STORE_REF_WORKSPACE_SPACESSTORE, "dummy-id");
then(nodesMock).shouldHaveNoMoreInteractions();
then(taggingServiceMock).shouldHaveNoInteractions();
}
}