ACS-4035: support for removing a piece of content from a category (#1670)

* ACS-4035: support for removing a piece of content from a category
This commit is contained in:
George Evangelopoulos
2023-01-24 14:29:05 +02:00
committed by GitHub
parent 8a1b81ab61
commit 281009bc50
6 changed files with 226 additions and 1 deletions

View File

@@ -71,4 +71,12 @@ public interface Categories
* @return Linked to categories.
*/
List<Category> linkNodeToCategories(String nodeId, List<Category> categoryLinks);
/**
* Unlink node from a category.
*
* @param nodeId Node ID.
* @param categoryId Category ID from which content node should be unlinked from.
*/
void unlinkNodeFromCategory(String nodeId, String categoryId, Parameters parameters);
}

View File

@@ -40,7 +40,9 @@ import org.alfresco.rest.framework.resource.parameters.ListPage;
import org.alfresco.rest.framework.resource.parameters.Parameters;
@RelationshipResource(name = "category-links", entityResource = NodesEntityResource.class, title = "Category links")
public class NodesCategoryLinksRelation implements RelationshipResourceAction.Read<Category>, RelationshipResourceAction.Create<Category>
public class NodesCategoryLinksRelation implements RelationshipResourceAction.Create<Category>,
RelationshipResourceAction.Read<Category>,
RelationshipResourceAction.Delete
{
private final Categories categories;
@@ -77,4 +79,19 @@ public class NodesCategoryLinksRelation implements RelationshipResourceAction.Re
{
return categories.linkNodeToCategories(nodeId, categoryLinks);
}
/**
* DELETE /nodes/{nodeId}/category-links/{categoryId}
*/
@WebApiDescription(
title = "Unlink content node from category",
description = "Removes the link between a content node and a category",
successStatus = HttpServletResponse.SC_NO_CONTENT
)
@Override
public void delete(String nodeId, String categoryId, Parameters parameters)
{
categories.unlinkNodeFromCategory(nodeId, categoryId, parameters);
}
}

View File

@@ -207,6 +207,35 @@ public class CategoriesImpl implements Categories
return categoryNodeRefs.stream().map(this::mapToCategory).collect(Collectors.toList());
}
@Override
public void unlinkNodeFromCategory(final String nodeId, final String categoryId, Parameters parameters)
{
final NodeRef categoryNodeRef = getCategoryNodeRef(categoryId);
final NodeRef contentNodeRef = nodes.validateNode(nodeId);
verifyChangePermission(contentNodeRef);
verifyNodeType(contentNodeRef);
if (isCategoryAspectMissing(contentNodeRef))
{
throw new InvalidArgumentException("Node with id: " + nodeId + " does not belong to a category");
}
if (isRootCategory(categoryNodeRef))
{
throw new InvalidArgumentException(NOT_A_VALID_CATEGORY, new String[]{categoryId});
}
final Collection<NodeRef> allCategories = removeCategory(contentNodeRef, categoryNodeRef);
if (allCategories.size()==0)
{
nodeService.removeAspect(contentNodeRef, ContentModel.ASPECT_GEN_CLASSIFIABLE);
nodeService.removeProperty(contentNodeRef, ContentModel.PROP_CATEGORIES);
return;
}
nodeService.setProperty(contentNodeRef, ContentModel.PROP_CATEGORIES, (Serializable) allCategories);
}
private void verifyAdminAuthority()
{
if (!authorityService.hasAdminAuthority())
@@ -369,6 +398,22 @@ public class CategoriesImpl implements Categories
return allCategories;
}
/**
* Remove specified category from present categories.
* @param contentNodeRef the nodeRef that contains the categories.
* @param categoryToRemove category that should be removed.
* @return updated category list.
*/
private Collection<NodeRef> removeCategory(final NodeRef contentNodeRef, final NodeRef categoryToRemove)
{
final Serializable currentCategories = nodeService.getProperty(contentNodeRef, ContentModel.PROP_CATEGORIES);
final Collection<NodeRef> actualCategories = DefaultTypeConverter.INSTANCE.getCollection(NodeRef.class, currentCategories);
final Collection<NodeRef> updatedCategories = new HashSet<>(actualCategories);
updatedCategories.remove(categoryToRemove);
return updatedCategories;
}
/**
* Add to or update node's property cm:categories containing linked category references.
*

View File

@@ -1020,6 +1020,40 @@ public class CategoriesImplTest
.isEqualTo(expectedLinkedCategories);
}
@Test
public void testUnlinkNodeFromCategory()
{
given(nodeServiceMock.hasAspect(CONTENT_NODE_REF,ContentModel.ASPECT_GEN_CLASSIFIABLE)).willReturn(true);
// when
objectUnderTest.unlinkNodeFromCategory(CONTENT_NODE_ID, CATEGORY_ID, parametersMock);
then(nodesMock).should().validateNode(CATEGORY_ID);
then(nodesMock).should().validateNode(CONTENT_NODE_ID);
then(permissionServiceMock).should().hasPermission(CONTENT_NODE_REF, PermissionService.CHANGE_PERMISSIONS);
then(permissionServiceMock).shouldHaveNoMoreInteractions();
then(typeConstraint).should().matches(CONTENT_NODE_REF);
then(typeConstraint).shouldHaveNoMoreInteractions();
then(nodeServiceMock).should().hasAspect(CONTENT_NODE_REF,ContentModel.ASPECT_GEN_CLASSIFIABLE);
then(nodeServiceMock).should().getProperty(CONTENT_NODE_REF, ContentModel.PROP_CATEGORIES);
then(nodeServiceMock).should().setProperty(eq(CONTENT_NODE_REF),eq(ContentModel.PROP_CATEGORIES),any());
}
@Test
public void testUnlinkNodeFromCategory_missingCategoryAspect()
{
given(nodeServiceMock.hasAspect(CONTENT_NODE_REF, ContentModel.ASPECT_GEN_CLASSIFIABLE)).willReturn(false);
//when
final Throwable actualException = catchThrowable(() -> objectUnderTest.unlinkNodeFromCategory(CONTENT_NODE_ID,CATEGORY_ID, parametersMock));
then(nodeServiceMock).should().hasAspect(CONTENT_NODE_REF,ContentModel.ASPECT_GEN_CLASSIFIABLE);
then(nodeServiceMock).shouldHaveNoMoreInteractions();
assertThat(actualException)
.isInstanceOf(InvalidArgumentException.class)
.hasMessageContaining("does not belong to a category");
}
@Test
public void testListCategoriesForNode()
{