68781: Update groups and epersons GETS based on REST contract

This commit is contained in:
Yana De Pauw
2020-02-19 14:00:13 +01:00
parent 820722d2e6
commit 148f9b2365
7 changed files with 218 additions and 95 deletions

View File

@@ -21,6 +21,10 @@ import org.dspace.app.rest.RestResourceController;
@LinkRest(
name = GroupRest.GROUPS,
method = "getGroups"
),
@LinkRest(
name = GroupRest.EPERSONS,
method = "getMembers"
)
})
public class GroupRest extends DSpaceObjectRest {
@@ -28,6 +32,7 @@ public class GroupRest extends DSpaceObjectRest {
public static final String CATEGORY = RestAddressableModel.EPERSON;
public static final String GROUPS = "groups";
public static final String EPERSONS = "epersons";
private String name;

View File

@@ -121,52 +121,31 @@ public class EPersonRestRepository extends DSpaceObjectRestRepository<EPerson, E
}
/**
* Find the epersons matching the query q parameter. The search is delegated to the
* Find the epersons matching the query parameter. The search is delegated to the
* {@link EPersonService#search(Context, String, int, int)} method
*
* @param q
* @param query
* is the *required* query string
* @param pageable
* contains the pagination information
* @return a Page of EPersonRest instances matching the user query
*/
@SearchRestMethod(name = "byName")
public Page<EPersonRest> findByName(@Parameter(value = "q", required = true) String q,
@PreAuthorize("hasAuthority('ADMIN')")
@SearchRestMethod(name = "byMetadata")
public Page<EPersonRest> findByMetadata(@Parameter(value = "query", required = true) String query,
Pageable pageable) {
try {
Context context = obtainContext();
long total = es.searchResultCount(context, q);
List<EPerson> epersons = es.search(context, q, Math.toIntExact(pageable.getOffset()),
Math.toIntExact(pageable.getOffset() + pageable.getPageSize()));
long total = es.searchResultCount(context, query);
List<EPerson> epersons = es.search(context, query, Math.toIntExact(pageable.getOffset()),
Math.toIntExact(pageable.getOffset() + pageable.getPageSize()));
return converter.toRestPage(epersons, pageable, total, utils.obtainProjection());
} catch (SQLException e) {
throw new RuntimeException(e.getMessage(), e);
}
}
/**
* Find the eperson with the provided email address if any. The search is delegated to the
* {@link EPersonService#findByEmail(Context, String)} method
*
* @param email
* is the *required* email address
* @return a Page of EPersonRest instances matching the user query
*/
@SearchRestMethod(name = "byEmail")
public EPersonRest findByEmail(@Parameter(value = "email", required = true) String email) {
EPerson eperson = null;
try {
Context context = obtainContext();
eperson = es.findByEmail(context, email);
} catch (SQLException e) {
throw new RuntimeException(e.getMessage(), e);
}
if (eperson == null) {
return null;
}
return converter.toRest(eperson, utils.obtainProjection());
}
@Override
@PreAuthorize("hasPermission(#uuid, 'EPERSON', #patch)")
protected void patch(Context context, HttpServletRequest request, String apiCategory, String model, UUID uuid,

View File

@@ -0,0 +1,55 @@
/**
* The contents of this file are subject to the license and copyright
* detailed in the LICENSE and NOTICE files at the root of the source
* tree and available online at
*
* http://www.dspace.org/license/
*/
package org.dspace.app.rest.repository;
import java.sql.SQLException;
import java.util.UUID;
import javax.annotation.Nullable;
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.GroupService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.rest.webmvc.ResourceNotFoundException;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.stereotype.Component;
/**
* Link repository for "groups" subresource of an individual group.
*/
@Component(GroupRest.CATEGORY + "." + GroupRest.NAME + "." + GroupRest.EPERSONS)
public class GroupEPersonLinkRepository extends AbstractDSpaceRestRepository
implements LinkRestRepository {
@Autowired
GroupService groupService;
@PreAuthorize("hasPermission(#groupId, 'GROUP', 'READ')")
public Page<GroupRest> getMembers(@Nullable HttpServletRequest request,
UUID groupId,
@Nullable Pageable optionalPageable,
Projection projection) {
try {
Context context = obtainContext();
Group group = groupService.find(context, groupId);
if (group == null) {
throw new ResourceNotFoundException("No such group: " + groupId);
}
Page<EPerson> ePersons = utils.getPage(group.getMembers(), optionalPageable);
return converter.toRestPage(ePersons, projection);
} catch (SQLException e) {
throw new RuntimeException(e);
}
}
}

View File

@@ -14,6 +14,8 @@ import java.util.UUID;
import javax.servlet.http.HttpServletRequest;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.dspace.app.rest.Parameter;
import org.dspace.app.rest.SearchRestMethod;
import org.dspace.app.rest.converter.MetadataConverter;
import org.dspace.app.rest.exception.RepositoryMethodNotImplementedException;
import org.dspace.app.rest.exception.UnprocessableEntityException;
@@ -99,7 +101,7 @@ public class GroupRestRepository extends DSpaceObjectRestRepository<Group, Group
try {
long total = gs.countTotal(context);
List<Group> groups = gs.findAll(context, null, pageable.getPageSize(),
Math.toIntExact(pageable.getOffset()));
Math.toIntExact(pageable.getOffset()));
return converter.toRestPage(groups, pageable, total, utils.obtainProjection());
} catch (SQLException e) {
throw new RuntimeException(e.getMessage(), e);
@@ -113,6 +115,31 @@ public class GroupRestRepository extends DSpaceObjectRestRepository<Group, Group
patchDSpaceObject(apiCategory, model, id, patch);
}
/**
* Find the groups matching the query parameter. The search is delegated to the
* {@link GroupService#search(Context, String, int, int)} method
*
* @param query is the *required* query string
* @param pageable contains the pagination information
* @return a Page of GroupRest instances matching the user query
*/
@PreAuthorize("hasAuthority('ADMIN')")
@SearchRestMethod(name = "byMetadata")
public Page<GroupRest> findByMetadata(@Parameter(value = "query", required = true) String query,
Pageable pageable) {
try {
Context context = obtainContext();
long total = gs.searchResultCount(context, query);
List<Group> groups = gs.search(context, query, Math.toIntExact(pageable.getOffset()),
Math.toIntExact(pageable.getOffset() + pageable.getPageSize()));
return converter.toRestPage(groups, pageable, total, utils.obtainProjection());
} catch (SQLException e) {
throw new RuntimeException(e.getMessage(), e);
}
}
@Override
public Class<GroupRest> getDomainClass() {
return GroupRest.class;

View File

@@ -296,62 +296,11 @@ public class EPersonRestRepositoryIT extends AbstractControllerIntegrationTest {
getClient(authToken).perform(get("/api/eperson/epersons/search"))
.andExpect(status().isOk())
.andExpect(content().contentType(contentType))
.andExpect(jsonPath("$._links.byEmail", Matchers.notNullValue()))
.andExpect(jsonPath("$._links.byName", Matchers.notNullValue()));
.andExpect(jsonPath("$._links.byMetadata", Matchers.notNullValue()));
}
@Test
public void findByEmail() throws Exception {
context.turnOffAuthorisationSystem();
EPerson ePerson = EPersonBuilder.createEPerson(context)
.withNameInMetadata("John", "Doe")
.withEmail("Johndoe@fake-email.com")
.build();
// create a second eperson to put the previous one in a no special position (is not the first as we have default
// epersons is not the latest created)
EPerson ePerson2 = EPersonBuilder.createEPerson(context)
.withNameInMetadata("Jane", "Smith")
.withEmail("janesmith@fake-email.com")
.build();
String authToken = getAuthToken(admin.getEmail(), password);
getClient(authToken).perform(get("/api/eperson/epersons/search/byEmail")
.param("email", ePerson.getEmail()))
.andExpect(status().isOk())
.andExpect(content().contentType(contentType))
.andExpect(jsonPath("$", is(
EPersonMatcher.matchEPersonEntry(ePerson)
)));
// it must be case-insensitive
getClient(authToken).perform(get("/api/eperson/epersons/search/byEmail")
.param("email", ePerson.getEmail().toUpperCase()))
.andExpect(status().isOk())
.andExpect(content().contentType(contentType))
.andExpect(jsonPath("$", is(
EPersonMatcher.matchEPersonEntry(ePerson)
)));
}
@Test
public void findByEmailUndefined() throws Exception {
String authToken = getAuthToken(admin.getEmail(), password);
getClient(authToken).perform(get("/api/eperson/epersons/search/byEmail")
.param("email", "undefined@undefined.com"))
.andExpect(status().isNoContent());
}
@Test
public void findByEmailUnprocessable() throws Exception {
String authToken = getAuthToken(admin.getEmail(), password);
getClient(authToken).perform(get("/api/eperson/epersons/search/byEmail"))
.andExpect(status().isUnprocessableEntity());
}
@Test
public void findByName() throws Exception {
public void findByMetadata() throws Exception {
context.turnOffAuthorisationSystem();
EPerson ePerson = EPersonBuilder.createEPerson(context)
.withNameInMetadata("John", "Doe")
@@ -379,8 +328,8 @@ public class EPersonRestRepositoryIT extends AbstractControllerIntegrationTest {
.build();
String authToken = getAuthToken(admin.getEmail(), password);
getClient(authToken).perform(get("/api/eperson/epersons/search/byName")
.param("q", ePerson.getLastName()))
getClient(authToken).perform(get("/api/eperson/epersons/search/byMetadata")
.param("query", ePerson.getLastName()))
.andExpect(status().isOk())
.andExpect(content().contentType(contentType))
.andExpect(jsonPath("$._embedded.epersons", Matchers.containsInAnyOrder(
@@ -392,8 +341,8 @@ public class EPersonRestRepositoryIT extends AbstractControllerIntegrationTest {
.andExpect(jsonPath("$.page.totalElements", is(4)));
// it must be case insensitive
getClient(authToken).perform(get("/api/eperson/epersons/search/byName")
.param("q", ePerson.getLastName().toLowerCase()))
getClient(authToken).perform(get("/api/eperson/epersons/search/byMetadata")
.param("query", ePerson.getLastName().toLowerCase()))
.andExpect(status().isOk())
.andExpect(content().contentType(contentType))
.andExpect(jsonPath("$._embedded.epersons", Matchers.containsInAnyOrder(
@@ -406,19 +355,34 @@ public class EPersonRestRepositoryIT extends AbstractControllerIntegrationTest {
}
@Test
public void findByNameUndefined() throws Exception {
public void findByMetadataUnauthorized() throws Exception {
getClient().perform(get("/api/eperson/epersons/search/byMetadata")
.param("query", "Doe, John"))
.andExpect(status().isUnauthorized());
}
@Test
public void findByMetadataForbidden() throws Exception {
String authToken = getAuthToken(eperson.getEmail(), password);
getClient(authToken).perform(get("/api/eperson/epersons/search/byMetadata")
.param("query", "Doe, John"))
.andExpect(status().isForbidden());
}
@Test
public void findByMetadataUndefined() throws Exception {
String authToken = getAuthToken(admin.getEmail(), password);
getClient(authToken).perform(get("/api/eperson/epersons/search/byName")
.param("q", "Doe, John"))
getClient(authToken).perform(get("/api/eperson/epersons/search/byMetadata")
.param("query", "Doe, John"))
.andExpect(status().isOk())
.andExpect(content().contentType(contentType))
.andExpect(jsonPath("$.page.totalElements", is(0)));
}
@Test
public void findByNameUnprocessable() throws Exception {
public void findByMetadataUnprocessable() throws Exception {
String authToken = getAuthToken(admin.getEmail(), password);
getClient(authToken).perform(get("/api/eperson/epersons/search/byName"))
getClient(authToken).perform(get("/api/eperson/epersons/search/byMetadata"))
.andExpect(status().isUnprocessableEntity());
}

View File

@@ -257,6 +257,96 @@ public class GroupRestRepositoryIT extends AbstractControllerIntegrationTest {
;
}
@Test
public void searchMethodsExist() throws Exception {
String authToken = getAuthToken(admin.getEmail(), password);
getClient(authToken).perform(get("/api/eperson/epersons"))
.andExpect(jsonPath("$._links.search.href", Matchers.notNullValue()));
getClient(authToken).perform(get("/api/eperson/epersons/search"))
.andExpect(status().isOk())
.andExpect(content().contentType(contentType))
.andExpect(jsonPath("$._links.byMetadata", Matchers.notNullValue()));
}
@Test
public void findByMetadata() throws Exception {
context.turnOffAuthorisationSystem();
Group group1 = GroupBuilder.createGroup(context)
.withName("Test group")
.build();
Group group2 = GroupBuilder.createGroup(context)
.withName("Test group 2")
.build();
Group group3 = GroupBuilder.createGroup(context)
.withName("Test group 3")
.build();
Group group4 = GroupBuilder.createGroup(context)
.withName("Test other group")
.build();
String authToken = getAuthToken(admin.getEmail(), password);
getClient(authToken).perform(get("/api/eperson/groups/search/byMetadata")
.param("query", group1.getName()))
.andExpect(status().isOk())
.andExpect(content().contentType(contentType))
.andExpect(jsonPath("$._embedded.groups", Matchers.containsInAnyOrder(
GroupMatcher.matchGroupEntry(group1.getID(), group1.getName()),
GroupMatcher.matchGroupEntry(group2.getID(), group2.getName()),
GroupMatcher.matchGroupEntry(group3.getID(), group3.getName())
)))
.andExpect(jsonPath("$.page.totalElements", is(3)));
// it must be case insensitive
getClient(authToken).perform(get("/api/eperson/groups/search/byMetadata")
.param("query", String.valueOf(group1.getID())))
.andExpect(status().isOk())
.andExpect(content().contentType(contentType))
.andExpect(jsonPath("$._embedded.groups", Matchers.contains(
GroupMatcher.matchGroupEntry(group1.getID(), group1.getName())
)))
.andExpect(jsonPath("$.page.totalElements", is(1)));
}
@Test
public void findByMetadataUnauthorized() throws Exception {
String authToken = getAuthToken(admin.getEmail(), password);
getClient().perform(get("/api/eperson/groups/search/byMetadata")
.param("query", "Administrator"))
.andExpect(status().isUnauthorized());
}
@Test
public void findByMetadataForbidden() throws Exception {
String authToken = getAuthToken(eperson.getEmail(), password);
getClient(authToken).perform(get("/api/eperson/groups/search/byMetadata")
.param("query", "Administrator"))
.andExpect(status().isForbidden());
}
@Test
public void findByMetadataUndefined() throws Exception {
String authToken = getAuthToken(admin.getEmail(), password);
getClient(authToken).perform(get("/api/eperson/groups/search/byMetadata")
.param("query", "Non-existing Group"))
.andExpect(status().isOk())
.andExpect(content().contentType(contentType))
.andExpect(jsonPath("$.page.totalElements", is(0)));
}
@Test
public void findByMetadataUnprocessable() throws Exception {
String authToken = getAuthToken(admin.getEmail(), password);
getClient(authToken).perform(get("/api/eperson/groups/search/byMetadata"))
.andExpect(status().isUnprocessableEntity());
}
@Test
public void patchGroupMetadataAuthorized() throws Exception {
runPatchMetadataTests(admin, 200);

View File

@@ -43,7 +43,8 @@ public class GroupMatcher {
*/
public static Matcher<? super Object> matchFullEmbeds() {
return matchEmbeds(
"groups[]"
"groups[]",
"epersons[]"
);
}
@@ -53,6 +54,7 @@ public class GroupMatcher {
public static Matcher<? super Object> matchLinks(UUID uuid) {
return HalMatcher.matchLinks(REST_SERVER_URL + "eperson/groups/" + uuid,
"groups",
"epersons",
"self"
);
}
@@ -63,7 +65,8 @@ public class GroupMatcher {
hasJsonPath("$.name", is(name)),
hasJsonPath("$.type", is("group")),
hasJsonPath("$._links.self.href", containsString("/api/eperson/groups/" + uuid.toString())),
hasJsonPath("$._links.groups.href", endsWith(uuid.toString() + "/groups"))
hasJsonPath("$._links.groups.href", endsWith(uuid.toString() + "/groups")),
hasJsonPath("$._links.epersons.href", endsWith(uuid.toString() + "/epersons"))
);
}
}