mirror of
https://github.com/Alfresco/alfresco-community-repo.git
synced 2025-07-24 17:32:48 +00:00
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:
committed by
GitHub
parent
8a1b81ab61
commit
281009bc50
@@ -1146,4 +1146,15 @@ public class Node extends ModelRequest<Node>
|
||||
RestRequest request = RestRequest.requestWithBody(HttpMethod.POST, arrayToJson(categoryLinks), "nodes/{nodeId}/category-links", repoModel.getNodeRef());
|
||||
return restWrapper.processModels(RestCategoryModelsCollection.class, request);
|
||||
}
|
||||
|
||||
/**
|
||||
* Unlink content from a category performing a DELETE call on "nodes/{nodeId}/category-links/{categoryId}"
|
||||
*
|
||||
* @param categoryId the id of the category to be unlinked from content
|
||||
*/
|
||||
public void unlinkFromCategory(String categoryId)
|
||||
{
|
||||
RestRequest request = RestRequest.simpleRequest(HttpMethod.DELETE, "nodes/{nodeId}/category-links/{categoryId}", repoModel.getNodeRef(), categoryId);
|
||||
restWrapper.processEmptyModel(request);
|
||||
}
|
||||
}
|
||||
|
@@ -33,6 +33,7 @@ import static org.springframework.http.HttpStatus.CREATED;
|
||||
import static org.springframework.http.HttpStatus.FORBIDDEN;
|
||||
import static org.springframework.http.HttpStatus.METHOD_NOT_ALLOWED;
|
||||
import static org.springframework.http.HttpStatus.NOT_FOUND;
|
||||
import static org.springframework.http.HttpStatus.NO_CONTENT;
|
||||
|
||||
import javax.json.Json;
|
||||
import java.util.Collections;
|
||||
@@ -395,6 +396,115 @@ public class LinkToCategoriesTests extends CategoriesRestTest
|
||||
restClient.assertStatusCodeIs(BAD_REQUEST);
|
||||
}
|
||||
|
||||
/**
|
||||
* Try to link and unlink content from a created category
|
||||
*/
|
||||
@Test(groups = {TestGroup.REST_API})
|
||||
public void testUnlinkContentFromCategory()
|
||||
{
|
||||
STEP("Link content to created category and expect 201");
|
||||
final RestCategoryLinkBodyModel categoryLink = createCategoryLinkModelWithId(category.getId());
|
||||
final RestCategoryModel linkedCategory = restClient.authenticateUser(user).withCoreAPI().usingNode(file).linkToCategory(categoryLink);
|
||||
|
||||
restClient.assertStatusCodeIs(CREATED);
|
||||
linkedCategory.assertThat().isEqualTo(category);
|
||||
|
||||
STEP("Verify that category is present in file metadata");
|
||||
RestNodeModel fileNode = restClient.authenticateUser(user).withCoreAPI().usingNode(file).getNode();
|
||||
|
||||
fileNode.assertThat().field(ASPECTS_FIELD).contains("cm:generalclassifiable");
|
||||
fileNode.assertThat().field(PROPERTIES_FIELD).contains("cm:categories");
|
||||
fileNode.assertThat().field(PROPERTIES_FIELD).contains(category.getId());
|
||||
|
||||
STEP("Unlink content from created category and expect 204");
|
||||
restClient.authenticateUser(user).withCoreAPI().usingNode(file).unlinkFromCategory(category.getId());
|
||||
restClient.assertStatusCodeIs(NO_CONTENT);
|
||||
|
||||
STEP("Verify that category isn't present in file metadata");
|
||||
fileNode = restClient.authenticateUser(user).withCoreAPI().usingNode(file).getNode();
|
||||
|
||||
fileNode.assertThat().field(ASPECTS_FIELD).notContains("cm:generalclassifiable");
|
||||
fileNode.assertThat().field(PROPERTIES_FIELD).notContains("cm:categories");
|
||||
fileNode.assertThat().field(PROPERTIES_FIELD).notContains(category.getId());
|
||||
}
|
||||
|
||||
/**
|
||||
* Try to link content to multiple categories and try to unlink content from a single category
|
||||
* Other categories should remain intact and file should keep having "cm:generalclassifiable" aspect
|
||||
*/
|
||||
@Test(groups = {TestGroup.REST_API})
|
||||
public void testUnlinkContentFromCategory_multipleLinkedCategories()
|
||||
{
|
||||
STEP("Create second category under root");
|
||||
final RestCategoryModel secondCategory = prepareCategoryUnderRoot();
|
||||
|
||||
STEP("Link content to created categories and expect 201");
|
||||
final List<RestCategoryLinkBodyModel> categoryLinks = List.of(
|
||||
createCategoryLinkModelWithId(category.getId()),
|
||||
createCategoryLinkModelWithId(secondCategory.getId())
|
||||
);
|
||||
restClient.authenticateUser(user).withCoreAPI().usingNode(file).linkToCategories(categoryLinks);
|
||||
restClient.assertStatusCodeIs(CREATED);
|
||||
|
||||
STEP("Unlink content from first category and expect 204");
|
||||
restClient.authenticateUser(user).withCoreAPI().usingNode(file).unlinkFromCategory(category.getId());
|
||||
restClient.assertStatusCodeIs(NO_CONTENT);
|
||||
|
||||
STEP("Verify that second category is still present in file metadata");
|
||||
RestNodeModel fileNode = restClient.authenticateUser(user).withCoreAPI().usingNode(file).getNode();
|
||||
|
||||
fileNode.assertThat().field(ASPECTS_FIELD).contains("cm:generalclassifiable");
|
||||
fileNode.assertThat().field(PROPERTIES_FIELD).contains("cm:categories");
|
||||
fileNode.assertThat().field(PROPERTIES_FIELD).notContains(category.getId());
|
||||
fileNode.assertThat().field(PROPERTIES_FIELD).contains(secondCategory.getId());
|
||||
}
|
||||
|
||||
/**
|
||||
* Link content to a category as user with permission and try to unlink content using a user without change permissions
|
||||
*/
|
||||
@Test(groups = {TestGroup.REST_API})
|
||||
public void testUnlinkContentFromCategory_asUserWithoutChangePermissionAndGet403()
|
||||
{
|
||||
STEP("Link content to created category and expect 201");
|
||||
final RestCategoryLinkBodyModel categoryLink = createCategoryLinkModelWithId(category.getId());
|
||||
final RestCategoryModel linkedCategory = restClient.authenticateUser(user).withCoreAPI().usingNode(file).linkToCategory(categoryLink);
|
||||
|
||||
restClient.assertStatusCodeIs(CREATED);
|
||||
linkedCategory.assertThat().isEqualTo(category);
|
||||
|
||||
STEP("Create another user as a consumer for file");
|
||||
final UserModel consumer = dataUser.createRandomTestUser();
|
||||
allowPermissionsForUser(consumer.getUsername(), "Consumer", file);
|
||||
|
||||
STEP("Try to unlink content to a category using user without change permission and expect 403");
|
||||
restClient.authenticateUser(consumer).withCoreAPI().usingNode(file).unlinkFromCategory(category.getId());
|
||||
restClient.assertStatusCodeIs(FORBIDDEN);
|
||||
}
|
||||
|
||||
/**
|
||||
* Try to unlink content from a category that the node isn't assigned to and expect 404
|
||||
*/
|
||||
@Test(groups = { TestGroup.REST_API})
|
||||
public void testUnlinkContentFromCategory_unlinkFromNonLinkedToNodeCategory()
|
||||
{
|
||||
STEP("Try to unlink content from a category that the node isn't assigned to");
|
||||
final RestCategoryModel nonLinkedToNodeCategory = createCategoryModelWithId("non-linked-category-dummy-id");
|
||||
restClient.authenticateUser(user).withCoreAPI().usingNode(file).unlinkFromCategory(nonLinkedToNodeCategory.getId());
|
||||
restClient.assertStatusCodeIs(NOT_FOUND);
|
||||
}
|
||||
|
||||
/**
|
||||
* Try to unlink content from category using non-existing category id and expect 404 (Not Found)
|
||||
*/
|
||||
@Test(groups = { TestGroup.REST_API})
|
||||
public void testUnlinkContentFromCategory_usingNonExistingCategoryAndExpect404()
|
||||
{
|
||||
STEP("Try to unlink content from non-existent category and expect 404");
|
||||
final String nonExistentCategoryId = "non-existent-dummy-id";
|
||||
restClient.authenticateUser(user).withCoreAPI().usingNode(file).unlinkFromCategory(nonExistentCategoryId);
|
||||
restClient.assertStatusCodeIs(NOT_FOUND);
|
||||
}
|
||||
|
||||
private void allowPermissionsForUser(final String username, final String role, final FileModel file)
|
||||
{
|
||||
final String putPermissionsBody = Json.createObjectBuilder().add("permissions",
|
||||
|
@@ -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);
|
||||
}
|
||||
|
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -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.
|
||||
*
|
||||
|
@@ -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()
|
||||
{
|
||||
|
Reference in New Issue
Block a user