From 0dde0c52c5aafd2ac6cf45ff350084269fd238d8 Mon Sep 17 00:00:00 2001 From: Andrei Rebegea Date: Wed, 14 Jun 2017 16:38:50 +0000 Subject: [PATCH] Merged 5.2.N (5.2.2) to HEAD (5.2) 133515 cturlica: REPO-1300: Retrieve list of groups - added groups api git-svn-id: https://svn.alfresco.com/repos/alfresco-enterprise/alfresco/HEAD/root@137305 c4b6b30b-aa2e-2d43-bbcb-ca4b014f7261 --- config/alfresco/public-rest-context.xml | 24 ++ source/java/org/alfresco/rest/api/Groups.java | 56 +++ .../rest/api/groups/GroupsEntityResource.java | 65 +++ .../rest/api/groups/package-info.java | 29 ++ .../alfresco/rest/api/impl/GroupsImpl.java | 380 ++++++++++++++++++ .../org/alfresco/rest/api/model/Group.java | 144 +++++++ .../org/alfresco/rest/api/tests/ApiTest.java | 6 +- .../alfresco/rest/api/tests/GroupsTest.java | 380 ++++++++++++++++++ .../api/tests/client/PublicApiClient.java | 32 ++ .../rest/api/tests/client/data/Group.java | 127 ++++++ 10 files changed, 1240 insertions(+), 3 deletions(-) create mode 100644 source/java/org/alfresco/rest/api/Groups.java create mode 100644 source/java/org/alfresco/rest/api/groups/GroupsEntityResource.java create mode 100644 source/java/org/alfresco/rest/api/groups/package-info.java create mode 100644 source/java/org/alfresco/rest/api/impl/GroupsImpl.java create mode 100644 source/java/org/alfresco/rest/api/model/Group.java create mode 100644 source/test-java/org/alfresco/rest/api/tests/GroupsTest.java create mode 100644 source/test-java/org/alfresco/rest/api/tests/client/data/Group.java diff --git a/config/alfresco/public-rest-context.xml b/config/alfresco/public-rest-context.xml index 5fd0e4f7d3..c37d90e2fd 100644 --- a/config/alfresco/public-rest-context.xml +++ b/config/alfresco/public-rest-context.xml @@ -1332,4 +1332,28 @@ + + + + + + + + + + org.alfresco.rest.api.Groups + + + + + + + + + + + + + + diff --git a/source/java/org/alfresco/rest/api/Groups.java b/source/java/org/alfresco/rest/api/Groups.java new file mode 100644 index 0000000000..5b829e246e --- /dev/null +++ b/source/java/org/alfresco/rest/api/Groups.java @@ -0,0 +1,56 @@ +/* + * #%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; + +import org.alfresco.rest.api.model.Group; +import org.alfresco.rest.framework.resource.parameters.CollectionWithPagingInfo; +import org.alfresco.rest.framework.resource.parameters.Parameters; + +/** + * Groups API + * + * @author cturlica + */ +public interface Groups +{ + String PARAM_ID = "id"; + String PARAM_DISPLAY_NAME = "displayName"; + String PARAM_INCLUDE_PARENT_IDS = "parentIds"; + String PARAM_INCLUDE_ZONES = "zones"; + String PARAM_IS_ROOT = "isRoot"; + + /** + * Gets a list of groups. + * + * @param parameters the {@link Parameters} object to get the parameters passed into the request + * including: + * - filter, sort & paging params (where, orderBy, skipCount, maxItems) + * - incFiles, incFolders (both true by default) + * @return a paged list of {@code org.alfresco.rest.api.model.Group} objects + */ + CollectionWithPagingInfo getGroups(Parameters parameters); + +} diff --git a/source/java/org/alfresco/rest/api/groups/GroupsEntityResource.java b/source/java/org/alfresco/rest/api/groups/GroupsEntityResource.java new file mode 100644 index 0000000000..6e8b52a0ec --- /dev/null +++ b/source/java/org/alfresco/rest/api/groups/GroupsEntityResource.java @@ -0,0 +1,65 @@ +/* + * #%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.groups; + +import org.alfresco.rest.api.Groups; +import org.alfresco.rest.api.model.Group; +import org.alfresco.rest.framework.WebApiDescription; +import org.alfresco.rest.framework.resource.EntityResource; +import org.alfresco.rest.framework.resource.actions.interfaces.EntityResourceAction; +import org.alfresco.rest.framework.resource.parameters.CollectionWithPagingInfo; +import org.alfresco.rest.framework.resource.parameters.Parameters; +import org.alfresco.util.ParameterCheck; +import org.springframework.beans.factory.InitializingBean; + +/** + * An implementation of an Entity Resource for a Group + * + * @author cturlica + */ +@EntityResource(name = "groups", title = "Groups") +public class GroupsEntityResource implements EntityResourceAction.Read, InitializingBean +{ + private Groups groups; + + public void setGroups(Groups groups) + { + this.groups = groups; + } + + @Override + public void afterPropertiesSet() + { + ParameterCheck.mandatory("groups", this.groups); + } + + @Override + @WebApiDescription(title = "Get List of Groups", description = "Get List of Groups") + public CollectionWithPagingInfo readAll(Parameters params) + { + return groups.getGroups(params); + } +} \ No newline at end of file diff --git a/source/java/org/alfresco/rest/api/groups/package-info.java b/source/java/org/alfresco/rest/api/groups/package-info.java new file mode 100644 index 0000000000..884613cec9 --- /dev/null +++ b/source/java/org/alfresco/rest/api/groups/package-info.java @@ -0,0 +1,29 @@ +/* + * #%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% + */ +@WebApi(name="alfresco", scope=Api.SCOPE.PUBLIC, version=1) +package org.alfresco.rest.api.groups; +import org.alfresco.rest.framework.Api; +import org.alfresco.rest.framework.WebApi; \ No newline at end of file diff --git a/source/java/org/alfresco/rest/api/impl/GroupsImpl.java b/source/java/org/alfresco/rest/api/impl/GroupsImpl.java new file mode 100644 index 0000000000..67b1404fd2 --- /dev/null +++ b/source/java/org/alfresco/rest/api/impl/GroupsImpl.java @@ -0,0 +1,380 @@ +/* + * #%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.text.Collator; +import java.util.AbstractList; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.Comparator; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.stream.Collectors; + +import org.alfresco.query.CannedQueryPageDetails; +import org.alfresco.query.PagingRequest; +import org.alfresco.query.PagingResults; +import org.alfresco.repo.security.authority.AuthorityInfo; +import org.alfresco.repo.security.authority.UnknownAuthorityException; +import org.alfresco.rest.antlr.WhereClauseParser; +import org.alfresco.rest.api.Groups; +import org.alfresco.rest.api.model.Group; +import org.alfresco.rest.framework.core.exceptions.EntityNotFoundException; +import org.alfresco.rest.framework.core.exceptions.InvalidArgumentException; +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.rest.framework.resource.parameters.SortColumn; +import org.alfresco.rest.framework.resource.parameters.where.Query; +import org.alfresco.rest.framework.resource.parameters.where.QueryHelper; +import org.alfresco.rest.workflow.api.impl.MapBasedQueryWalkerOrSupported; +import org.alfresco.service.cmr.security.AuthorityService; +import org.alfresco.service.cmr.security.AuthorityType; +import org.alfresco.util.Pair; +import org.springframework.extensions.surf.util.I18NUtil; + +/** + * Centralises access to groups services and maps between representations. + * + * @author cturlica + */ +public class GroupsImpl implements Groups +{ + private static final String DISPLAY_NAME = "displayName"; + private static final String SHORT_NAME = "shortName"; + // private static final String AUTHORITY_NAME = "authorityName"; + + private final static Map SORT_PARAMS_TO_NAMES; + static + { + Map aMap = new HashMap<>(2); + aMap.put(PARAM_ID, SHORT_NAME); + aMap.put(PARAM_DISPLAY_NAME, DISPLAY_NAME); + + SORT_PARAMS_TO_NAMES = Collections.unmodifiableMap(aMap); + } + + // List groups filtering (via where clause) + private final static Set LIST_GROUPS_EQUALS_QUERY_PROPERTIES = new HashSet<>(Arrays.asList(new String[] { PARAM_IS_ROOT })); + + protected AuthorityService authorityService; + + public AuthorityService getAuthorityService() + { + return authorityService; + } + + public void setAuthorityService(AuthorityService authorityService) + { + this.authorityService = authorityService; + } + + public CollectionWithPagingInfo getGroups(final Parameters parameters) + { + final List includeParam = parameters.getInclude(); + + Paging paging = parameters.getPaging(); + + // Retrieve sort column. This is limited for now to sort column due to + // v0 api implementation. Should be improved in the future. + Pair sortProp = getGroupsSortProp(parameters); + + // Parse where clause properties. + Boolean isRootParam = null; + Query q = parameters.getQuery(); + if (q != null) + { + MapBasedQueryWalkerOrSupported propertyWalker = new MapBasedQueryWalkerOrSupported(LIST_GROUPS_EQUALS_QUERY_PROPERTIES, null); + QueryHelper.walk(q, propertyWalker); + + isRootParam = propertyWalker.getProperty(PARAM_IS_ROOT, WhereClauseParser.EQUALS, Boolean.class); + } + + final AuthorityType authorityType = AuthorityType.GROUP; + final Set rootAuthorities = getAllRootAuthorities(authorityType); + + PagingResults pagingResult = getAuthoritiesInfo(authorityType, isRootParam, rootAuthorities, sortProp, paging); + + // Create response. + final List page = pagingResult.getPage(); + int totalItems = pagingResult.getTotalResultCount().getFirst(); + List groups = new AbstractList() + { + @Override + public Group get(int index) + { + AuthorityInfo authorityInfo = page.get(index); + return getGroup(authorityInfo, includeParam, rootAuthorities); + } + + @Override + public int size() + { + return page.size(); + } + }; + + return CollectionWithPagingInfo.asPaged(paging, groups, pagingResult.hasMoreItems(), totalItems); + } + + private PagingResults getAuthoritiesInfo(AuthorityType authorityType, Boolean isRootParam, Set rootAuthorities, Pair sortProp, + Paging paging) + { + PagingResults pagingResult; + if (isRootParam != null) + { + List groupList; + + if (isRootParam) + { + // Limit the post processing work by using the already loaded + // list of root authorities. + groupList = new ArrayList<>(rootAuthorities.size()); + groupList.addAll(rootAuthorities.stream().map(this::getAuthorityInfo).collect(Collectors.toList())); + + // Post process sorting - this should be moved to service + // layer. It is done here because sorting is not supported at + // service layer. + AuthorityInfoComparator authorityComparator = new AuthorityInfoComparator(sortProp.getFirst(), sortProp.getSecond()); + Collections.sort(groupList, authorityComparator); + } + else + { + PagingRequest pagingNoMaxItems = new PagingRequest(CannedQueryPageDetails.DEFAULT_PAGE_SIZE); + + // Get authorities using canned query but without using + // the requested paginating now because we need to filter out + // the root authorities. + PagingResults nonPagingResult = authorityService.getAuthoritiesInfo(authorityType, null, null, sortProp.getFirst(), sortProp.getSecond(), + pagingNoMaxItems); + + // Post process filtering - this should be moved to service + // layer. It is done here because filtering by "isRoot" is not + // supported at service layer. + groupList = nonPagingResult.getPage(); + if (groupList != null) + { + for (Iterator i = groupList.iterator(); i.hasNext();) + { + AuthorityInfo authorityInfo = i.next(); + if (!isRootParam.equals(isRootAuthority(rootAuthorities, authorityInfo.getAuthorityName()))) + { + i.remove(); + } + } + } + } + + // Post process paging - this should be moved to service layer. + pagingResult = Util.wrapPagingResults(paging, groupList); + } + else + { + PagingRequest pagingRequest = Util.getPagingRequest(paging); + + // Get authorities using canned query. + pagingResult = authorityService.getAuthoritiesInfo(authorityType, null, null, sortProp.getFirst(), sortProp.getSecond(), pagingRequest); + } + return pagingResult; + } + + private Set getAllRootAuthorities(AuthorityType authorityType) + { + Set authorities; + try + { + authorities = authorityService.getAllRootAuthorities(authorityType); + } + catch (UnknownAuthorityException e) + { + authorities = Collections.emptySet(); + } + + return authorities; + } + + /** + * Retrieve authority info by name. Node id field isn't used at this time + * and it is set to null. + * + * @param id + * The authority name. + * @return The authority info. + */ + private AuthorityInfo getAuthorityInfo(String id) + { + if (id == null || id.isEmpty()) + { + throw new InvalidArgumentException("id is null or empty"); + } + + if (!authorityService.authorityExists(id)) + { + throw new EntityNotFoundException(id); + } + + String authorityDisplayName = authorityService.getAuthorityDisplayName(id); + + return new AuthorityInfo(null, authorityDisplayName, id); + } + + private Group getGroup(AuthorityInfo authorityInfo, List includeParam, Set rootAuthorities) + { + if (authorityInfo == null) + { + return null; + } + + Group group = new Group(); + group.setId(authorityInfo.getAuthorityName()); + group.setDisplayName(authorityInfo.getAuthorityDisplayName()); + group.setIsRoot(isRootAuthority(rootAuthorities, authorityInfo.getAuthorityName())); + + // Optionally include + if (includeParam != null) + { + if (includeParam.contains(PARAM_INCLUDE_PARENT_IDS)) + { + Set containingAuthorities = authorityService.getContainingAuthorities(AuthorityType.GROUP, authorityInfo.getAuthorityName(), true); + group.setParentIds(containingAuthorities); + } + + if (includeParam.contains(PARAM_INCLUDE_ZONES)) + { + Set authorityZones = authorityService.getAuthorityZones(authorityInfo.getAuthorityName()); + group.setZones(authorityZones); + } + } + + return group; + } + + private boolean isRootAuthority(Set rootAuthorities, String authorityName) + { + return rootAuthorities.contains(authorityName); + } + + private Pair getGroupsSortProp(Parameters parameters) + { + Pair sortProp; + List sortCols = parameters.getSorting(); + + if ((sortCols != null) && (sortCols.size() > 0)) + { + if (sortCols.size() > 1) + { + throw new InvalidArgumentException("Multiple sort fields not allowed."); + } + + SortColumn sortCol = sortCols.get(0); + + String sortPropName = SORT_PARAMS_TO_NAMES.get(sortCol.column); + if (sortPropName == null) + { + throw new InvalidArgumentException("Invalid sort field: " + sortCol.column); + } + + sortProp = new Pair<>(sortPropName, (sortCol.asc ? Boolean.TRUE : Boolean.FALSE)); + } + else + { + sortProp = getGroupsSortPropDefault(); + } + return sortProp; + } + + /** + *

+ * Returns the default sort order. + *

+ * + * @return The default Pair<QName, Boolean> sort + * property. + */ + private Pair getGroupsSortPropDefault() + { + return new Pair<>(DISPLAY_NAME, Boolean.FALSE); + } + + private class AuthorityInfoComparator implements Comparator + { + private Map nameCache; + private String sortBy; + private Collator col; + private int orderMultiplier = 1; + + private AuthorityInfoComparator(String sortBy, boolean sortAsc) + { + col = Collator.getInstance(I18NUtil.getLocale()); + this.sortBy = sortBy; + this.nameCache = new HashMap<>(); + + if (!sortAsc) + { + orderMultiplier = -1; + } + } + + @Override + public int compare(AuthorityInfo g1, AuthorityInfo g2) + { + return col.compare(get(g1), get(g2)) * orderMultiplier; + } + + private String get(AuthorityInfo g) + { + String v = nameCache.get(g); + if (v == null) + { + // Get the value from the group + if (PARAM_DISPLAY_NAME.equals(sortBy)) + { + v = g.getAuthorityDisplayName(); + } + else if (PARAM_ID.equals(sortBy)) + { + v = g.getAuthorityName(); + } + else + { + throw new InvalidArgumentException("Invalid sort field: " + sortBy); + } + + // Lower case it for case insensitive search + v = v.toLowerCase(); + + // Cache it + nameCache.put(g, v); + } + return v; + } + } +} diff --git a/source/java/org/alfresco/rest/api/model/Group.java b/source/java/org/alfresco/rest/api/model/Group.java new file mode 100644 index 0000000000..320002d5f8 --- /dev/null +++ b/source/java/org/alfresco/rest/api/model/Group.java @@ -0,0 +1,144 @@ +/* + * #%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.model; + +import java.util.Set; + +import org.alfresco.rest.framework.resource.UniqueId; + +/** + * Represents a group. + * + * @author cturlica + * + */ +public class Group implements Comparable +{ + + protected String id; // group id (aka authority name) + protected String displayName; + protected Boolean isRoot; + protected Set parentIds; + protected Set zones; + + public Group() + { + } + + @UniqueId + public String getId() + { + return id; + } + + public void setId(String id) + { + this.id = id; + } + + public String getDisplayName() + { + return displayName; + } + + public void setDisplayName(String displayName) + { + this.displayName = displayName; + } + + public Boolean getIsRoot() + { + return isRoot; + } + + public void setIsRoot(Boolean isRoot) + { + this.isRoot = isRoot; + } + + public Set getParentIds() + { + return parentIds; + } + + public void setParentIds(Set parentIds) + { + this.parentIds = parentIds; + } + + public Set getZones() + { + return zones; + } + + public void setZones(Set zones) + { + this.zones = zones; + } + + @Override + public boolean equals(Object obj) + { + if (this == obj) + { + return true; + } + + if (obj == null) + { + return false; + } + + if (getClass() != obj.getClass()) + { + return false; + } + + Group other = (Group) obj; + return id.equals(other.id); + } + + @Override + public int compareTo(Group group) + { + return id.compareTo(group.getId()); + } + + @Override + public int hashCode() + { + final int prime = 31; + int result = 1; + result = prime * result + ((id == null) ? 0 : id.hashCode()); + return result; + } + + @Override + public String toString() + { + return "Group [id=" + id + ", displayName=" + displayName + ", isRoot=" + isRoot + "]"; + } +} \ No newline at end of file diff --git a/source/test-java/org/alfresco/rest/api/tests/ApiTest.java b/source/test-java/org/alfresco/rest/api/tests/ApiTest.java index 5e65a23704..ff4549a3f5 100644 --- a/source/test-java/org/alfresco/rest/api/tests/ApiTest.java +++ b/source/test-java/org/alfresco/rest/api/tests/ApiTest.java @@ -57,7 +57,7 @@ import org.junit.runners.Suite; AuthenticationsTest.class, ModulePackagesApiTest.class, WherePredicateApiTest.class, - DiscoveryApiTest.class, + DiscoveryApiTest.class, TestSites.class, TestNodeComments.class, TestFavouriteSites.class, @@ -73,8 +73,8 @@ import org.junit.runners.Suite; TestSiteMembershipRequests.class, TestFavourites.class, TestPublicApi128.class, - TestPublicApiCaching.class - + TestPublicApiCaching.class, + GroupsTest.class }) public class ApiTest { diff --git a/source/test-java/org/alfresco/rest/api/tests/GroupsTest.java b/source/test-java/org/alfresco/rest/api/tests/GroupsTest.java new file mode 100644 index 0000000000..815af94f11 --- /dev/null +++ b/source/test-java/org/alfresco/rest/api/tests/GroupsTest.java @@ -0,0 +1,380 @@ +/* + * #%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.tests; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import javax.servlet.http.HttpServletResponse; + +import org.alfresco.repo.security.authentication.AuthenticationUtil; +import org.alfresco.rest.AbstractSingleNetworkSiteTest; +import org.alfresco.rest.api.tests.client.PublicApiClient; +import org.alfresco.rest.api.tests.client.PublicApiClient.Groups; +import org.alfresco.rest.api.tests.client.PublicApiClient.ListResponse; +import org.alfresco.rest.api.tests.client.PublicApiClient.Paging; +import org.alfresco.rest.api.tests.client.data.Group; +import org.alfresco.rest.framework.resource.parameters.SortColumn; +import org.alfresco.service.cmr.security.AuthorityService; +import org.alfresco.service.cmr.security.AuthorityType; +import org.alfresco.util.GUID; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; + +/** + * V1 REST API tests for managing Groups + * + * @author cturlica + */ +public class GroupsTest extends AbstractSingleNetworkSiteTest +{ + protected AuthorityService authorityService; + + private String rootGroupName = null; + private Group groupA = null; + private Group groupB = null; + + @Before + public void setup() throws Exception + { + super.setup(); + + authorityService = (AuthorityService) applicationContext.getBean("AuthorityService"); + } + + @After + public void tearDown() throws Exception + { + super.tearDown(); + } + + @Test + public void testGetGroups() throws Exception + { + try + { + createAuthorityContext(user1); + + setRequestContext(user1); + + testGetGroupsSorting(); + testGetGroupsWithInclude(); + testGetGroupsSkipPaging(); + testGetGroupsByIsRoot(true); + testGetGroupsByIsRoot(false); + } + finally + { + clearAuthorityContext(); + } + } + + private void testGetGroupsSkipPaging() throws Exception + { + // +ve: check skip count. + { + // Sort params + Map otherParams = new HashMap<>(); + addOrderBy(otherParams, org.alfresco.rest.api.Groups.PARAM_DISPLAY_NAME, false); + + // Paging and list groups + + int skipCount = 0; + int maxItems = 4; + Paging paging = getPaging(skipCount, maxItems); + + ListResponse resp = getGroups(paging, otherParams); + + // Paging and list groups with skip count. + + skipCount = 2; + maxItems = 2; + paging = getPaging(skipCount, maxItems); + + ListResponse sublistResponse = getGroups(paging, otherParams); + + List expectedSublist = sublist(resp.getList(), skipCount, maxItems); + checkList(expectedSublist, sublistResponse.getPaging(), sublistResponse); + } + + // -ve: check skip count. + { + getGroups(getPaging(-1, null), null, "", HttpServletResponse.SC_BAD_REQUEST); + } + } + + private void testGetGroupsSorting() throws Exception + { + // orderBy=sortColumn should be the same to orderBy=sortColumn ASC + { + // paging + Paging paging = getPaging(0, Integer.MAX_VALUE); + + Map otherParams = new HashMap<>(); + + // Default order. + addOrderBy(otherParams, org.alfresco.rest.api.Groups.PARAM_DISPLAY_NAME, null); + + ListResponse resp = getGroups(paging, otherParams); + List groups = resp.getList(); + assertTrue("groups order not valid", groups.indexOf(groupA) < groups.indexOf(groupB)); + + // Ascending order. + addOrderBy(otherParams, org.alfresco.rest.api.Groups.PARAM_DISPLAY_NAME, true); + + ListResponse respOrderAsc = getGroups(paging, otherParams); + + checkList(respOrderAsc.getList(), resp.getPaging(), resp); + } + + // Sorting should be the same regardless of implementation (canned query + // or postprocessing). + { + // paging + Paging paging = getPaging(0, Integer.MAX_VALUE); + + Map otherParams = new HashMap<>(); + addOrderBy(otherParams, org.alfresco.rest.api.Groups.PARAM_DISPLAY_NAME, null); + + // Get and sort groups using canned query. + ListResponse respCannedQuery = getGroups(paging, otherParams); + + // Get and sort groups using postprocessing. + otherParams.put("where", "(isRoot=true)"); + ListResponse respPostProcess = getGroups(paging, otherParams); + + List expected = respCannedQuery.getList(); + expected.retainAll(respPostProcess.getList()); + + checkList(expected, respPostProcess.getPaging(), respPostProcess); + } + + // Sort by displayName. + { + // paging + Paging paging = getPaging(0, Integer.MAX_VALUE); + + Map otherParams = new HashMap<>(); + + // Default order. + addOrderBy(otherParams, org.alfresco.rest.api.Groups.PARAM_DISPLAY_NAME, true); + + ListResponse resp = getGroups(paging, otherParams); + List groups = resp.getList(); + assertTrue("groups order not valid", groups.indexOf(groupA) < groups.indexOf(groupB)); + } + + // Sort by id. + { + // paging + Paging paging = getPaging(0, Integer.MAX_VALUE); + + Map otherParams = new HashMap<>(); + addOrderBy(otherParams, org.alfresco.rest.api.Groups.PARAM_ID, false); + + // list sites + ListResponse resp = getGroups(paging, otherParams); + + List groups = resp.getList(); + assertTrue("groups order not valid", groups.indexOf(groupB) < groups.indexOf(groupA)); + } + + // Multiple sort fields not allowed. + { + // paging + Paging paging = getPaging(0, Integer.MAX_VALUE); + Map otherParams = new HashMap<>(); + otherParams.put("orderBy", org.alfresco.rest.api.Groups.PARAM_ID + " ASC," + org.alfresco.rest.api.Groups.PARAM_DISPLAY_NAME + " ASC"); + + getGroups(paging, otherParams, "", HttpServletResponse.SC_BAD_REQUEST); + } + } + + private void testGetGroupsWithInclude() throws Exception + { + // paging + int maxItems = 2; + Paging paging = getPaging(0, maxItems); + + Map otherParams = new HashMap<>(); + + // Validate that by default optionally fields aren't returned. + { + // list sites + ListResponse resp = getGroups(paging, null); + + // check results + assertNotNull(resp); + assertNotNull(resp.getList()); + assertFalse(resp.getList().isEmpty()); + + assertEquals(maxItems, resp.getList().size()); + + resp.getList().forEach(group -> validateGroupDefaultFields(group)); + } + + // Check include parent ids. + { + otherParams.put("include", org.alfresco.rest.api.Groups.PARAM_INCLUDE_PARENT_IDS); + + // list sites + ListResponse resp = getGroups(paging, otherParams); + + // check results + assertEquals(maxItems, resp.getList().size()); + + resp.getList().forEach(group -> + { + assertNotNull(group); + + assertNotNull(group.getParentIds()); + assertFalse(group.getParentIds().isEmpty()); + }); + } + + // Check include zones. + { + otherParams.put("include", org.alfresco.rest.api.Groups.PARAM_INCLUDE_ZONES); + + // list sites + ListResponse resp = getGroups(paging, otherParams); + + // check results + assertEquals(maxItems, resp.getList().size()); + + resp.getList().forEach(group -> + { + assertNotNull(group); + + assertNotNull(group.getZones()); + assertFalse(group.getZones().isEmpty()); + }); + } + } + + private void testGetGroupsByIsRoot(boolean isRoot) throws Exception + { + // Sort params + Map otherParams = new HashMap<>(); + otherParams.put("where", "(isRoot=" + isRoot + ")"); + + // Paging + Paging paging = getPaging(0, 4); + + ListResponse resp = getGroups(paging, otherParams); + resp.getList().forEach(group -> { + validateGroupDefaultFields(group); + assertEquals("isRoot was expected to be " + isRoot, isRoot, group.getIsRoot()); + }); + } + + private ListResponse getGroups(final PublicApiClient.Paging paging, Map otherParams, String errorMessage, int expectedStatus) throws Exception + { + final Groups groupsProxy = publicApiClient.groups(); + return groupsProxy.getGroups(createParams(paging, otherParams), errorMessage, expectedStatus); + } + + private ListResponse getGroups(final PublicApiClient.Paging paging, Map otherParams) throws Exception + { + return getGroups(paging, otherParams, "Failed to get groups", HttpServletResponse.SC_OK); + } + + private void addOrderBy(Map otherParams, String sortColumn, Boolean asc) + { + otherParams.put("orderBy", sortColumn + (asc != null ? " " + (asc ? SortColumn.ASCENDING : SortColumn.DESCENDING) : "")); + } + + /** + * Creates authority context. + * + * @param userName + * The user to run as. + */ + private void createAuthorityContext(String userName) + { + String groupName = "Group_ROOT" + GUID.generate(); + + AuthenticationUtil.setRunAsUser(userName); + if (rootGroupName == null) + { + rootGroupName = authorityService.getName(AuthorityType.GROUP, groupName); + } + + if (!authorityService.authorityExists(rootGroupName)) + { + AuthenticationUtil.setAdminUserAsFullyAuthenticatedUser(); + + rootGroupName = authorityService.createAuthority(AuthorityType.GROUP, groupName); + + String groupBAuthorityName = authorityService.createAuthority(AuthorityType.GROUP, "Test_GroupB" + GUID.generate()); + authorityService.addAuthority(rootGroupName, groupBAuthorityName); + + String groupAAuthorityName = authorityService.createAuthority(AuthorityType.GROUP, "Test_GroupA" + GUID.generate()); + authorityService.addAuthority(rootGroupName, groupAAuthorityName); + + authorityService.addAuthority(groupAAuthorityName, user1); + authorityService.addAuthority(groupBAuthorityName, user2); + + groupA = new Group(); + groupA.setId(groupAAuthorityName); + + groupB = new Group(); + groupB.setId(groupBAuthorityName); + } + } + + /** + * Clears authority context: removes root group and all child groups. + */ + private void clearAuthorityContext() + { + if (authorityService.authorityExists(rootGroupName)) + { + AuthenticationUtil.setAdminUserAsFullyAuthenticatedUser(); + authorityService.deleteAuthority(rootGroupName, true); + } + } + + private void validateGroupDefaultFields(Group group) + { + assertNotNull(group); + assertNotNull(group.getId()); + assertNotNull(group.getDisplayName()); + assertNotNull(group.getIsRoot()); + + // Optionally included. + assertNull(group.getParentIds()); + assertNull(group.getZones()); + } +} diff --git a/source/test-java/org/alfresco/rest/api/tests/client/PublicApiClient.java b/source/test-java/org/alfresco/rest/api/tests/client/PublicApiClient.java index f2e3264cf5..2a12c2efe9 100644 --- a/source/test-java/org/alfresco/rest/api/tests/client/PublicApiClient.java +++ b/source/test-java/org/alfresco/rest/api/tests/client/PublicApiClient.java @@ -56,6 +56,7 @@ import org.alfresco.rest.api.tests.client.data.ContentData; import org.alfresco.rest.api.tests.client.data.Favourite; import org.alfresco.rest.api.tests.client.data.FavouriteSite; import org.alfresco.rest.api.tests.client.data.FolderNode; +import org.alfresco.rest.api.tests.client.data.Group; import org.alfresco.rest.api.tests.client.data.JSONAble; import org.alfresco.rest.api.tests.client.data.MemberOfSite; import org.alfresco.rest.api.tests.client.data.NodeRating; @@ -116,6 +117,7 @@ public class PublicApiClient private People people; private Favourites favourites; private SiteMembershipRequests siteMembershipRequests; + private Groups groups; private RawProxy rawProxy; private ThreadLocal rc = new ThreadLocal(); @@ -137,6 +139,7 @@ public class PublicApiClient people = new People(); favourites = new Favourites(); siteMembershipRequests = new SiteMembershipRequests(); + groups = new Groups(); rawProxy = new RawProxy(); } @@ -200,6 +203,11 @@ public class PublicApiClient return comments; } + public Groups groups() + { + return groups; + } + public CmisSession createPublicApiCMISSession(Binding binding, String version) { return createPublicApiCMISSession(binding, version, null); @@ -2235,4 +2243,28 @@ public class PublicApiClient return sb.toString(); } } + + public class Groups extends AbstractProxy + { + + public ListResponse getGroups(Map params, String errorMessage, int expectedStatus) throws PublicApiException, ParseException + { + HttpResponse response = getAll("groups", null, null, null, params, errorMessage, expectedStatus); + + if (response != null && response.getJsonResponse() != null) + { + JSONObject jsonList = (JSONObject) response.getJsonResponse().get("list"); + if (jsonList != null) + { + return Group.parseGroups(response.getJsonResponse()); + } + } + return null; + } + + public ListResponse getGroups(Map params) throws PublicApiException, ParseException + { + return getGroups(params, "Failed to get groups", HttpServletResponse.SC_OK); + } + } } diff --git a/source/test-java/org/alfresco/rest/api/tests/client/data/Group.java b/source/test-java/org/alfresco/rest/api/tests/client/data/Group.java new file mode 100644 index 0000000000..075a38ec89 --- /dev/null +++ b/source/test-java/org/alfresco/rest/api/tests/client/data/Group.java @@ -0,0 +1,127 @@ +/* + * #%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.tests.client.data; + +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; + +import java.io.Serializable; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +import org.alfresco.rest.api.tests.client.PublicApiClient.ExpectedPaging; +import org.alfresco.rest.api.tests.client.PublicApiClient.ListResponse; +import org.json.simple.JSONArray; +import org.json.simple.JSONObject; + +/** + * Represents a group. + * + * @author cturlica + * + */ +public class Group extends org.alfresco.rest.api.model.Group implements Serializable, ExpectedComparison +{ + + private static final long serialVersionUID = -3580248429177260831L; + + @Override + public void expected(Object o) + { + assertTrue("o is an instance of " + o.getClass(), o instanceof Group); + + Group other = (Group) o; + + AssertUtil.assertEquals("id", getId(), other.getId()); + AssertUtil.assertEquals("displayName", getDisplayName(), other.getDisplayName()); + AssertUtil.assertEquals("isRoot", getIsRoot(), other.getIsRoot()); + AssertUtil.assertEquals("parentIds", getParentIds(), other.getParentIds()); + AssertUtil.assertEquals("zones", getZones(), other.getZones()); + } + + public JSONObject toJSON() + { + JSONObject groupJson = new JSONObject(); + groupJson.put("id", getId()); + groupJson.put("displayName", getDisplayName()); + groupJson.put("isRoot", getIsRoot()); + + if (getParentIds() != null) + { + groupJson.put("parentIds", getParentIds()); + } + + if (getZones() != null) + { + groupJson.put("zones", getZones()); + } + + return groupJson; + } + + public static Group parseGroup(JSONObject jsonObject) + { + String id = (String) jsonObject.get("id"); + String displayName = (String) jsonObject.get("displayName"); + Boolean isRoot = (Boolean) jsonObject.get("isRoot"); + List parentIds = (List) jsonObject.get("parentIds"); + List zones = (List) jsonObject.get("zones"); + + Group group = new Group(); + group.setId(id); + group.setDisplayName(displayName); + group.setIsRoot(isRoot); + group.setParentIds(parentIds != null ? new HashSet(parentIds) : null); + group.setZones(zones != null ? new HashSet(zones) : null); + + return group; + } + + public static ListResponse parseGroups(JSONObject jsonObject) + { + List groups = new ArrayList<>(); + + JSONObject jsonList = (JSONObject) jsonObject.get("list"); + assertNotNull(jsonList); + + JSONArray jsonEntries = (JSONArray) jsonList.get("entries"); + assertNotNull(jsonEntries); + + for (int i = 0; i < jsonEntries.size(); i++) + { + JSONObject jsonEntry = (JSONObject) jsonEntries.get(i); + JSONObject entry = (JSONObject) jsonEntry.get("entry"); + groups.add(parseGroup(entry)); + } + + ExpectedPaging paging = ExpectedPaging.parsePagination(jsonList); + ListResponse resp = new ListResponse<>(paging, groups); + return resp; + } + +}