mirror of
https://github.com/DSpace/DSpace.git
synced 2025-10-07 01:54:22 +00:00
Merge pull request #9166 from DSpace/backport-9078-to-dspace-7_x
[Port dspace-7_x] Improve performance for Groups with many EPerson members. Fix pagination on endpoints
This commit is contained in:
@@ -305,10 +305,13 @@ public class EPersonServiceImpl extends DSpaceObjectServiceImpl<EPerson> impleme
|
||||
throw new AuthorizeException(
|
||||
"You must be an admin to delete an EPerson");
|
||||
}
|
||||
// Get all workflow-related groups that the current EPerson belongs to
|
||||
Set<Group> workFlowGroups = getAllWorkFlowGroups(context, ePerson);
|
||||
for (Group group: workFlowGroups) {
|
||||
List<EPerson> ePeople = groupService.allMembers(context, group);
|
||||
if (ePeople.size() == 1 && ePeople.contains(ePerson)) {
|
||||
// Get total number of unique EPerson objs who are a member of this group (or subgroup)
|
||||
int totalMembers = groupService.countAllMembers(context, group);
|
||||
// If only one EPerson is a member, then we cannot delete the last member of this group.
|
||||
if (totalMembers == 1) {
|
||||
throw new EmptyWorkflowGroupException(ePerson.getID(), group.getID());
|
||||
}
|
||||
}
|
||||
@@ -567,14 +570,29 @@ public class EPersonServiceImpl extends DSpaceObjectServiceImpl<EPerson> impleme
|
||||
|
||||
@Override
|
||||
public List<EPerson> findByGroups(Context c, Set<Group> groups) throws SQLException {
|
||||
return findByGroups(c, groups, -1, -1);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<EPerson> findByGroups(Context c, Set<Group> groups, int pageSize, int offset) throws SQLException {
|
||||
//Make sure we at least have one group, if not don't even bother searching.
|
||||
if (CollectionUtils.isNotEmpty(groups)) {
|
||||
return ePersonDAO.findByGroups(c, groups);
|
||||
return ePersonDAO.findByGroups(c, groups, pageSize, offset);
|
||||
} else {
|
||||
return new ArrayList<>();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int countByGroups(Context c, Set<Group> groups) throws SQLException {
|
||||
//Make sure we at least have one group, if not don't even bother counting.
|
||||
if (CollectionUtils.isNotEmpty(groups)) {
|
||||
return ePersonDAO.countByGroups(c, groups);
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<EPerson> findEPeopleWithSubscription(Context context) throws SQLException {
|
||||
return ePersonDAO.findAllSubscribers(context);
|
||||
|
@@ -98,7 +98,11 @@ public class Group extends DSpaceObject implements DSpaceObjectLegacySupport {
|
||||
}
|
||||
|
||||
/**
|
||||
* Return EPerson members of a Group
|
||||
* Return EPerson members of a Group.
|
||||
* <P>
|
||||
* WARNING: This method may have bad performance for Groups with large numbers of EPerson members.
|
||||
* Therefore, only use this when you need to access every EPerson member. Instead, consider using
|
||||
* EPersonService.findByGroups() for a paginated list of EPersons.
|
||||
*
|
||||
* @return list of EPersons
|
||||
*/
|
||||
@@ -143,9 +147,13 @@ public class Group extends DSpaceObject implements DSpaceObjectLegacySupport {
|
||||
}
|
||||
|
||||
/**
|
||||
* Return Group members of a Group.
|
||||
* Return Group members (i.e. direct subgroups) of a Group.
|
||||
* <P>
|
||||
* WARNING: This method may have bad performance for Groups with large numbers of Subgroups.
|
||||
* Therefore, only use this when you need to access every Subgroup. Instead, consider using
|
||||
* GroupService.findByParent() for a paginated list of Subgroups.
|
||||
*
|
||||
* @return list of groups
|
||||
* @return list of subgroups
|
||||
*/
|
||||
public List<Group> getMemberGroups() {
|
||||
return groups;
|
||||
|
@@ -179,8 +179,13 @@ public class GroupServiceImpl extends DSpaceObjectServiceImpl<Group> implements
|
||||
for (CollectionRole collectionRole : collectionRoles) {
|
||||
if (StringUtils.equals(collectionRole.getRoleId(), role.getId())
|
||||
&& claimedTask.getWorkflowItem().getCollection() == collectionRole.getCollection()) {
|
||||
List<EPerson> ePeople = allMembers(context, group);
|
||||
if (ePeople.size() == 1 && ePeople.contains(ePerson)) {
|
||||
// Count number of EPersons who are *direct* members of this group
|
||||
int totalDirectEPersons = ePersonService.countByGroups(context, Set.of(group));
|
||||
// Count number of Groups which have this groupParent as a direct parent
|
||||
int totalChildGroups = countByParent(context, group);
|
||||
// If this group has only one direct EPerson and *zero* child groups, then we cannot delete the
|
||||
// EPerson or we will leave this group empty.
|
||||
if (totalDirectEPersons == 1 && totalChildGroups == 0) {
|
||||
throw new IllegalStateException(
|
||||
"Refused to remove user " + ePerson
|
||||
.getID() + " from workflow group because the group " + group
|
||||
@@ -191,8 +196,13 @@ public class GroupServiceImpl extends DSpaceObjectServiceImpl<Group> implements
|
||||
}
|
||||
}
|
||||
if (!poolTasks.isEmpty()) {
|
||||
List<EPerson> ePeople = allMembers(context, group);
|
||||
if (ePeople.size() == 1 && ePeople.contains(ePerson)) {
|
||||
// Count number of EPersons who are *direct* members of this group
|
||||
int totalDirectEPersons = ePersonService.countByGroups(context, Set.of(group));
|
||||
// Count number of Groups which have this groupParent as a direct parent
|
||||
int totalChildGroups = countByParent(context, group);
|
||||
// If this group has only one direct EPerson and *zero* child groups, then we cannot delete the
|
||||
// EPerson or we will leave this group empty.
|
||||
if (totalDirectEPersons == 1 && totalChildGroups == 0) {
|
||||
throw new IllegalStateException(
|
||||
"Refused to remove user " + ePerson
|
||||
.getID() + " from workflow group because the group " + group
|
||||
@@ -212,9 +222,13 @@ public class GroupServiceImpl extends DSpaceObjectServiceImpl<Group> implements
|
||||
if (!collectionRoles.isEmpty()) {
|
||||
List<PoolTask> poolTasks = poolTaskService.findByGroup(context, groupParent);
|
||||
if (!poolTasks.isEmpty()) {
|
||||
List<EPerson> parentPeople = allMembers(context, groupParent);
|
||||
List<EPerson> childPeople = allMembers(context, childGroup);
|
||||
if (childPeople.containsAll(parentPeople)) {
|
||||
// Count number of Groups which have this groupParent as a direct parent
|
||||
int totalChildGroups = countByParent(context, groupParent);
|
||||
// Count number of EPersons who are *direct* members of this group
|
||||
int totalDirectEPersons = ePersonService.countByGroups(context, Set.of(groupParent));
|
||||
// If this group has only one childGroup and *zero* direct EPersons, then we cannot delete the
|
||||
// childGroup or we will leave this group empty.
|
||||
if (totalChildGroups == 1 && totalDirectEPersons == 0) {
|
||||
throw new IllegalStateException(
|
||||
"Refused to remove sub group " + childGroup
|
||||
.getID() + " from workflow group because the group " + groupParent
|
||||
@@ -368,7 +382,8 @@ public class GroupServiceImpl extends DSpaceObjectServiceImpl<Group> implements
|
||||
|
||||
// Get all groups which are a member of this group
|
||||
List<Group2GroupCache> group2GroupCaches = group2GroupCacheDAO.findByParent(c, g);
|
||||
Set<Group> groups = new HashSet<>();
|
||||
// Initialize HashSet based on List size to avoid Set resizing. See https://stackoverflow.com/a/21822273
|
||||
Set<Group> groups = new HashSet<>((int) (group2GroupCaches.size() / 0.75 + 1));
|
||||
for (Group2GroupCache group2GroupCache : group2GroupCaches) {
|
||||
groups.add(group2GroupCache.getChild());
|
||||
}
|
||||
@@ -381,6 +396,23 @@ public class GroupServiceImpl extends DSpaceObjectServiceImpl<Group> implements
|
||||
return new ArrayList<>(childGroupChildren);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int countAllMembers(Context context, Group group) throws SQLException {
|
||||
// Get all groups which are a member of this group
|
||||
List<Group2GroupCache> group2GroupCaches = group2GroupCacheDAO.findByParent(context, group);
|
||||
// Initialize HashSet based on List size + current 'group' to avoid Set resizing.
|
||||
// See https://stackoverflow.com/a/21822273
|
||||
Set<Group> groups = new HashSet<>((int) ((group2GroupCaches.size() + 1) / 0.75 + 1));
|
||||
for (Group2GroupCache group2GroupCache : group2GroupCaches) {
|
||||
groups.add(group2GroupCache.getChild());
|
||||
}
|
||||
// Append current group as well
|
||||
groups.add(group);
|
||||
|
||||
// Return total number of unique EPerson objects in any of these groups
|
||||
return ePersonService.countByGroups(context, groups);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Group find(Context context, UUID id) throws SQLException {
|
||||
if (id == null) {
|
||||
@@ -829,4 +861,20 @@ public class GroupServiceImpl extends DSpaceObjectServiceImpl<Group> implements
|
||||
public String getName(Group dso) {
|
||||
return dso.getName();
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Group> findByParent(Context context, Group parent, int pageSize, int offset) throws SQLException {
|
||||
if (parent == null) {
|
||||
return null;
|
||||
}
|
||||
return groupDAO.findByParent(context, parent, pageSize, offset);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int countByParent(Context context, Group parent) throws SQLException {
|
||||
if (parent == null) {
|
||||
return 0;
|
||||
}
|
||||
return groupDAO.countByParent(context, parent);
|
||||
}
|
||||
}
|
||||
|
@@ -38,7 +38,29 @@ public interface EPersonDAO extends DSpaceObjectDAO<EPerson>, DSpaceObjectLegacy
|
||||
|
||||
public int searchResultCount(Context context, String query, List<MetadataField> queryFields) throws SQLException;
|
||||
|
||||
public List<EPerson> findByGroups(Context context, Set<Group> groups) throws SQLException;
|
||||
/**
|
||||
* Find all EPersons who are a member of one or more of the listed groups in a paginated fashion. This returns
|
||||
* EPersons ordered by UUID.
|
||||
*
|
||||
* @param context current Context
|
||||
* @param groups Set of group(s) to check membership in
|
||||
* @param pageSize number of EPerson objects to load at one time. Set to <=0 to disable pagination
|
||||
* @param offset number of page to load (starting with 1). Set to <=0 to disable pagination
|
||||
* @return List of all EPersons who are a member of one or more groups.
|
||||
* @throws SQLException
|
||||
*/
|
||||
List<EPerson> findByGroups(Context context, Set<Group> groups, int pageSize, int offset) throws SQLException;
|
||||
|
||||
/**
|
||||
* Count total number of EPersons who are a member of one or more of the listed groups. This provides the total
|
||||
* number of results to expect from corresponding findByGroups() for pagination purposes.
|
||||
*
|
||||
* @param context current Context
|
||||
* @param groups Set of group(s) to check membership in
|
||||
* @return total number of (unique) EPersons who are a member of one or more groups.
|
||||
* @throws SQLException
|
||||
*/
|
||||
int countByGroups(Context context, Set<Group> groups) throws SQLException;
|
||||
|
||||
public List<EPerson> findWithPasswordWithoutDigestAlgorithm(Context context) throws SQLException;
|
||||
|
||||
|
@@ -146,4 +146,28 @@ public interface GroupDAO extends DSpaceObjectDAO<Group>, DSpaceObjectLegacySupp
|
||||
*/
|
||||
Group findByIdAndMembership(Context context, UUID id, EPerson ePerson) throws SQLException;
|
||||
|
||||
/**
|
||||
* Find all groups which are members of a given parent group.
|
||||
* This provides the same behavior as group.getMemberGroups(), but in a paginated fashion.
|
||||
*
|
||||
* @param context The DSpace context
|
||||
* @param parent Parent Group to search within
|
||||
* @param pageSize how many results return
|
||||
* @param offset the position of the first result to return
|
||||
* @return Groups matching the query
|
||||
* @throws SQLException if database error
|
||||
*/
|
||||
List<Group> findByParent(Context context, Group parent, int pageSize, int offset) throws SQLException;
|
||||
|
||||
/**
|
||||
* Returns the number of groups which are members of a given parent group.
|
||||
* This provides the same behavior as group.getMemberGroups().size(), but with better performance for large groups.
|
||||
* This method may be used with findByParent() to perform pagination.
|
||||
*
|
||||
* @param context The DSpace context
|
||||
* @param parent Parent Group to search within
|
||||
* @return Number of Groups matching the query
|
||||
* @throws SQLException if database error
|
||||
*/
|
||||
int countByParent(Context context, Group parent) throws SQLException;
|
||||
}
|
||||
|
@@ -112,12 +112,36 @@ public class EPersonDAOImpl extends AbstractHibernateDSODAO<EPerson> implements
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<EPerson> findByGroups(Context context, Set<Group> groups) throws SQLException {
|
||||
public List<EPerson> findByGroups(Context context, Set<Group> groups, int pageSize, int offset)
|
||||
throws SQLException {
|
||||
Query query = createQuery(context,
|
||||
"SELECT DISTINCT e FROM EPerson e " +
|
||||
"JOIN e.groups g " +
|
||||
"WHERE g.id IN (:idList) ");
|
||||
|
||||
List<UUID> idList = new ArrayList<>(groups.size());
|
||||
for (Group group : groups) {
|
||||
idList.add(group.getID());
|
||||
}
|
||||
query.setParameter("idList", idList);
|
||||
|
||||
if (pageSize > 0) {
|
||||
query.setMaxResults(pageSize);
|
||||
}
|
||||
if (offset > 0) {
|
||||
query.setFirstResult(offset);
|
||||
}
|
||||
|
||||
return list(query);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int countByGroups(Context context, Set<Group> groups) throws SQLException {
|
||||
Query query = createQuery(context,
|
||||
"SELECT count(DISTINCT e) FROM EPerson e " +
|
||||
"JOIN e.groups g " +
|
||||
"WHERE g.id IN (:idList) ");
|
||||
|
||||
List<UUID> idList = new ArrayList<>(groups.size());
|
||||
for (Group group : groups) {
|
||||
idList.add(group.getID());
|
||||
@@ -125,7 +149,7 @@ public class EPersonDAOImpl extends AbstractHibernateDSODAO<EPerson> implements
|
||||
|
||||
query.setParameter("idList", idList);
|
||||
|
||||
return list(query);
|
||||
return count(query);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@@ -196,4 +196,28 @@ public class GroupDAOImpl extends AbstractHibernateDSODAO<Group> implements Grou
|
||||
return count(createQuery(context, "SELECT count(*) FROM Group"));
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Group> findByParent(Context context, Group parent, int pageSize, int offset) throws SQLException {
|
||||
Query query = createQuery(context,
|
||||
"SELECT g FROM Group g JOIN g.parentGroups pg " +
|
||||
"WHERE pg.id = :parent_id");
|
||||
query.setParameter("parent_id", parent.getID());
|
||||
if (pageSize > 0) {
|
||||
query.setMaxResults(pageSize);
|
||||
}
|
||||
if (offset > 0) {
|
||||
query.setFirstResult(offset);
|
||||
}
|
||||
query.setHint("org.hibernate.cacheable", Boolean.TRUE);
|
||||
|
||||
return list(query);
|
||||
}
|
||||
|
||||
public int countByParent(Context context, Group parent) throws SQLException {
|
||||
Query query = createQuery(context, "SELECT count(g) FROM Group g JOIN g.parentGroups pg " +
|
||||
"WHERE pg.id = :parent_id");
|
||||
query.setParameter("parent_id", parent.getID());
|
||||
|
||||
return count(query);
|
||||
}
|
||||
}
|
||||
|
@@ -252,14 +252,42 @@ public interface EPersonService extends DSpaceObjectService<EPerson>, DSpaceObje
|
||||
public List<String> getDeleteConstraints(Context context, EPerson ePerson) throws SQLException;
|
||||
|
||||
/**
|
||||
* Retrieve all accounts which belong to at least one of the specified groups.
|
||||
* Retrieve all EPerson accounts which belong to at least one of the specified groups.
|
||||
* <P>
|
||||
* WARNING: This method may have bad performance issues for Groups with a very large number of members,
|
||||
* as it will load all member EPerson objects into memory.
|
||||
* <P>
|
||||
* For better performance, use the paginated version of this method.
|
||||
*
|
||||
* @param c The relevant DSpace Context.
|
||||
* @param groups set of eperson groups
|
||||
* @return a list of epeople
|
||||
* @throws SQLException An exception that provides information on a database access error or other errors.
|
||||
*/
|
||||
public List<EPerson> findByGroups(Context c, Set<Group> groups) throws SQLException;
|
||||
List<EPerson> findByGroups(Context c, Set<Group> groups) throws SQLException;
|
||||
|
||||
/**
|
||||
* Retrieve all EPerson accounts which belong to at least one of the specified groups, in a paginated fashion.
|
||||
*
|
||||
* @param c The relevant DSpace Context.
|
||||
* @param groups Set of group(s) to check membership in
|
||||
* @param pageSize number of EPerson objects to load at one time. Set to <=0 to disable pagination
|
||||
* @param offset number of page to load (starting with 1). Set to <=0 to disable pagination
|
||||
* @return a list of epeople
|
||||
* @throws SQLException An exception that provides information on a database access error or other errors.
|
||||
*/
|
||||
List<EPerson> findByGroups(Context c, Set<Group> groups, int pageSize, int offset) throws SQLException;
|
||||
|
||||
/**
|
||||
* Count all EPerson accounts which belong to at least one of the specified groups. This provides the total
|
||||
* number of results to expect from corresponding findByGroups() for pagination purposes.
|
||||
*
|
||||
* @param c The relevant DSpace Context.
|
||||
* @param groups Set of group(s) to check membership in
|
||||
* @return total number of (unique) EPersons who are a member of one or more groups.
|
||||
* @throws SQLException An exception that provides information on a database access error or other errors.
|
||||
*/
|
||||
int countByGroups(Context c, Set<Group> groups) throws SQLException;
|
||||
|
||||
/**
|
||||
* Retrieve all accounts which are subscribed to receive information about new items.
|
||||
|
@@ -189,9 +189,11 @@ public interface GroupService extends DSpaceObjectService<Group>, DSpaceObjectLe
|
||||
Set<Group> allMemberGroupsSet(Context context, EPerson ePerson) throws SQLException;
|
||||
|
||||
/**
|
||||
* Get all of the epeople who are a member of the
|
||||
* specified group, or a member of a sub-group of the
|
||||
* Get all of the EPerson objects who are a member of the specified group, or a member of a subgroup of the
|
||||
* specified group, etc.
|
||||
* <P>
|
||||
* WARNING: This method may have bad performance for Groups with a very large number of members, as it will load
|
||||
* all member EPerson objects into memory. Only use if you need access to *every* EPerson object at once.
|
||||
*
|
||||
* @param context The relevant DSpace Context.
|
||||
* @param group Group object
|
||||
@@ -200,6 +202,18 @@ public interface GroupService extends DSpaceObjectService<Group>, DSpaceObjectLe
|
||||
*/
|
||||
public List<EPerson> allMembers(Context context, Group group) throws SQLException;
|
||||
|
||||
/**
|
||||
* Count all of the EPerson objects who are a member of the specified group, or a member of a subgroup of the
|
||||
* specified group, etc.
|
||||
* In other words, this will return the size of "allMembers()" without having to load all EPerson objects into
|
||||
* memory.
|
||||
* @param context current DSpace context
|
||||
* @param group Group object
|
||||
* @return count of EPerson object members
|
||||
* @throws SQLException if error
|
||||
*/
|
||||
int countAllMembers(Context context, Group group) throws SQLException;
|
||||
|
||||
/**
|
||||
* Find the group by its name - assumes name is unique
|
||||
*
|
||||
@@ -327,4 +341,29 @@ public interface GroupService extends DSpaceObjectService<Group>, DSpaceObjectLe
|
||||
*/
|
||||
List<Group> findByMetadataField(Context context, String searchValue, MetadataField metadataField)
|
||||
throws SQLException;
|
||||
|
||||
/**
|
||||
* Find all groups which are a member of the given Parent group
|
||||
*
|
||||
* @param context The relevant DSpace Context.
|
||||
* @param parent The parent Group to search on
|
||||
* @param pageSize how many results return
|
||||
* @param offset the position of the first result to return
|
||||
* @return List of all groups which are members of the parent group
|
||||
* @throws SQLException database exception if error
|
||||
*/
|
||||
List<Group> findByParent(Context context, Group parent, int pageSize, int offset)
|
||||
throws SQLException;
|
||||
|
||||
/**
|
||||
* Return number of groups which are a member of the given Parent group.
|
||||
* Can be used with findByParent() for pagination of all groups within a given Parent group.
|
||||
*
|
||||
* @param context The relevant DSpace Context.
|
||||
* @param parent The parent Group to search on
|
||||
* @return number of groups which are members of the parent group
|
||||
* @throws SQLException database exception if error
|
||||
*/
|
||||
int countByParent(Context context, Group parent)
|
||||
throws SQLException;
|
||||
}
|
||||
|
@@ -10,15 +10,18 @@ package org.dspace.eperson;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
import static org.junit.Assert.assertNull;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static org.junit.Assert.fail;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.sql.SQLException;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import javax.mail.MessagingException;
|
||||
|
||||
import org.apache.commons.codec.DecoderException;
|
||||
import org.apache.commons.collections4.CollectionUtils;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
import org.dspace.AbstractUnitTest;
|
||||
@@ -1029,6 +1032,57 @@ public class EPersonTest extends AbstractUnitTest {
|
||||
wfi.getSubmitter());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void findAndCountByGroups() throws SQLException, AuthorizeException, IOException {
|
||||
// Create a group with 3 EPerson members
|
||||
Group group = createGroup("parentGroup");
|
||||
EPerson eperson1 = createEPersonAndAddToGroup("test1@example.com", group);
|
||||
EPerson eperson2 = createEPersonAndAddToGroup("test2@example.com", group);
|
||||
EPerson eperson3 = createEPersonAndAddToGroup("test3@example.com", group);
|
||||
groupService.update(context, group);
|
||||
|
||||
Group group2 = null;
|
||||
EPerson eperson4 = null;
|
||||
|
||||
try {
|
||||
// Assert that findByGroup is the same list of EPersons as getMembers() when pagination is ignored
|
||||
// (NOTE: Pagination is tested in GroupRestRepositoryIT)
|
||||
// NOTE: isEqualCollection() must be used for comparison because Hibernate's "PersistentBag" cannot be
|
||||
// compared directly to a List. See https://stackoverflow.com/a/57399383/3750035
|
||||
assertTrue(
|
||||
CollectionUtils.isEqualCollection(group.getMembers(),
|
||||
ePersonService.findByGroups(context, Set.of(group), -1, -1)));
|
||||
// Assert countByGroups is the same as the size of members
|
||||
assertEquals(group.getMembers().size(), ePersonService.countByGroups(context, Set.of(group)));
|
||||
|
||||
// Add another group with duplicate EPerson
|
||||
group2 = createGroup("anotherGroup");
|
||||
groupService.addMember(context, group2, eperson1);
|
||||
groupService.update(context, group2);
|
||||
|
||||
// Verify countByGroups is still 3 (existing person should not be counted twice)
|
||||
assertEquals(3, ePersonService.countByGroups(context, Set.of(group, group2)));
|
||||
|
||||
// Add a new EPerson to new group, verify count goes up by one
|
||||
eperson4 = createEPersonAndAddToGroup("test4@example.com", group2);
|
||||
assertEquals(4, ePersonService.countByGroups(context, Set.of(group, group2)));
|
||||
} finally {
|
||||
// Clean up our data
|
||||
context.turnOffAuthorisationSystem();
|
||||
groupService.delete(context, group);
|
||||
if (group2 != null) {
|
||||
groupService.delete(context, group2);
|
||||
}
|
||||
ePersonService.delete(context, eperson1);
|
||||
ePersonService.delete(context, eperson2);
|
||||
ePersonService.delete(context, eperson3);
|
||||
if (eperson4 != null) {
|
||||
ePersonService.delete(context, eperson4);
|
||||
}
|
||||
context.restoreAuthSystemState();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an item, sets the specified submitter.
|
||||
*
|
||||
@@ -1075,4 +1129,32 @@ public class EPersonTest extends AbstractUnitTest {
|
||||
context.restoreAuthSystemState();
|
||||
return wsi;
|
||||
}
|
||||
|
||||
protected Group createGroup(String name) throws SQLException, AuthorizeException {
|
||||
context.turnOffAuthorisationSystem();
|
||||
Group group = groupService.create(context);
|
||||
group.setName(name);
|
||||
groupService.update(context, group);
|
||||
context.restoreAuthSystemState();
|
||||
return group;
|
||||
}
|
||||
|
||||
protected EPerson createEPersonAndAddToGroup(String email, Group group) throws SQLException, AuthorizeException {
|
||||
context.turnOffAuthorisationSystem();
|
||||
EPerson ePerson = createEPerson(email);
|
||||
groupService.addMember(context, group, ePerson);
|
||||
groupService.update(context, group);
|
||||
ePersonService.update(context, ePerson);
|
||||
context.restoreAuthSystemState();
|
||||
return ePerson;
|
||||
}
|
||||
|
||||
protected EPerson createEPerson(String email) throws SQLException, AuthorizeException {
|
||||
context.turnOffAuthorisationSystem();
|
||||
EPerson ePerson = ePersonService.create(context);
|
||||
ePerson.setEmail(email);
|
||||
ePersonService.update(context, ePerson);
|
||||
context.restoreAuthSystemState();
|
||||
return ePerson;
|
||||
}
|
||||
}
|
||||
|
@@ -10,6 +10,7 @@ package org.dspace.eperson;
|
||||
import static org.hamcrest.CoreMatchers.equalTo;
|
||||
import static org.hamcrest.CoreMatchers.notNullValue;
|
||||
import static org.hamcrest.MatcherAssert.assertThat;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static org.junit.Assert.fail;
|
||||
@@ -21,6 +22,7 @@ import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import org.apache.commons.collections4.CollectionUtils;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
import org.dspace.AbstractUnitTest;
|
||||
import org.dspace.authorize.AuthorizeException;
|
||||
@@ -604,6 +606,30 @@ public class GroupTest extends AbstractUnitTest {
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void countAllMembers() throws SQLException, AuthorizeException, EPersonDeletionException, IOException {
|
||||
List<EPerson> allEPeopleAdded = new ArrayList<>();
|
||||
try {
|
||||
context.turnOffAuthorisationSystem();
|
||||
allEPeopleAdded.add(createEPersonAndAddToGroup("allMemberGroups1@dspace.org", topGroup));
|
||||
allEPeopleAdded.add(createEPersonAndAddToGroup("allMemberGroups2@dspace.org", level1Group));
|
||||
allEPeopleAdded.add(createEPersonAndAddToGroup("allMemberGroups3@dspace.org", level2Group));
|
||||
context.restoreAuthSystemState();
|
||||
|
||||
assertEquals(3, groupService.countAllMembers(context, topGroup));
|
||||
assertEquals(2, groupService.countAllMembers(context, level1Group));
|
||||
assertEquals(1, groupService.countAllMembers(context, level2Group));
|
||||
} finally {
|
||||
// Remove all the people added (in order to not impact other tests)
|
||||
context.turnOffAuthorisationSystem();
|
||||
for (EPerson ePerson : allEPeopleAdded) {
|
||||
ePersonService.delete(context, ePerson);
|
||||
}
|
||||
context.restoreAuthSystemState();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void isEmpty() throws SQLException, AuthorizeException, EPersonDeletionException, IOException {
|
||||
assertTrue(groupService.isEmpty(topGroup));
|
||||
@@ -620,6 +646,40 @@ public class GroupTest extends AbstractUnitTest {
|
||||
assertTrue(groupService.isEmpty(level2Group));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void findAndCountByParent() throws SQLException, AuthorizeException, IOException {
|
||||
|
||||
// Create a parent group with 3 child groups
|
||||
Group parentGroup = createGroup("parentGroup");
|
||||
Group childGroup = createGroup("childGroup");
|
||||
Group child2Group = createGroup("child2Group");
|
||||
Group child3Group = createGroup("child3Group");
|
||||
groupService.addMember(context, parentGroup, childGroup);
|
||||
groupService.addMember(context, parentGroup, child2Group);
|
||||
groupService.addMember(context, parentGroup, child3Group);
|
||||
groupService.update(context, parentGroup);
|
||||
|
||||
try {
|
||||
// Assert that findByParent is the same list of groups as getMemberGroups() when pagination is ignored
|
||||
// (NOTE: Pagination is tested in GroupRestRepositoryIT)
|
||||
// NOTE: isEqualCollection() must be used for comparison because Hibernate's "PersistentBag" cannot be
|
||||
// compared directly to a List. See https://stackoverflow.com/a/57399383/3750035
|
||||
assertTrue(
|
||||
CollectionUtils.isEqualCollection(parentGroup.getMemberGroups(),
|
||||
groupService.findByParent(context, parentGroup, -1, -1)));
|
||||
// Assert countBy parent is the same as the size of group members
|
||||
assertEquals(parentGroup.getMemberGroups().size(), groupService.countByParent(context, parentGroup));
|
||||
} finally {
|
||||
// Clean up our data
|
||||
context.turnOffAuthorisationSystem();
|
||||
groupService.delete(context, parentGroup);
|
||||
groupService.delete(context, childGroup);
|
||||
groupService.delete(context, child2Group);
|
||||
groupService.delete(context, child3Group);
|
||||
context.restoreAuthSystemState();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
protected Group createGroup(String name) throws SQLException, AuthorizeException {
|
||||
context.turnOffAuthorisationSystem();
|
||||
|
@@ -8,6 +8,8 @@
|
||||
package org.dspace.app.rest.repository;
|
||||
|
||||
import java.sql.SQLException;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
import javax.annotation.Nullable;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
@@ -15,7 +17,9 @@ import javax.servlet.http.HttpServletRequest;
|
||||
import org.dspace.app.rest.model.GroupRest;
|
||||
import org.dspace.app.rest.projection.Projection;
|
||||
import org.dspace.core.Context;
|
||||
import org.dspace.eperson.EPerson;
|
||||
import org.dspace.eperson.Group;
|
||||
import org.dspace.eperson.service.EPersonService;
|
||||
import org.dspace.eperson.service.GroupService;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.data.domain.Page;
|
||||
@@ -31,6 +35,9 @@ import org.springframework.stereotype.Component;
|
||||
public class GroupEPersonLinkRepository extends AbstractDSpaceRestRepository
|
||||
implements LinkRestRepository {
|
||||
|
||||
@Autowired
|
||||
EPersonService epersonService;
|
||||
|
||||
@Autowired
|
||||
GroupService groupService;
|
||||
|
||||
@@ -45,7 +52,11 @@ public class GroupEPersonLinkRepository extends AbstractDSpaceRestRepository
|
||||
if (group == null) {
|
||||
throw new ResourceNotFoundException("No such group: " + groupId);
|
||||
}
|
||||
return converter.toRestPage(group.getMembers(), optionalPageable, projection);
|
||||
int total = epersonService.countByGroups(context, Set.of(group));
|
||||
Pageable pageable = utils.getPageable(optionalPageable);
|
||||
List<EPerson> members = epersonService.findByGroups(context, Set.of(group), pageable.getPageSize(),
|
||||
Math.toIntExact(pageable.getOffset()));
|
||||
return converter.toRestPage(members, pageable, total, projection);
|
||||
} catch (SQLException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
|
@@ -8,6 +8,7 @@
|
||||
package org.dspace.app.rest.repository;
|
||||
|
||||
import java.sql.SQLException;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
import javax.annotation.Nullable;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
@@ -45,7 +46,11 @@ public class GroupGroupLinkRepository extends AbstractDSpaceRestRepository
|
||||
if (group == null) {
|
||||
throw new ResourceNotFoundException("No such group: " + groupId);
|
||||
}
|
||||
return converter.toRestPage(group.getMemberGroups(), optionalPageable, projection);
|
||||
int total = groupService.countByParent(context, group);
|
||||
Pageable pageable = utils.getPageable(optionalPageable);
|
||||
List<Group> memberGroups = groupService.findByParent(context, group, pageable.getPageSize(),
|
||||
Math.toIntExact(pageable.getOffset()));
|
||||
return converter.toRestPage(memberGroups, pageable, total, projection);
|
||||
} catch (SQLException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
|
@@ -3091,6 +3091,157 @@ public class GroupRestRepositoryIT extends AbstractControllerIntegrationTest {
|
||||
|
||||
}
|
||||
|
||||
// Test of /groups/[uuid]/epersons pagination
|
||||
@Test
|
||||
public void epersonMemberPaginationTest() throws Exception {
|
||||
context.turnOffAuthorisationSystem();
|
||||
|
||||
EPerson eperson1 = EPersonBuilder.createEPerson(context)
|
||||
.withEmail("test1@example.com")
|
||||
.withNameInMetadata("Test1", "User")
|
||||
.build();
|
||||
EPerson eperson2 = EPersonBuilder.createEPerson(context)
|
||||
.withEmail("test2@example.com")
|
||||
.withNameInMetadata("Test2", "User")
|
||||
.build();
|
||||
EPerson eperson3 = EPersonBuilder.createEPerson(context)
|
||||
.withEmail("test3@example.com")
|
||||
.withNameInMetadata("Test3", "User")
|
||||
.build();
|
||||
EPerson eperson4 = EPersonBuilder.createEPerson(context)
|
||||
.withEmail("test4@example.com")
|
||||
.withNameInMetadata("Test4", "User")
|
||||
.build();
|
||||
EPerson eperson5 = EPersonBuilder.createEPerson(context)
|
||||
.withEmail("test5@example.com")
|
||||
.withNameInMetadata("Test5", "User")
|
||||
.build();
|
||||
|
||||
Group group = GroupBuilder.createGroup(context)
|
||||
.withName("Test group")
|
||||
.addMember(eperson1)
|
||||
.addMember(eperson2)
|
||||
.addMember(eperson3)
|
||||
.addMember(eperson4)
|
||||
.addMember(eperson5)
|
||||
.build();
|
||||
|
||||
context.restoreAuthSystemState();
|
||||
|
||||
String authTokenAdmin = getAuthToken(admin.getEmail(), password);
|
||||
getClient(authTokenAdmin).perform(get("/api/eperson/groups/" + group.getID() + "/epersons")
|
||||
.param("page", "0")
|
||||
.param("size", "2"))
|
||||
.andExpect(status().isOk()).andExpect(content().contentType(contentType))
|
||||
.andExpect(jsonPath("$._embedded.epersons", Matchers.everyItem(
|
||||
hasJsonPath("$.type", is("eperson")))
|
||||
))
|
||||
.andExpect(jsonPath("$._embedded.epersons").value(Matchers.hasSize(2)))
|
||||
.andExpect(jsonPath("$.page.size", is(2)))
|
||||
.andExpect(jsonPath("$.page.number", is(0)))
|
||||
.andExpect(jsonPath("$.page.totalPages", is(3)))
|
||||
.andExpect(jsonPath("$.page.totalElements", is(5)));
|
||||
|
||||
getClient(authTokenAdmin).perform(get("/api/eperson/groups/" + group.getID() + "/epersons")
|
||||
.param("page", "1")
|
||||
.param("size", "2"))
|
||||
.andExpect(status().isOk()).andExpect(content().contentType(contentType))
|
||||
.andExpect(jsonPath("$._embedded.epersons", Matchers.everyItem(
|
||||
hasJsonPath("$.type", is("eperson")))
|
||||
))
|
||||
.andExpect(jsonPath("$._embedded.epersons").value(Matchers.hasSize(2)))
|
||||
.andExpect(jsonPath("$.page.size", is(2)))
|
||||
.andExpect(jsonPath("$.page.number", is(1)))
|
||||
.andExpect(jsonPath("$.page.totalPages", is(3)))
|
||||
.andExpect(jsonPath("$.page.totalElements", is(5)));
|
||||
|
||||
getClient(authTokenAdmin).perform(get("/api/eperson/groups/" + group.getID() + "/epersons")
|
||||
.param("page", "2")
|
||||
.param("size", "2"))
|
||||
.andExpect(status().isOk()).andExpect(content().contentType(contentType))
|
||||
.andExpect(jsonPath("$._embedded.epersons", Matchers.everyItem(
|
||||
hasJsonPath("$.type", is("eperson")))
|
||||
))
|
||||
.andExpect(jsonPath("$._embedded.epersons").value(Matchers.hasSize(1)))
|
||||
.andExpect(jsonPath("$.page.size", is(2)))
|
||||
.andExpect(jsonPath("$.page.number", is(2)))
|
||||
.andExpect(jsonPath("$.page.totalPages", is(3)))
|
||||
.andExpect(jsonPath("$.page.totalElements", is(5)));
|
||||
}
|
||||
|
||||
// Test of /groups/[uuid]/subgroups pagination
|
||||
@Test
|
||||
public void subgroupPaginationTest() throws Exception {
|
||||
context.turnOffAuthorisationSystem();
|
||||
|
||||
Group group = GroupBuilder.createGroup(context)
|
||||
.withName("Test group")
|
||||
.build();
|
||||
|
||||
GroupBuilder.createGroup(context)
|
||||
.withParent(group)
|
||||
.withName("Test subgroup 1")
|
||||
.build();
|
||||
GroupBuilder.createGroup(context)
|
||||
.withParent(group)
|
||||
.withName("Test subgroup 2")
|
||||
.build();
|
||||
GroupBuilder.createGroup(context)
|
||||
.withParent(group)
|
||||
.withName("Test subgroup 3")
|
||||
.build();
|
||||
GroupBuilder.createGroup(context)
|
||||
.withParent(group)
|
||||
.withName("Test subgroup 4")
|
||||
.build();
|
||||
GroupBuilder.createGroup(context)
|
||||
.withParent(group)
|
||||
.withName("Test subgroup 5")
|
||||
.build();
|
||||
|
||||
context.restoreAuthSystemState();
|
||||
|
||||
String authTokenAdmin = getAuthToken(admin.getEmail(), password);
|
||||
getClient(authTokenAdmin).perform(get("/api/eperson/groups/" + group.getID() + "/subgroups")
|
||||
.param("page", "0")
|
||||
.param("size", "2"))
|
||||
.andExpect(status().isOk()).andExpect(content().contentType(contentType))
|
||||
.andExpect(jsonPath("$._embedded.subgroups", Matchers.everyItem(
|
||||
hasJsonPath("$.type", is("group")))
|
||||
))
|
||||
.andExpect(jsonPath("$._embedded.subgroups").value(Matchers.hasSize(2)))
|
||||
.andExpect(jsonPath("$.page.size", is(2)))
|
||||
.andExpect(jsonPath("$.page.number", is(0)))
|
||||
.andExpect(jsonPath("$.page.totalPages", is(3)))
|
||||
.andExpect(jsonPath("$.page.totalElements", is(5)));
|
||||
|
||||
getClient(authTokenAdmin).perform(get("/api/eperson/groups/" + group.getID() + "/subgroups")
|
||||
.param("page", "1")
|
||||
.param("size", "2"))
|
||||
.andExpect(status().isOk()).andExpect(content().contentType(contentType))
|
||||
.andExpect(jsonPath("$._embedded.subgroups", Matchers.everyItem(
|
||||
hasJsonPath("$.type", is("group")))
|
||||
))
|
||||
.andExpect(jsonPath("$._embedded.subgroups").value(Matchers.hasSize(2)))
|
||||
.andExpect(jsonPath("$.page.size", is(2)))
|
||||
.andExpect(jsonPath("$.page.number", is(1)))
|
||||
.andExpect(jsonPath("$.page.totalPages", is(3)))
|
||||
.andExpect(jsonPath("$.page.totalElements", is(5)));
|
||||
|
||||
getClient(authTokenAdmin).perform(get("/api/eperson/groups/" + group.getID() + "/subgroups")
|
||||
.param("page", "2")
|
||||
.param("size", "2"))
|
||||
.andExpect(status().isOk()).andExpect(content().contentType(contentType))
|
||||
.andExpect(jsonPath("$._embedded.subgroups", Matchers.everyItem(
|
||||
hasJsonPath("$.type", is("group")))
|
||||
))
|
||||
.andExpect(jsonPath("$._embedded.subgroups").value(Matchers.hasSize(1)))
|
||||
.andExpect(jsonPath("$.page.size", is(2)))
|
||||
.andExpect(jsonPath("$.page.number", is(2)))
|
||||
.andExpect(jsonPath("$.page.totalPages", is(3)))
|
||||
.andExpect(jsonPath("$.page.totalElements", is(5)));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void commAdminAndColAdminCannotExploitItemReadGroupTest() throws Exception {
|
||||
|
||||
|
Reference in New Issue
Block a user