Skip to content

Commit

Permalink
Merge pull request DSpace#9125 from tdonohue/add_isNotMemberOf_endpoints
Browse files Browse the repository at this point in the history
Add `isNotMemberOf` searches for Groups and EPersons (for improved performance on Edit Group pages)
  • Loading branch information
tdonohue authored Nov 10, 2023
2 parents 272744a + e5e0eaa commit 1a5bac7
Show file tree
Hide file tree
Showing 15 changed files with 1,193 additions and 107 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -83,13 +83,14 @@ protected void addMetadataValueWhereQuery(StringBuilder query, List<MetadataFiel
if (CollectionUtils.isNotEmpty(metadataFields) || StringUtils.isNotBlank(additionalWhere)) {
//Add the where query on metadata
query.append(" WHERE ");
// Group the 'OR' clauses below in outer parentheses, e.g. "WHERE (clause1 OR clause2 OR clause3)".
// Grouping these 'OR' clauses allows for later code to append 'AND' clauses without unexpected behaviors
query.append("(");
for (int i = 0; i < metadataFields.size(); i++) {
MetadataField metadataField = metadataFields.get(i);
if (StringUtils.isNotBlank(operator)) {
query.append(" (");
query.append("lower(STR(" + metadataField.toString()).append(".value)) ").append(operator)
.append(" lower(:queryParam)");
query.append(")");
if (i < metadataFields.size() - 1) {
query.append(" OR ");
}
Expand All @@ -102,6 +103,7 @@ protected void addMetadataValueWhereQuery(StringBuilder query, List<MetadataFiel
}
query.append(additionalWhere);
}
query.append(")");

}
}
Expand Down
94 changes: 80 additions & 14 deletions dspace-api/src/main/java/org/dspace/eperson/EPersonServiceImpl.java
Original file line number Diff line number Diff line change
Expand Up @@ -184,32 +184,98 @@ public List<EPerson> search(Context context, String query) throws SQLException {

@Override
public List<EPerson> search(Context context, String query, int offset, int limit) throws SQLException {
try {
List<EPerson> ePerson = new ArrayList<>();
EPerson person = find(context, UUID.fromString(query));
List<EPerson> ePersons = new ArrayList<>();
UUID uuid = UUIDUtils.fromString(query);
if (uuid == null) {
// Search by firstname & lastname (NOTE: email will also be included automatically)
MetadataField firstNameField = metadataFieldService.findByElement(context, "eperson", "firstname", null);
MetadataField lastNameField = metadataFieldService.findByElement(context, "eperson", "lastname", null);
if (StringUtils.isBlank(query)) {
query = null;
}
ePersons = ePersonDAO.search(context, query, Arrays.asList(firstNameField, lastNameField),
Arrays.asList(firstNameField, lastNameField), offset, limit);
} else {
// Search by UUID
EPerson person = find(context, uuid);
if (person != null) {
ePerson.add(person);
ePersons.add(person);
}
return ePerson;
} catch (IllegalArgumentException e) {
}
return ePersons;
}

@Override
public int searchResultCount(Context context, String query) throws SQLException {
int result = 0;
UUID uuid = UUIDUtils.fromString(query);
if (uuid == null) {
// Count results found by firstname & lastname (email is also included automatically)
MetadataField firstNameField = metadataFieldService.findByElement(context, "eperson", "firstname", null);
MetadataField lastNameField = metadataFieldService.findByElement(context, "eperson", "lastname", null);
if (StringUtils.isBlank(query)) {
query = null;
}
return ePersonDAO.search(context, query, Arrays.asList(firstNameField, lastNameField),
Arrays.asList(firstNameField, lastNameField), offset, limit);
result = ePersonDAO.searchResultCount(context, query, Arrays.asList(firstNameField, lastNameField));
} else {
// Search by UUID
EPerson person = find(context, uuid);
if (person != null) {
result = 1;
}
}
return result;
}

@Override
public int searchResultCount(Context context, String query) throws SQLException {
MetadataField firstNameField = metadataFieldService.findByElement(context, "eperson", "firstname", null);
MetadataField lastNameField = metadataFieldService.findByElement(context, "eperson", "lastname", null);
if (StringUtils.isBlank(query)) {
query = null;
public List<EPerson> searchNonMembers(Context context, String query, Group excludeGroup, int offset, int limit)
throws SQLException {
List<EPerson> ePersons = new ArrayList<>();
UUID uuid = UUIDUtils.fromString(query);
if (uuid == null) {
// Search by firstname & lastname (NOTE: email will also be included automatically)
MetadataField firstNameField = metadataFieldService.findByElement(context, "eperson", "firstname", null);
MetadataField lastNameField = metadataFieldService.findByElement(context, "eperson", "lastname", null);
if (StringUtils.isBlank(query)) {
query = null;
}
ePersons = ePersonDAO.searchNotMember(context, query, Arrays.asList(firstNameField, lastNameField),
excludeGroup, Arrays.asList(firstNameField, lastNameField),
offset, limit);
} else {
// Search by UUID
EPerson person = find(context, uuid);
// Verify EPerson is NOT a member of the given excludeGroup before adding
if (person != null && !groupService.isDirectMember(excludeGroup, person)) {
ePersons.add(person);
}
}

return ePersons;
}

@Override
public int searchNonMembersCount(Context context, String query, Group excludeGroup) throws SQLException {
int result = 0;
UUID uuid = UUIDUtils.fromString(query);
if (uuid == null) {
// Count results found by firstname & lastname (email is also included automatically)
MetadataField firstNameField = metadataFieldService.findByElement(context, "eperson", "firstname", null);
MetadataField lastNameField = metadataFieldService.findByElement(context, "eperson", "lastname", null);
if (StringUtils.isBlank(query)) {
query = null;
}
result = ePersonDAO.searchNotMemberCount(context, query, Arrays.asList(firstNameField, lastNameField),
excludeGroup);
} else {
// Search by UUID
EPerson person = find(context, uuid);
// Verify EPerson is NOT a member of the given excludeGroup before counting
if (person != null && !groupService.isDirectMember(excludeGroup, person)) {
result = 1;
}
}
return ePersonDAO.searchResultCount(context, query, Arrays.asList(firstNameField, lastNameField));
return result;
}

@Override
Expand Down
54 changes: 46 additions & 8 deletions dspace-api/src/main/java/org/dspace/eperson/GroupServiceImpl.java
Original file line number Diff line number Diff line change
Expand Up @@ -460,17 +460,17 @@ public List<Group> findAll(Context context, List<MetadataField> metadataSortFiel
}

@Override
public List<Group> search(Context context, String groupIdentifier) throws SQLException {
return search(context, groupIdentifier, -1, -1);
public List<Group> search(Context context, String query) throws SQLException {
return search(context, query, -1, -1);
}

@Override
public List<Group> search(Context context, String groupIdentifier, int offset, int limit) throws SQLException {
public List<Group> search(Context context, String query, int offset, int limit) throws SQLException {
List<Group> groups = new ArrayList<>();
UUID uuid = UUIDUtils.fromString(groupIdentifier);
UUID uuid = UUIDUtils.fromString(query);
if (uuid == null) {
//Search by group name
groups = groupDAO.findByNameLike(context, groupIdentifier, offset, limit);
groups = groupDAO.findByNameLike(context, query, offset, limit);
} else {
//Search by group id
Group group = find(context, uuid);
Expand All @@ -483,12 +483,12 @@ public List<Group> search(Context context, String groupIdentifier, int offset, i
}

@Override
public int searchResultCount(Context context, String groupIdentifier) throws SQLException {
public int searchResultCount(Context context, String query) throws SQLException {
int result = 0;
UUID uuid = UUIDUtils.fromString(groupIdentifier);
UUID uuid = UUIDUtils.fromString(query);
if (uuid == null) {
//Search by group name
result = groupDAO.countByNameLike(context, groupIdentifier);
result = groupDAO.countByNameLike(context, query);
} else {
//Search by group id
Group group = find(context, uuid);
Expand All @@ -500,6 +500,44 @@ public int searchResultCount(Context context, String groupIdentifier) throws SQL
return result;
}

@Override
public List<Group> searchNonMembers(Context context, String query, Group excludeParentGroup,
int offset, int limit) throws SQLException {
List<Group> groups = new ArrayList<>();
UUID uuid = UUIDUtils.fromString(query);
if (uuid == null) {
// Search by group name
groups = groupDAO.findByNameLikeAndNotMember(context, query, excludeParentGroup, offset, limit);
} else if (!uuid.equals(excludeParentGroup.getID())) {
// Search by group id
Group group = find(context, uuid);
// Verify it is NOT a member of the given excludeParentGroup before adding
if (group != null && !isMember(excludeParentGroup, group)) {
groups.add(group);
}
}

return groups;
}

@Override
public int searchNonMembersCount(Context context, String query, Group excludeParentGroup) throws SQLException {
int result = 0;
UUID uuid = UUIDUtils.fromString(query);
if (uuid == null) {
// Search by group name
result = groupDAO.countByNameLikeAndNotMember(context, query, excludeParentGroup);
} else if (!uuid.equals(excludeParentGroup.getID())) {
// Search by group id
Group group = find(context, uuid);
// Verify it is NOT a member of the given excludeParentGroup before adding
if (group != null && !isMember(excludeParentGroup, group)) {
result = 1;
}
}
return result;
}

@Override
public void delete(Context context, Group group) throws SQLException {
if (group.isPermanent()) {
Expand Down
57 changes: 57 additions & 0 deletions dspace-api/src/main/java/org/dspace/eperson/dao/EPersonDAO.java
Original file line number Diff line number Diff line change
Expand Up @@ -33,11 +33,68 @@ public interface EPersonDAO extends DSpaceObjectDAO<EPerson>, DSpaceObjectLegacy

public EPerson findByNetid(Context context, String netid) throws SQLException;

/**
* Search all EPersons by the given MetadataField objects, sorting by the given sort fields.
* <P>
* NOTE: As long as a query is specified, the EPerson's email address is included in the search alongside any given
* metadata fields.
*
* @param context DSpace context
* @param query the text to search EPersons for
* @param queryFields the metadata fields to search within (email is also included automatically)
* @param sortFields the metadata field(s) to sort the results by
* @param offset the position of the first result to return
* @param limit how many results return
* @return List of matching EPerson objects
* @throws SQLException if an error occurs
*/
public List<EPerson> search(Context context, String query, List<MetadataField> queryFields,
List<MetadataField> sortFields, int offset, int limit) throws SQLException;

/**
* Count number of EPersons who match a search on the given metadata fields. This returns the count of total
* results for the same query using the 'search()', and therefore can be used to provide pagination.
*
* @param context DSpace context
* @param query the text to search EPersons for
* @param queryFields the metadata fields to search within (email is also included automatically)
* @return total number of EPersons who match the query
* @throws SQLException if an error occurs
*/
public int searchResultCount(Context context, String query, List<MetadataField> queryFields) throws SQLException;

/**
* Search all EPersons via their firstname, lastname, email (fuzzy match), limited to those EPersons which are NOT
* a member of the given group. This may be used to search across EPersons which are valid to add as members to the
* given group.
*
* @param context The DSpace context
* @param query the text to search EPersons for
* @param queryFields the metadata fields to search within (email is also included automatically)
* @param excludeGroup Group to exclude results from. Members of this group will never be returned.
* @param offset the position of the first result to return
* @param limit how many results return
* @return EPersons matching the query (which are not members of the given group)
* @throws SQLException if database error
*/
List<EPerson> searchNotMember(Context context, String query, List<MetadataField> queryFields, Group excludeGroup,
List<MetadataField> sortFields, int offset, int limit) throws SQLException;

/**
* Count number of EPersons that match a given search (fuzzy match) across firstname, lastname and email. This
* search is limited to those EPersons which are NOT a member of the given group. This may be used
* (with searchNotMember()) to perform a paginated search across EPersons which are valid to add to the given group.
*
* @param context The DSpace context
* @param query querystring to fuzzy match against.
* @param queryFields the metadata fields to search within (email is also included automatically)
* @param excludeGroup Group to exclude results from. Members of this group will never be returned.
* @return Groups matching the query (which are not members of the given parent)
* @throws SQLException if database error
*/
int searchNotMemberCount(Context context, String query, List<MetadataField> queryFields, Group excludeGroup)
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.
Expand Down
32 changes: 32 additions & 0 deletions dspace-api/src/main/java/org/dspace/eperson/dao/GroupDAO.java
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,38 @@ List<Group> findAll(Context context, List<MetadataField> metadataSortFields, int
*/
int countByNameLike(Context context, String groupName) throws SQLException;

/**
* Search all groups via their name (fuzzy match), limited to those groups which are NOT a member of the given
* parent group. This may be used to search across groups which are valid to add to the given parent group.
* <P>
* NOTE: The parent group itself is also excluded from the search.
*
* @param context The DSpace context
* @param groupName Group name to fuzzy match against.
* @param excludeParent Parent Group to exclude results from. Groups under this parent will never be returned.
* @param offset Offset to use for pagination (-1 to disable)
* @param limit The maximum number of results to return (-1 to disable)
* @return Groups matching the query (which are not members of the given parent)
* @throws SQLException if database error
*/
List<Group> findByNameLikeAndNotMember(Context context, String groupName, Group excludeParent,
int offset, int limit) throws SQLException;

/**
* Count number of groups that match a given name (fuzzy match), limited to those groups which are NOT a member of
* the given parent group. This may be used (with findByNameLikeAndNotMember()) to search across groups which are
* valid to add to the given parent group.
* <P>
* NOTE: The parent group itself is also excluded from the count.
*
* @param context The DSpace context
* @param groupName Group name to fuzzy match against.
* @param excludeParent Parent Group to exclude results from. Groups under this parent will never be returned.
* @return Groups matching the query (which are not members of the given parent)
* @throws SQLException if database error
*/
int countByNameLikeAndNotMember(Context context, String groupName, Group excludeParent) throws SQLException;

/**
* Find a group by its name and the membership of the given EPerson
*
Expand Down
Loading

0 comments on commit 1a5bac7

Please sign in to comment.