From a59234f47f75edd0bed7b166237ab2f12e39d71e Mon Sep 17 00:00:00 2001 From: George Evangelopoulos Date: Tue, 7 Feb 2023 18:32:52 +0200 Subject: [PATCH] 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 --- .../java/org/alfresco/rest/requests/Tags.java | 11 ++ .../main/java/org/alfresco/rest/api/Tags.java | 40 ++--- .../org/alfresco/rest/api/impl/TagsImpl.java | 150 ++++++++++-------- .../rest/api/tags/TagsEntityResource.java | 58 ++++--- .../alfresco/public-rest-context.xml | 1 + .../alfresco/rest/api/impl/TagsImplTest.java | 119 ++++++++++++++ 6 files changed, 270 insertions(+), 109 deletions(-) create mode 100644 remote-api/src/test/java/org/alfresco/rest/api/impl/TagsImplTest.java diff --git a/packaging/tests/tas-restapi/src/main/java/org/alfresco/rest/requests/Tags.java b/packaging/tests/tas-restapi/src/main/java/org/alfresco/rest/requests/Tags.java index 6812eb99c8..6138609076 100644 --- a/packaging/tests/tas-restapi/src/main/java/org/alfresco/rest/requests/Tags.java +++ b/packaging/tests/tas-restapi/src/main/java/org/alfresco/rest/requests/Tags.java @@ -82,4 +82,15 @@ public class Tags extends ModelRequest 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); + } } diff --git a/remote-api/src/main/java/org/alfresco/rest/api/Tags.java b/remote-api/src/main/java/org/alfresco/rest/api/Tags.java index 6c8971a12a..e3fc52c7b7 100644 --- a/remote-api/src/main/java/org/alfresco/rest/api/Tags.java +++ b/remote-api/src/main/java/org/alfresco/rest/api/Tags.java @@ -23,22 +23,24 @@ * along with Alfresco. If not, see . * #L% */ -package org.alfresco.rest.api; - -import java.util.List; - -import org.alfresco.rest.api.model.Tag; -import org.alfresco.rest.framework.resource.parameters.CollectionWithPagingInfo; -import org.alfresco.rest.framework.resource.parameters.Paging; -import org.alfresco.rest.framework.resource.parameters.Parameters; -import org.alfresco.service.cmr.repository.StoreRef; - -public interface Tags -{ - public List addTags(String nodeId, List tags); - public Tag getTag(StoreRef storeRef, String tagId); - public void deleteTag(String nodeId, String tagId); - public CollectionWithPagingInfo getTags(StoreRef storeRef, Parameters params); - public Tag changeTag(StoreRef storeRef, String tagId, Tag tag); - public CollectionWithPagingInfo getTags(String nodeId, Parameters params); -} +package org.alfresco.rest.api; + +import java.util.List; + +import org.alfresco.rest.api.model.Tag; +import org.alfresco.rest.framework.resource.parameters.CollectionWithPagingInfo; +import org.alfresco.rest.framework.resource.parameters.Paging; +import org.alfresco.rest.framework.resource.parameters.Parameters; +import org.alfresco.service.cmr.repository.StoreRef; + +public interface Tags +{ + public List addTags(String nodeId, List tags); + public Tag getTag(StoreRef storeRef, String tagId); + public void deleteTag(String nodeId, String tagId); + public CollectionWithPagingInfo getTags(StoreRef storeRef, Parameters params); + public Tag changeTag(StoreRef storeRef, String tagId, Tag tag); + public CollectionWithPagingInfo getTags(String nodeId, Parameters params); + + void deleteTagById(StoreRef storeRef, String tagId); +} diff --git a/remote-api/src/main/java/org/alfresco/rest/api/impl/TagsImpl.java b/remote-api/src/main/java/org/alfresco/rest/api/impl/TagsImpl.java index 529714e41a..2c0b80834b 100644 --- a/remote-api/src/main/java/org/alfresco/rest/api/impl/TagsImpl.java +++ b/remote-api/src/main/java/org/alfresco/rest/api/impl/TagsImpl.java @@ -1,36 +1,36 @@ -/* - * #%L - * Alfresco Remote API - * %% - * Copyright (C) 2005 - 2016 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% - */ +/* + * #%L + * Alfresco Remote API + * %% + * Copyright (C) 2005 - 2016 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.impl; import java.util.AbstractList; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + import org.alfresco.query.PagingResults; import org.alfresco.repo.tagging.NonExistentTagException; 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.InvalidArgumentException; 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.resource.parameters.CollectionWithPagingInfo; import org.alfresco.rest.framework.resource.parameters.Paging; import org.alfresco.rest.framework.resource.parameters.Parameters; 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.alfresco.util.Pair; import org.alfresco.util.TypeConstraint; @@ -60,11 +62,14 @@ import org.alfresco.util.TypeConstraint; */ public class TagsImpl implements Tags { - private static final Object PARAM_INCLUDE_COUNT = "count"; - private Nodes nodes; + private static final Object PARAM_INCLUDE_COUNT = "count"; + private Nodes nodes; private TaggingService taggingService; 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) { this.typeConstraint = typeConstraint; @@ -80,6 +85,11 @@ public class TagsImpl implements Tags this.taggingService = taggingService; } + public void setAuthorityService(AuthorityService authorityService) + { + this.authorityService = authorityService; + } + public List addTags(String nodeId, final List tags) { NodeRef nodeRef = nodes.validateNode(nodeId); @@ -128,37 +138,49 @@ public class TagsImpl implements Tags taggingService.removeTag(nodeRef, tagValue); } - public CollectionWithPagingInfo getTags(StoreRef storeRef, Parameters params) - { - Paging paging = params.getPaging(); - PagingResults> results = taggingService.getTags(storeRef, Util.getPagingRequest(paging)); - taggingService.getPagedTags(storeRef, 0, paging.getMaxItems()); - Integer totalItems = results.getTotalResultCount().getFirst(); - List> page = results.getPage(); - List tags = new ArrayList(page.size()); - List> tagsByCount = null; - Map tagsByCountMap = new HashMap(); - - if (params.getInclude().contains(PARAM_INCLUDE_COUNT)) - { - tagsByCount = taggingService.findTaggedNodesAndCountByTagName(storeRef); - if (tagsByCount != null) - { - for (Pair tagByCountElem : tagsByCount) - { - tagsByCountMap.put(tagByCountElem.getFirst(), tagByCountElem.getSecond()); - } - } - } - for (Pair 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())); - } + @Override + public void deleteTagById(StoreRef storeRef, String tagId) { + if (!authorityService.hasAdminAuthority()) + { + throw new PermissionDeniedException(NO_PERMISSION_TO_MANAGE_A_TAG); + } + + NodeRef tagNodeRef = validateTag(storeRef, tagId); + String tagValue = taggingService.getTagName(tagNodeRef); + taggingService.deleteTag(storeRef, tagValue); + } + + public CollectionWithPagingInfo getTags(StoreRef storeRef, Parameters params) + { + Paging paging = params.getPaging(); + PagingResults> results = taggingService.getTags(storeRef, Util.getPagingRequest(paging)); + taggingService.getPagedTags(storeRef, 0, paging.getMaxItems()); + Integer totalItems = results.getTotalResultCount().getFirst(); + List> page = results.getPage(); + List tags = new ArrayList(page.size()); + List> tagsByCount = null; + Map tagsByCountMap = new HashMap(); + + if (params.getInclude().contains(PARAM_INCLUDE_COUNT)) + { + tagsByCount = taggingService.findTaggedNodesAndCountByTagName(storeRef); + if (tagsByCount != null) + { + for (Pair tagByCountElem : tagsByCount) + { + tagsByCountMap.put(tagByCountElem.getFirst(), tagByCountElem.getSecond()); + } + } + } + for (Pair 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) { diff --git a/remote-api/src/main/java/org/alfresco/rest/api/tags/TagsEntityResource.java b/remote-api/src/main/java/org/alfresco/rest/api/tags/TagsEntityResource.java index 007bf218f1..635acdf895 100644 --- a/remote-api/src/main/java/org/alfresco/rest/api/tags/TagsEntityResource.java +++ b/remote-api/src/main/java/org/alfresco/rest/api/tags/TagsEntityResource.java @@ -1,28 +1,28 @@ -/* - * #%L - * Alfresco Remote API - * %% - * Copyright (C) 2005 - 2016 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% - */ +/* + * #%L + * Alfresco Remote API + * %% + * Copyright (C) 2005 - 2016 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.tags; import org.alfresco.rest.api.Tags; @@ -38,7 +38,7 @@ import org.alfresco.util.ParameterCheck; import org.springframework.beans.factory.InitializingBean; @EntityResource(name="tags", title = "Tags") -public class TagsEntityResource implements EntityResourceAction.Read, EntityResourceAction.ReadById, EntityResourceAction.Update, InitializingBean +public class TagsEntityResource implements EntityResourceAction.Read, EntityResourceAction.ReadById, EntityResourceAction.Update, EntityResourceAction.Delete, InitializingBean { private Tags tags; @@ -77,4 +77,10 @@ public class TagsEntityResource implements EntityResourceAction.Read, Entit { 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); + } } diff --git a/remote-api/src/main/resources/alfresco/public-rest-context.xml b/remote-api/src/main/resources/alfresco/public-rest-context.xml index 6c3ed290ca..3460b0a888 100644 --- a/remote-api/src/main/resources/alfresco/public-rest-context.xml +++ b/remote-api/src/main/resources/alfresco/public-rest-context.xml @@ -856,6 +856,7 @@ + diff --git a/remote-api/src/test/java/org/alfresco/rest/api/impl/TagsImplTest.java b/remote-api/src/test/java/org/alfresco/rest/api/impl/TagsImplTest.java new file mode 100644 index 0000000000..f1ee8cee95 --- /dev/null +++ b/remote-api/src/test/java/org/alfresco/rest/api/impl/TagsImplTest.java @@ -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 . + * #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(); + } +}