mirror of
https://github.com/Alfresco/alfresco-community-repo.git
synced 2025-07-24 17:32:48 +00:00
ACS-4034: Assign a piece of content to a category (#1655)
* ACS-4034: Assign a piece of content to a category - POST /nodes/{nodeId}/category-links - added also one TAS test for update category covering repeated name case
This commit is contained in:
@@ -2,7 +2,7 @@
|
||||
* #%L
|
||||
* Alfresco Remote API
|
||||
* %%
|
||||
* Copyright (C) 2005 - 2022 Alfresco Software Limited
|
||||
* 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
|
||||
@@ -53,4 +53,12 @@ public interface Categories
|
||||
|
||||
void deleteCategoryById(String id, Parameters parameters);
|
||||
|
||||
/**
|
||||
* Link node to categories. Node types allowed for categorization are specified within {@link org.alfresco.util.TypeConstraint}.
|
||||
*
|
||||
* @param nodeId Node ID.
|
||||
* @param categoryLinks Category IDs to which content should be linked to.
|
||||
* @return Linked to categories.
|
||||
*/
|
||||
List<Category> linkNodeToCategories(String nodeId, List<Category> categoryLinks);
|
||||
}
|
||||
|
@@ -0,0 +1,64 @@
|
||||
/*
|
||||
* #%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.categories;
|
||||
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import java.util.List;
|
||||
|
||||
import org.alfresco.rest.api.Categories;
|
||||
import org.alfresco.rest.api.model.Category;
|
||||
import org.alfresco.rest.api.nodes.NodesEntityResource;
|
||||
import org.alfresco.rest.framework.WebApiDescription;
|
||||
import org.alfresco.rest.framework.resource.RelationshipResource;
|
||||
import org.alfresco.rest.framework.resource.actions.interfaces.RelationshipResourceAction;
|
||||
import org.alfresco.rest.framework.resource.parameters.Parameters;
|
||||
|
||||
@RelationshipResource(name = "category-links", entityResource = NodesEntityResource.class, title = "Category links")
|
||||
public class NodesCategoryLinksRelation implements RelationshipResourceAction.Create<Category>
|
||||
{
|
||||
|
||||
private final Categories categories;
|
||||
|
||||
public NodesCategoryLinksRelation(Categories categories)
|
||||
{
|
||||
this.categories = categories;
|
||||
}
|
||||
|
||||
/**
|
||||
* POST /nodes/{nodeId}/category-links
|
||||
*/
|
||||
@WebApiDescription(
|
||||
title = "Link content node to categories",
|
||||
description = "Creates a link between a content node and categories",
|
||||
successStatus = HttpServletResponse.SC_CREATED
|
||||
)
|
||||
@Override
|
||||
public List<Category> create(String nodeId, List<Category> categoryLinks, Parameters parameters)
|
||||
{
|
||||
return categories.linkNodeToCategories(nodeId, categoryLinks);
|
||||
}
|
||||
}
|
@@ -2,7 +2,7 @@
|
||||
* #%L
|
||||
* Alfresco Remote API
|
||||
* %%
|
||||
* Copyright (C) 2005 - 2022 Alfresco Software Limited
|
||||
* 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
|
||||
@@ -27,8 +27,15 @@
|
||||
package org.alfresco.rest.api.impl;
|
||||
|
||||
import static org.alfresco.rest.api.Nodes.PATH_ROOT;
|
||||
import static org.alfresco.service.cmr.security.AccessStatus.ALLOWED;
|
||||
import static org.alfresco.service.cmr.security.PermissionService.CHANGE_PERMISSIONS;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.Collection;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.alfresco.model.ContentModel;
|
||||
@@ -39,6 +46,7 @@ import org.alfresco.rest.api.model.Node;
|
||||
import org.alfresco.rest.framework.core.exceptions.EntityNotFoundException;
|
||||
import org.alfresco.rest.framework.core.exceptions.InvalidArgumentException;
|
||||
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.ListPage;
|
||||
import org.alfresco.rest.framework.resource.parameters.Parameters;
|
||||
@@ -47,10 +55,13 @@ import org.alfresco.service.cmr.repository.ChildAssociationRef;
|
||||
import org.alfresco.service.cmr.repository.NodeRef;
|
||||
import org.alfresco.service.cmr.repository.NodeService;
|
||||
import org.alfresco.service.cmr.repository.StoreRef;
|
||||
import org.alfresco.service.cmr.repository.datatype.DefaultTypeConverter;
|
||||
import org.alfresco.service.cmr.search.CategoryService;
|
||||
import org.alfresco.service.cmr.security.AuthorityService;
|
||||
import org.alfresco.service.cmr.security.PermissionService;
|
||||
import org.alfresco.service.namespace.QName;
|
||||
import org.alfresco.service.namespace.RegexQNamePattern;
|
||||
import org.alfresco.util.TypeConstraint;
|
||||
import org.apache.commons.collections.CollectionUtils;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
@@ -59,20 +70,26 @@ public class CategoriesImpl implements Categories
|
||||
{
|
||||
static final String NOT_A_VALID_CATEGORY = "Node id does not refer to a valid category";
|
||||
static final String NO_PERMISSION_TO_MANAGE_A_CATEGORY = "Current user does not have permission to manage a category";
|
||||
static final String NO_PERMISSION_TO_READ_CONTENT = "Current user does not have read permission to content";
|
||||
static final String NOT_NULL_OR_EMPTY = "Category name must not be null or empty";
|
||||
static final String FIELD_NOT_MATCH = "Category field: %s does not match the original one";
|
||||
static final String INVALID_NODE_TYPE = "Cannot categorize this node type";
|
||||
|
||||
private final AuthorityService authorityService;
|
||||
private final CategoryService categoryService;
|
||||
private final Nodes nodes;
|
||||
private final NodeService nodeService;
|
||||
private final PermissionService permissionService;
|
||||
private final TypeConstraint typeConstraint;
|
||||
|
||||
public CategoriesImpl(AuthorityService authorityService, CategoryService categoryService, Nodes nodes, NodeService nodeService)
|
||||
public CategoriesImpl(AuthorityService authorityService, CategoryService categoryService, Nodes nodes, NodeService nodeService, PermissionService permissionService,
|
||||
TypeConstraint typeConstraint)
|
||||
{
|
||||
this.authorityService = authorityService;
|
||||
this.categoryService = categoryService;
|
||||
this.nodes = nodes;
|
||||
this.nodeService = nodeService;
|
||||
this.permissionService = permissionService;
|
||||
this.typeConstraint = typeConstraint;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -123,7 +140,7 @@ public class CategoriesImpl implements Categories
|
||||
throw new InvalidArgumentException(NOT_A_VALID_CATEGORY, new String[]{id});
|
||||
}
|
||||
|
||||
verifyCategoryFields(fixedCategoryModel);
|
||||
validateCategoryFields(fixedCategoryModel);
|
||||
|
||||
return mapToCategory(changeCategoryName(categoryNodeRef, fixedCategoryModel.getName()));
|
||||
}
|
||||
@@ -141,6 +158,35 @@ public class CategoriesImpl implements Categories
|
||||
nodeService.deleteNode(nodeRef);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Category> linkNodeToCategories(final String nodeId, final List<Category> categoryLinks)
|
||||
{
|
||||
if (CollectionUtils.isEmpty(categoryLinks))
|
||||
{
|
||||
throw new InvalidArgumentException(NOT_A_VALID_CATEGORY);
|
||||
}
|
||||
|
||||
final NodeRef contentNodeRef = nodes.validateNode(nodeId);
|
||||
verifyChangePermission(contentNodeRef);
|
||||
verifyNodeType(contentNodeRef);
|
||||
|
||||
final Collection<NodeRef> categoryNodeRefs = categoryLinks.stream()
|
||||
.filter(Objects::nonNull)
|
||||
.map(Category::getId)
|
||||
.filter(StringUtils::isNotEmpty)
|
||||
.map(this::getCategoryNodeRef)
|
||||
.collect(Collectors.toList());
|
||||
|
||||
if (CollectionUtils.isEmpty(categoryNodeRefs) || isRootCategoryPresent(categoryNodeRefs))
|
||||
{
|
||||
throw new InvalidArgumentException(NOT_A_VALID_CATEGORY);
|
||||
}
|
||||
|
||||
linkNodeToCategories(contentNodeRef, categoryNodeRefs);
|
||||
|
||||
return categoryNodeRefs.stream().map(this::mapToCategory).collect(Collectors.toList());
|
||||
}
|
||||
|
||||
private void verifyAdminAuthority()
|
||||
{
|
||||
if (!authorityService.hasAdminAuthority())
|
||||
@@ -149,6 +195,22 @@ public class CategoriesImpl implements Categories
|
||||
}
|
||||
}
|
||||
|
||||
private void verifyChangePermission(final NodeRef nodeRef)
|
||||
{
|
||||
if (permissionService.hasPermission(nodeRef, CHANGE_PERMISSIONS) != ALLOWED)
|
||||
{
|
||||
throw new PermissionDeniedException(NO_PERMISSION_TO_READ_CONTENT);
|
||||
}
|
||||
}
|
||||
|
||||
private void verifyNodeType(final NodeRef nodeRef)
|
||||
{
|
||||
if (!typeConstraint.matches(nodeRef))
|
||||
{
|
||||
throw new UnsupportedResourceOperationException(INVALID_NODE_TYPE);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This method gets category NodeRef for a given category id.
|
||||
* If '-root-' is passed as category id, then it's retrieved as a call to {@link org.alfresco.service.cmr.search.CategoryService#getRootCategoryNodeRef}
|
||||
@@ -181,7 +243,7 @@ public class CategoriesImpl implements Categories
|
||||
|
||||
private NodeRef createCategoryNodeRef(NodeRef parentNodeRef, Category c)
|
||||
{
|
||||
verifyCategoryFields(c);
|
||||
validateCategoryFields(c);
|
||||
return categoryService.createCategory(parentNodeRef, c.getName());
|
||||
}
|
||||
|
||||
@@ -236,15 +298,67 @@ public class CategoriesImpl implements Categories
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify if fixed category name is not empty.
|
||||
* Validate if fixed category name is not empty.
|
||||
*
|
||||
* @param fixedCategoryModel Fixed category model.
|
||||
*/
|
||||
private void verifyCategoryFields(final Category fixedCategoryModel)
|
||||
private void validateCategoryFields(final Category fixedCategoryModel)
|
||||
{
|
||||
if (StringUtils.isEmpty(fixedCategoryModel.getName()))
|
||||
{
|
||||
throw new InvalidArgumentException(NOT_NULL_OR_EMPTY);
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isRootCategoryPresent(final Collection<NodeRef> categoryNodeRefs)
|
||||
{
|
||||
return categoryNodeRefs.stream().anyMatch(this::isRootCategory);
|
||||
}
|
||||
|
||||
private boolean isCategoryAspectMissing(final NodeRef nodeRef)
|
||||
{
|
||||
return !nodeService.hasAspect(nodeRef, ContentModel.ASPECT_GEN_CLASSIFIABLE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Merge already present and new categories ignoring repeating ones.
|
||||
*
|
||||
* @param currentCategories Already present categories.
|
||||
* @param newCategories Categories which should be added.
|
||||
* @return Merged categories.
|
||||
*/
|
||||
private Collection<NodeRef> mergeCategories(final Serializable currentCategories, final Collection<NodeRef> newCategories)
|
||||
{
|
||||
if (currentCategories == null)
|
||||
{
|
||||
return newCategories;
|
||||
}
|
||||
|
||||
final Collection<NodeRef> actualCategories = DefaultTypeConverter.INSTANCE.getCollection(NodeRef.class, currentCategories);
|
||||
final Collection<NodeRef> allCategories = new HashSet<>(actualCategories);
|
||||
allCategories.addAll(newCategories);
|
||||
|
||||
return allCategories;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add to or update node's property cm:categories containing linked category references.
|
||||
*
|
||||
* @param nodeRef Node reference.
|
||||
* @param categoryNodeRefs Category node references.
|
||||
*/
|
||||
private void linkNodeToCategories(final NodeRef nodeRef, final Collection<NodeRef> categoryNodeRefs)
|
||||
{
|
||||
if (isCategoryAspectMissing(nodeRef))
|
||||
{
|
||||
final Map<QName, Serializable> properties = Map.of(ContentModel.PROP_CATEGORIES, (Serializable) categoryNodeRefs);
|
||||
nodeService.addAspect(nodeRef, ContentModel.ASPECT_GEN_CLASSIFIABLE, properties);
|
||||
}
|
||||
else
|
||||
{
|
||||
final Serializable currentCategories = nodeService.getProperty(nodeRef, ContentModel.PROP_CATEGORIES);
|
||||
final Collection<NodeRef> allCategories = mergeCategories(currentCategories, categoryNodeRefs);
|
||||
nodeService.setProperty(nodeRef, ContentModel.PROP_CATEGORIES, (Serializable) allCategories);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -45,6 +45,11 @@ public class Category
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public void setCategoryId(String categoryId)
|
||||
{
|
||||
this.id = categoryId;
|
||||
}
|
||||
|
||||
public String getName()
|
||||
{
|
||||
return name;
|
||||
|
@@ -834,6 +834,8 @@
|
||||
<constructor-arg name="categoryService" ref="CategoryService"/>
|
||||
<constructor-arg name="nodes" ref="nodes"/>
|
||||
<constructor-arg name="nodeService" ref="NodeService"/>
|
||||
<constructor-arg name="permissionService" ref="PermissionService"/>
|
||||
<constructor-arg name="typeConstraint" ref="nodeTypeConstraint"/>
|
||||
</bean>
|
||||
|
||||
<bean id="Categories" class="org.springframework.aop.framework.ProxyFactoryBean">
|
||||
@@ -1105,6 +1107,10 @@
|
||||
<constructor-arg name="categories" ref="Categories"/>
|
||||
</bean>
|
||||
|
||||
<bean class="org.alfresco.rest.api.categories.NodesCategoryLinksRelation">
|
||||
<constructor-arg name="categories" ref="Categories"/>
|
||||
</bean>
|
||||
|
||||
<bean class="org.alfresco.rest.api.tags.TagsEntityResource">
|
||||
<property name="tags" ref="Tags" />
|
||||
</bean>
|
||||
|
@@ -2,7 +2,7 @@
|
||||
* #%L
|
||||
* Alfresco Remote API
|
||||
* %%
|
||||
* Copyright (C) 2005 - 2022 Alfresco Software Limited
|
||||
* 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
|
||||
@@ -27,24 +27,31 @@
|
||||
package org.alfresco.rest.api.impl;
|
||||
|
||||
import static org.alfresco.rest.api.Nodes.PATH_ROOT;
|
||||
import static org.alfresco.rest.api.impl.CategoriesImpl.INVALID_NODE_TYPE;
|
||||
import static org.alfresco.rest.api.impl.CategoriesImpl.NOT_A_VALID_CATEGORY;
|
||||
import static org.alfresco.rest.api.impl.CategoriesImpl.NOT_NULL_OR_EMPTY;
|
||||
import static org.alfresco.rest.api.impl.CategoriesImpl.NO_PERMISSION_TO_READ_CONTENT;
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
|
||||
import static org.assertj.core.api.Assertions.catchThrowable;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertThrows;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.anyBoolean;
|
||||
import static org.mockito.ArgumentMatchers.eq;
|
||||
import static org.mockito.ArgumentMatchers.isNull;
|
||||
import static org.mockito.BDDMockito.given;
|
||||
import static org.mockito.BDDMockito.then;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.times;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.IntStream;
|
||||
|
||||
@@ -55,6 +62,7 @@ import org.alfresco.rest.api.model.Node;
|
||||
import org.alfresco.rest.framework.core.exceptions.EntityNotFoundException;
|
||||
import org.alfresco.rest.framework.core.exceptions.InvalidArgumentException;
|
||||
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.Parameters;
|
||||
import org.alfresco.service.cmr.repository.ChildAssociationRef;
|
||||
@@ -62,9 +70,14 @@ import org.alfresco.service.cmr.repository.NodeRef;
|
||||
import org.alfresco.service.cmr.repository.NodeService;
|
||||
import org.alfresco.service.cmr.repository.StoreRef;
|
||||
import org.alfresco.service.cmr.search.CategoryService;
|
||||
import org.alfresco.service.cmr.security.AccessStatus;
|
||||
import org.alfresco.service.cmr.security.AuthorityService;
|
||||
import org.alfresco.service.cmr.security.PermissionService;
|
||||
import org.alfresco.service.namespace.NamespaceService;
|
||||
import org.alfresco.service.namespace.QName;
|
||||
import org.alfresco.service.namespace.RegexQNamePattern;
|
||||
import org.alfresco.util.TypeConstraint;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
@@ -81,6 +94,8 @@ public class CategoriesImplTest
|
||||
private static final String CAT_ROOT_NODE_ID = "cat-root-node-id";
|
||||
private static final NodeRef CATEGORY_NODE_REF = createNodeRefWithId(CATEGORY_ID);
|
||||
private static final Category CATEGORY = createDefaultCategoryWithName(CATEGORY_NAME);
|
||||
private static final String CONTENT_NODE_ID = "content-node-id";
|
||||
private static final NodeRef CONTENT_NODE_REF = createNodeRefWithId(CONTENT_NODE_ID);
|
||||
|
||||
@Mock
|
||||
private Nodes nodesMock;
|
||||
@@ -96,6 +111,10 @@ public class CategoriesImplTest
|
||||
private ChildAssociationRef dummyChildAssociationRefMock;
|
||||
@Mock
|
||||
private ChildAssociationRef categoryChildAssociationRefMock;
|
||||
@Mock
|
||||
private PermissionService permissionServiceMock;
|
||||
@Mock
|
||||
private TypeConstraint typeConstraint;
|
||||
|
||||
@InjectMocks
|
||||
private CategoriesImpl objectUnderTest;
|
||||
@@ -104,8 +123,11 @@ public class CategoriesImplTest
|
||||
public void setUp() throws Exception
|
||||
{
|
||||
given(authorityServiceMock.hasAdminAuthority()).willReturn(true);
|
||||
given(nodesMock.validateNode(eq(CATEGORY_ID))).willReturn(CATEGORY_NODE_REF);
|
||||
given(nodesMock.validateNode(CATEGORY_ID)).willReturn(CATEGORY_NODE_REF);
|
||||
given(nodesMock.validateNode(CONTENT_NODE_ID)).willReturn(CONTENT_NODE_REF);
|
||||
given(nodesMock.isSubClass(any(), any(), anyBoolean())).willReturn(true);
|
||||
given(typeConstraint.matches(any())).willReturn(true);
|
||||
given(permissionServiceMock.hasPermission(any(), any())).willReturn(AccessStatus.ALLOWED);
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -774,6 +796,208 @@ public class CategoriesImplTest
|
||||
.isEqualTo(expectedCategory);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testLinkNodeToCategories_withoutCategoryAspect()
|
||||
{
|
||||
final List<Category> categoryLinks = List.of(CATEGORY);
|
||||
final NodeRef categoryParentNodeRef = createNodeRefWithId(PARENT_ID);
|
||||
final ChildAssociationRef parentAssociation = createAssociationOf(categoryParentNodeRef, CATEGORY_NODE_REF);
|
||||
given(nodesMock.getNode(any())).willReturn(prepareCategoryNode());
|
||||
given(nodeServiceMock.getPrimaryParent(any())).willReturn(parentAssociation);
|
||||
|
||||
// when
|
||||
final List<Category> actualLinkedCategories = objectUnderTest.linkNodeToCategories(CONTENT_NODE_ID, categoryLinks);
|
||||
|
||||
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(nodesMock).should().validateNode(CATEGORY_ID);
|
||||
then(nodesMock).should().getNode(CATEGORY_ID);
|
||||
then(nodesMock).should().isSubClass(CATEGORY_NODE_REF, ContentModel.TYPE_CATEGORY, false);
|
||||
then(nodesMock).shouldHaveNoMoreInteractions();
|
||||
then(nodeServiceMock).should().getChildAssocs(CATEGORY_NODE_REF, RegexQNamePattern.MATCH_ALL, RegexQNamePattern.MATCH_ALL, false);
|
||||
then(nodeServiceMock).should().getPrimaryParent(CATEGORY_NODE_REF);
|
||||
then(nodeServiceMock).should().getParentAssocs(CATEGORY_NODE_REF);
|
||||
then(nodeServiceMock).should().hasAspect(CONTENT_NODE_REF, ContentModel.ASPECT_GEN_CLASSIFIABLE);
|
||||
final Map<QName, Serializable> expectedProperties = Map.of(ContentModel.PROP_CATEGORIES, (Serializable) List.of(CATEGORY_NODE_REF));
|
||||
then(nodeServiceMock).should().addAspect(CONTENT_NODE_REF, ContentModel.ASPECT_GEN_CLASSIFIABLE, expectedProperties);
|
||||
then(nodeServiceMock).should().getParentAssocs(categoryParentNodeRef);
|
||||
then(nodeServiceMock).shouldHaveNoMoreInteractions();
|
||||
final List<Category> expectedLinkedCategories = List.of(CATEGORY);
|
||||
assertThat(actualLinkedCategories)
|
||||
.isNotNull().usingRecursiveComparison()
|
||||
.isEqualTo(expectedLinkedCategories);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testLinkNodeToCategories_withPresentCategoryAspect()
|
||||
{
|
||||
final NodeRef categoryParentNodeRef = createNodeRefWithId(PARENT_ID);
|
||||
final ChildAssociationRef parentAssociation = createAssociationOf(categoryParentNodeRef, CATEGORY_NODE_REF);
|
||||
given(nodesMock.getNode(any())).willReturn(prepareCategoryNode());
|
||||
given(nodeServiceMock.getPrimaryParent(any())).willReturn(parentAssociation);
|
||||
given(nodeServiceMock.hasAspect(any(), any())).willReturn(true);
|
||||
|
||||
// when
|
||||
final List<Category> actualLinkedCategories = objectUnderTest.linkNodeToCategories(CONTENT_NODE_ID, List.of(CATEGORY));
|
||||
|
||||
then(nodesMock).should().getNode(CATEGORY_ID);
|
||||
then(nodeServiceMock).should().getChildAssocs(CATEGORY_NODE_REF, RegexQNamePattern.MATCH_ALL, RegexQNamePattern.MATCH_ALL, false);
|
||||
then(nodeServiceMock).should().getPrimaryParent(CATEGORY_NODE_REF);
|
||||
then(nodeServiceMock).should().getParentAssocs(CATEGORY_NODE_REF);
|
||||
then(nodeServiceMock).should().hasAspect(CONTENT_NODE_REF, ContentModel.ASPECT_GEN_CLASSIFIABLE);
|
||||
then(nodeServiceMock).should().getProperty(CONTENT_NODE_REF, ContentModel.PROP_CATEGORIES);
|
||||
final Serializable expectedCategories = (Serializable) List.of(CATEGORY_NODE_REF);
|
||||
then(nodeServiceMock).should().setProperty(CONTENT_NODE_REF, ContentModel.PROP_CATEGORIES, expectedCategories);
|
||||
then(nodeServiceMock).should().getParentAssocs(categoryParentNodeRef);
|
||||
then(nodeServiceMock).shouldHaveNoMoreInteractions();
|
||||
final List<Category> expectedLinkedCategories = List.of(CATEGORY);
|
||||
assertThat(actualLinkedCategories)
|
||||
.isNotNull().usingRecursiveComparison()
|
||||
.isEqualTo(expectedLinkedCategories);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testLinkNodeToCategories_withMultipleCategoryIds()
|
||||
{
|
||||
final String secondCategoryId = "second-category-id";
|
||||
final String secondCategoryName = "secondCategoryName";
|
||||
final NodeRef secondCategoryNodeRef = createNodeRefWithId(secondCategoryId);
|
||||
final Category secondCategoryLink = Category.builder().id(secondCategoryId).create();
|
||||
final List<Category> categoryLinks = List.of(CATEGORY, secondCategoryLink);
|
||||
final NodeRef categoryParentNodeRef = createNodeRefWithId(PARENT_ID);
|
||||
final ChildAssociationRef categoryParentAssociation = createAssociationOf(categoryParentNodeRef, CATEGORY_NODE_REF);
|
||||
final ChildAssociationRef secondCategoryParentAssociation = createAssociationOf(categoryParentNodeRef, secondCategoryNodeRef);
|
||||
given(nodesMock.validateNode(secondCategoryId)).willReturn(secondCategoryNodeRef);
|
||||
given(nodesMock.getNode(any())).willReturn(prepareCategoryNode(), prepareCategoryNode(secondCategoryName));
|
||||
given(nodeServiceMock.getPrimaryParent(any())).willReturn(categoryParentAssociation, secondCategoryParentAssociation);
|
||||
|
||||
// when
|
||||
final List<Category> actualLinkedCategories = objectUnderTest.linkNodeToCategories(CONTENT_NODE_ID, categoryLinks);
|
||||
|
||||
then(nodesMock).should().validateNode(CATEGORY_ID);
|
||||
then(nodesMock).should().validateNode(secondCategoryId);
|
||||
then(nodesMock).should().isSubClass(CATEGORY_NODE_REF, ContentModel.TYPE_CATEGORY, false);
|
||||
then(nodesMock).should().isSubClass(secondCategoryNodeRef, ContentModel.TYPE_CATEGORY, false);
|
||||
final Map<QName, Serializable> expectedProperties = Map.of(ContentModel.PROP_CATEGORIES, (Serializable) List.of(CATEGORY_NODE_REF, secondCategoryNodeRef));
|
||||
then(nodeServiceMock).should().addAspect(CONTENT_NODE_REF, ContentModel.ASPECT_GEN_CLASSIFIABLE, expectedProperties);
|
||||
final List<Category> expectedLinkedCategories = List.of(
|
||||
CATEGORY,
|
||||
Category.builder().id(secondCategoryId).name(secondCategoryName).parentId(PARENT_ID).create()
|
||||
);
|
||||
assertThat(actualLinkedCategories)
|
||||
.isNotNull().usingRecursiveComparison()
|
||||
.isEqualTo(expectedLinkedCategories);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testLinkNodeToCategories_withPreviouslyLinkedCategories()
|
||||
{
|
||||
final String otherCategoryId = "other-category-id";
|
||||
final NodeRef otherCategoryNodeRef = createNodeRefWithId(otherCategoryId);
|
||||
final Serializable previousCategories = (Serializable) List.of(otherCategoryNodeRef);
|
||||
final NodeRef categoryParentNodeRef = createNodeRefWithId(PARENT_ID);
|
||||
final ChildAssociationRef parentAssociation = createAssociationOf(categoryParentNodeRef, CATEGORY_NODE_REF);
|
||||
given(nodesMock.getNode(any())).willReturn(prepareCategoryNode());
|
||||
given(nodeServiceMock.getPrimaryParent(any())).willReturn(parentAssociation);
|
||||
given(nodeServiceMock.hasAspect(any(), any())).willReturn(true);
|
||||
given(nodeServiceMock.getProperty(any(), eq(ContentModel.PROP_CATEGORIES))).willReturn(previousCategories);
|
||||
|
||||
// when
|
||||
final List<Category> actualLinkedCategories = objectUnderTest.linkNodeToCategories(CONTENT_NODE_ID, List.of(CATEGORY));
|
||||
|
||||
final Serializable expectedCategories = (Serializable) Set.of(otherCategoryNodeRef, CATEGORY_NODE_REF);
|
||||
then(nodeServiceMock).should().setProperty(CONTENT_NODE_REF, ContentModel.PROP_CATEGORIES, expectedCategories);
|
||||
final List<Category> expectedLinkedCategories = List.of(CATEGORY);
|
||||
assertThat(actualLinkedCategories)
|
||||
.isNotNull().usingRecursiveComparison()
|
||||
.isEqualTo(expectedLinkedCategories);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testLinkNodeToCategories_withInvalidNodeId()
|
||||
{
|
||||
given(nodesMock.validateNode(CONTENT_NODE_ID)).willThrow(EntityNotFoundException.class);
|
||||
|
||||
// when
|
||||
final Throwable actualException = catchThrowable(() -> objectUnderTest.linkNodeToCategories(CONTENT_NODE_ID, List.of(CATEGORY)));
|
||||
|
||||
then(nodesMock).should().validateNode(CONTENT_NODE_ID);
|
||||
then(permissionServiceMock).shouldHaveNoInteractions();
|
||||
then(nodeServiceMock).shouldHaveNoInteractions();
|
||||
assertThat(actualException)
|
||||
.isInstanceOf(EntityNotFoundException.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testLinkNodeToCategories_withoutPermission()
|
||||
{
|
||||
given(permissionServiceMock.hasPermission(any(), any())).willReturn(AccessStatus.DENIED);
|
||||
|
||||
// when
|
||||
final Throwable actualException = catchThrowable(() -> objectUnderTest.linkNodeToCategories(CONTENT_NODE_ID, List.of(CATEGORY)));
|
||||
|
||||
then(nodesMock).should().validateNode(CONTENT_NODE_ID);
|
||||
then(permissionServiceMock).should().hasPermission(CONTENT_NODE_REF, PermissionService.CHANGE_PERMISSIONS);
|
||||
then(nodeServiceMock).shouldHaveNoInteractions();
|
||||
assertThat(actualException)
|
||||
.isInstanceOf(PermissionDeniedException.class)
|
||||
.hasMessageContaining(NO_PERMISSION_TO_READ_CONTENT);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testLinkContentNodeToCategories_withInvalidNodeType()
|
||||
{
|
||||
given(typeConstraint.matches(any())).willReturn(false);
|
||||
|
||||
|
||||
// when
|
||||
final Throwable actualException = catchThrowable(() -> objectUnderTest.linkNodeToCategories(CONTENT_NODE_ID, List.of(CATEGORY)));
|
||||
|
||||
then(typeConstraint).should().matches(CONTENT_NODE_REF);
|
||||
then(nodeServiceMock).shouldHaveNoInteractions();
|
||||
assertThat(actualException)
|
||||
.isInstanceOf(UnsupportedResourceOperationException.class)
|
||||
.hasMessageContaining(INVALID_NODE_TYPE);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testLinkNodeToCategories_withEmptyLinks()
|
||||
{
|
||||
final List<Category> categoryLinks = Collections.emptyList();
|
||||
|
||||
// when
|
||||
final Throwable actualException = catchThrowable(() -> objectUnderTest.linkNodeToCategories(CONTENT_NODE_ID, categoryLinks));
|
||||
|
||||
then(nodesMock).shouldHaveNoInteractions();
|
||||
then(permissionServiceMock).shouldHaveNoInteractions();
|
||||
then(nodeServiceMock).shouldHaveNoInteractions();
|
||||
assertThat(actualException)
|
||||
.isInstanceOf(InvalidArgumentException.class)
|
||||
.hasMessageContaining(NOT_A_VALID_CATEGORY);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testLinkNodeToCategories_withInvalidCategoryIds()
|
||||
{
|
||||
final Category categoryLinkWithNullId = Category.builder().id(null).create();
|
||||
final Category categoryLinkWithEmptyId = Category.builder().id(StringUtils.EMPTY).create();
|
||||
final List<Category> categoryLinks = new ArrayList<>();
|
||||
categoryLinks.add(categoryLinkWithNullId);
|
||||
categoryLinks.add(null);
|
||||
categoryLinks.add(categoryLinkWithEmptyId);
|
||||
|
||||
// when
|
||||
final Throwable actualException = catchThrowable(() -> objectUnderTest.linkNodeToCategories(CONTENT_NODE_ID, categoryLinks));
|
||||
|
||||
then(nodeServiceMock).shouldHaveNoInteractions();
|
||||
assertThat(actualException)
|
||||
.isInstanceOf(InvalidArgumentException.class)
|
||||
.hasMessageContaining(NOT_A_VALID_CATEGORY);
|
||||
}
|
||||
|
||||
private Node prepareCategoryNode(final String name, final String id, final NodeRef parentNodeRef)
|
||||
{
|
||||
final Node categoryNode = new Node();
|
||||
@@ -785,8 +1009,7 @@ public class CategoriesImplTest
|
||||
|
||||
private Node prepareCategoryNode(final String name)
|
||||
{
|
||||
final NodeRef parentNodeRef = new NodeRef(StoreRef.STORE_REF_WORKSPACE_SPACESSTORE, PARENT_ID);
|
||||
return prepareCategoryNode(name, CATEGORY_ID, parentNodeRef);
|
||||
return prepareCategoryNode(name, CATEGORY_ID, createNodeRefWithId(PARENT_ID));
|
||||
}
|
||||
|
||||
private Node prepareCategoryNode()
|
||||
@@ -858,7 +1081,12 @@ public class CategoriesImplTest
|
||||
|
||||
private static QName createCmQNameOf(final String name)
|
||||
{
|
||||
return QName.createQName(ContentModel.TYPE_CATEGORY.getNamespaceURI(), QName.createValidLocalName(name));
|
||||
return QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, QName.createValidLocalName(name));
|
||||
}
|
||||
|
||||
private static ChildAssociationRef createAssociationOf(final NodeRef parentNode, final NodeRef childNode)
|
||||
{
|
||||
return createAssociationOf(parentNode, childNode, null);
|
||||
}
|
||||
|
||||
private static ChildAssociationRef createAssociationOf(final NodeRef parentNode, final NodeRef childNode, final QName childNodeName)
|
||||
|
Reference in New Issue
Block a user