From d77dd7fa4f3e06463ae8e7b1590997091e8b10f5 Mon Sep 17 00:00:00 2001 From: Raf Ponsaerts Date: Wed, 21 Nov 2018 15:20:32 +0100 Subject: [PATCH 01/68] Added support for the CRUD operations on the Collection and Community REST endpoints --- .../app/rest/RestResourceController.java | 26 ++ .../rest/converter/CollectionConverter.java | 5 + .../rest/converter/CommunityConverter.java | 8 +- .../dspace/app/rest/model/CollectionRest.java | 13 + .../dspace/app/rest/model/CommunityRest.java | 10 + .../repository/CollectionRestRepository.java | 115 +++++++ .../repository/CommunityRestRepository.java | 67 ++++- .../rest/repository/DSpaceRestRepository.java | 16 + .../app/rest/utils/DSpaceObjectUtils.java | 43 +++ .../app/rest/CollectionRestRepositoryIT.java | 283 +++++++++++++++++- .../app/rest/CommunityRestRepositoryIT.java | 253 ++++++++++++++++ .../rest/WorkspaceItemRestRepositoryIT.java | 41 +-- .../app/rest/matcher/CollectionMatcher.java | 10 +- .../app/rest/matcher/CommunityMatcher.java | 8 +- 14 files changed, 859 insertions(+), 39 deletions(-) create mode 100644 dspace-spring-rest/src/main/java/org/dspace/app/rest/utils/DSpaceObjectUtils.java diff --git a/dspace-spring-rest/src/main/java/org/dspace/app/rest/RestResourceController.java b/dspace-spring-rest/src/main/java/org/dspace/app/rest/RestResourceController.java index 9e5c339455..74775a9314 100644 --- a/dspace-spring-rest/src/main/java/org/dspace/app/rest/RestResourceController.java +++ b/dspace-spring-rest/src/main/java/org/dspace/app/rest/RestResourceController.java @@ -981,4 +981,30 @@ public class RestResourceController implements InitializingBean { repository.delete(id); return ControllerUtils.toEmptyResponse(HttpStatus.NO_CONTENT); } + + + + @RequestMapping(method = RequestMethod.PUT, value = REGEX_REQUESTMAPPING_IDENTIFIER_AS_UUID) + public DSpaceResource put(HttpServletRequest request, + @PathVariable String apiCategory, @PathVariable String model, + @PathVariable UUID uuid, + @RequestBody(required = true) JsonNode jsonNode) { + return putOneInternal(request, apiCategory, model, uuid, jsonNode); + } + + private DSpaceResource putOneInternal(HttpServletRequest request, + String apiCategory, + String model, ID uuid, + JsonNode jsonNode) { + checkModelPluralForm(apiCategory, model); + DSpaceRestRepository repository = utils.getResourceRepository(apiCategory, model); + RestAddressableModel modelObject = null; + modelObject = repository.put(request, apiCategory, model, uuid, jsonNode); + if (modelObject == null) { + throw new ResourceNotFoundException(apiCategory + "." + model + " with id: " + uuid + " not found"); + } + DSpaceResource result = repository.wrapResource(modelObject); + linkService.addLinks(result); + return result; + } } diff --git a/dspace-spring-rest/src/main/java/org/dspace/app/rest/converter/CollectionConverter.java b/dspace-spring-rest/src/main/java/org/dspace/app/rest/converter/CollectionConverter.java index a4c368d2b4..d3d641510f 100644 --- a/dspace-spring-rest/src/main/java/org/dspace/app/rest/converter/CollectionConverter.java +++ b/dspace-spring-rest/src/main/java/org/dspace/app/rest/converter/CollectionConverter.java @@ -66,6 +66,11 @@ public class CollectionConverter col.setDefaultAccessConditions(getDefaultBitstreamPoliciesForCollection(obj.getID())); + try { + col.setOwningCommunity(obj.getCommunities().get(0).getID().toString()); + } catch (SQLException e) { + log.error(e.getMessage(), e); + } return col; } diff --git a/dspace-spring-rest/src/main/java/org/dspace/app/rest/converter/CommunityConverter.java b/dspace-spring-rest/src/main/java/org/dspace/app/rest/converter/CommunityConverter.java index b1883ccff0..edb4528482 100644 --- a/dspace-spring-rest/src/main/java/org/dspace/app/rest/converter/CommunityConverter.java +++ b/dspace-spring-rest/src/main/java/org/dspace/app/rest/converter/CommunityConverter.java @@ -64,7 +64,13 @@ public class CommunityConverter } } com.setSubCommunities(communityRest); - + List parentCommunities = obj.getParentCommunities(); + if (parentCommunities.size() > 0) { + Community parentCommunity = parentCommunities.get(0); + if (parentCommunity != null) { + com.setOwningCommunity(parentCommunity.getID().toString()); + } + } return com; } diff --git a/dspace-spring-rest/src/main/java/org/dspace/app/rest/model/CollectionRest.java b/dspace-spring-rest/src/main/java/org/dspace/app/rest/model/CollectionRest.java index f5dde2c043..cc55ef3f55 100644 --- a/dspace-spring-rest/src/main/java/org/dspace/app/rest/model/CollectionRest.java +++ b/dspace-spring-rest/src/main/java/org/dspace/app/rest/model/CollectionRest.java @@ -10,6 +10,7 @@ package org.dspace.app.rest.model; import java.util.List; import com.fasterxml.jackson.annotation.JsonIgnore; +import com.fasterxml.jackson.annotation.JsonProperty; /** * The Collection REST Resource @@ -28,6 +29,8 @@ public class CollectionRest extends DSpaceObjectRest { @JsonIgnore private BitstreamRest logo; + private String owningCommunity; + @JsonIgnore private List defaultAccessConditions; @@ -45,6 +48,7 @@ public class CollectionRest extends DSpaceObjectRest { } @Override + @JsonProperty(access = JsonProperty.Access.READ_ONLY) public String getType() { return NAME; } @@ -58,4 +62,13 @@ public class CollectionRest extends DSpaceObjectRest { public void setDefaultAccessConditions(List defaultAccessConditions) { this.defaultAccessConditions = defaultAccessConditions; } + + public String getOwningCommunity() { + return owningCommunity; + } + + public void setOwningCommunity(String owningCommunity) { + this.owningCommunity = owningCommunity; + } + } diff --git a/dspace-spring-rest/src/main/java/org/dspace/app/rest/model/CommunityRest.java b/dspace-spring-rest/src/main/java/org/dspace/app/rest/model/CommunityRest.java index 19d3decaee..53b9f1333c 100644 --- a/dspace-spring-rest/src/main/java/org/dspace/app/rest/model/CommunityRest.java +++ b/dspace-spring-rest/src/main/java/org/dspace/app/rest/model/CommunityRest.java @@ -24,6 +24,16 @@ public class CommunityRest extends DSpaceObjectRest { @JsonIgnore private BitstreamRest logo; + private String owningCommunity; + + public String getOwningCommunity() { + return owningCommunity; + } + + public void setOwningCommunity(String owningCommunity) { + this.owningCommunity = owningCommunity; + } + private List collections; @LinkRest(linkClass = CollectionRest.class) diff --git a/dspace-spring-rest/src/main/java/org/dspace/app/rest/repository/CollectionRestRepository.java b/dspace-spring-rest/src/main/java/org/dspace/app/rest/repository/CollectionRestRepository.java index 66474c6029..2c0b9bf44a 100644 --- a/dspace-spring-rest/src/main/java/org/dspace/app/rest/repository/CollectionRestRepository.java +++ b/dspace-spring-rest/src/main/java/org/dspace/app/rest/repository/CollectionRestRepository.java @@ -7,23 +7,38 @@ */ package org.dspace.app.rest.repository; +import java.io.IOException; import java.sql.SQLException; import java.util.ArrayList; import java.util.List; import java.util.UUID; +import javax.servlet.ServletInputStream; +import javax.servlet.http.HttpServletRequest; +import javax.ws.rs.BadRequestException; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import org.apache.commons.lang3.StringUtils; +import org.apache.log4j.Logger; import org.dspace.app.rest.Parameter; import org.dspace.app.rest.SearchRestMethod; import org.dspace.app.rest.converter.CollectionConverter; +import org.dspace.app.rest.exception.RepositoryMethodNotImplementedException; +import org.dspace.app.rest.exception.UnprocessableEntityException; import org.dspace.app.rest.model.CollectionRest; import org.dspace.app.rest.model.CommunityRest; +import org.dspace.app.rest.model.MetadataEntryRest; import org.dspace.app.rest.model.hateoas.CollectionResource; +import org.dspace.app.rest.utils.DSpaceObjectUtils; +import org.dspace.authorize.AuthorizeException; import org.dspace.content.Collection; import org.dspace.content.Community; import org.dspace.content.service.CollectionService; import org.dspace.content.service.CommunityService; import org.dspace.core.Constants; import org.dspace.core.Context; +import org.dspace.util.UUIDUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.domain.Page; import org.springframework.data.domain.PageImpl; @@ -41,6 +56,8 @@ import org.springframework.stereotype.Component; @Component(CollectionRest.CATEGORY + "." + CollectionRest.NAME) public class CollectionRestRepository extends DSpaceRestRepository { + private static final Logger log = Logger.getLogger(CollectionRestRepository.class); + @Autowired CommunityService communityService; @@ -50,6 +67,9 @@ public class CollectionRestRepository extends DSpaceRestRepository metadataEntryRestList = collectionRest.getMetadata(); + collection = (Collection) dspaceObjectUtils.replaceMetadataValues(context, + collection, + metadataEntryRestList); + } else { + throw new IllegalArgumentException("The UUID in the Json and the UUID in the url do not match: " + + id + ", " + + collectionRest.getId()); + } + return converter.fromModel(collection); + } + @Override + @PreAuthorize("hasAuthority('ADMIN')") + protected void delete(Context context, UUID id) throws AuthorizeException { + Collection collection = null; + try { + collection = cs.find(context, id); + if (collection == null) { + throw new ResourceNotFoundException( + CollectionRest.CATEGORY + "." + CollectionRest.NAME + " with id: " + id + " not found"); + } + } catch (SQLException e) { + throw new RuntimeException(e.getMessage(), e); + } + try { + cs.delete(context, collection); + } catch (SQLException | IOException e) { + throw new RuntimeException(e.getMessage(), e); + } + } + } \ No newline at end of file diff --git a/dspace-spring-rest/src/main/java/org/dspace/app/rest/repository/CommunityRestRepository.java b/dspace-spring-rest/src/main/java/org/dspace/app/rest/repository/CommunityRestRepository.java index dc20cd945f..f81630ae9f 100644 --- a/dspace-spring-rest/src/main/java/org/dspace/app/rest/repository/CommunityRestRepository.java +++ b/dspace-spring-rest/src/main/java/org/dspace/app/rest/repository/CommunityRestRepository.java @@ -15,19 +15,26 @@ import java.util.UUID; import javax.servlet.ServletInputStream; import javax.servlet.http.HttpServletRequest; +import javax.ws.rs.BadRequestException; +import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; +import com.google.gson.Gson; +import org.apache.commons.lang3.StringUtils; import org.dspace.app.rest.Parameter; import org.dspace.app.rest.SearchRestMethod; import org.dspace.app.rest.converter.CommunityConverter; +import org.dspace.app.rest.exception.RepositoryMethodNotImplementedException; import org.dspace.app.rest.exception.UnprocessableEntityException; import org.dspace.app.rest.model.CommunityRest; import org.dspace.app.rest.model.MetadataEntryRest; import org.dspace.app.rest.model.hateoas.CommunityResource; +import org.dspace.app.rest.utils.DSpaceObjectUtils; import org.dspace.authorize.AuthorizeException; import org.dspace.content.Community; import org.dspace.content.service.CommunityService; import org.dspace.core.Context; +import org.dspace.util.UUIDUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.domain.Page; import org.springframework.data.domain.PageImpl; @@ -51,6 +58,9 @@ public class CommunityRestRepository extends DSpaceRestRepository metadataEntryRestList = communityRest.getMetadata(); + community = (Community) dspaceObjectUtils.replaceMetadataValues(context, community, metadataEntryRestList); + } else { + throw new IllegalArgumentException("The UUID in the Json and the UUID in the url do not match: " + + id + ", " + + communityRest.getId()); + } + return converter.fromModel(community); + } + @Override + @PreAuthorize("hasAuthority('ADMIN')") + protected void delete(Context context, UUID id) throws AuthorizeException { + Community community = null; + try { + community = cs.find(context, id); + if (community == null) { + throw new ResourceNotFoundException( + CommunityRest.CATEGORY + "." + CommunityRest.NAME + " with id: " + id + " not found"); + } + } catch (SQLException e) { + throw new RuntimeException(e.getMessage(), e); + } + try { + cs.delete(context, community); + } catch (SQLException | IOException e) { + throw new RuntimeException(e.getMessage(), e); + } + } + } \ No newline at end of file diff --git a/dspace-spring-rest/src/main/java/org/dspace/app/rest/repository/DSpaceRestRepository.java b/dspace-spring-rest/src/main/java/org/dspace/app/rest/repository/DSpaceRestRepository.java index 5248cf024f..4be7596ad4 100644 --- a/dspace-spring-rest/src/main/java/org/dspace/app/rest/repository/DSpaceRestRepository.java +++ b/dspace-spring-rest/src/main/java/org/dspace/app/rest/repository/DSpaceRestRepository.java @@ -14,6 +14,7 @@ import java.sql.SQLException; import javax.servlet.http.HttpServletRequest; +import com.fasterxml.jackson.databind.JsonNode; import org.apache.logging.log4j.Logger; import org.dspace.app.rest.exception.PatchBadRequestException; import org.dspace.app.rest.exception.RESTAuthorizationException; @@ -406,5 +407,20 @@ public abstract class DSpaceRestRepository metadataEntryRestList) + throws SQLException, AuthorizeException { + DSpaceObjectService dSpaceObjectService = contentServiceFactory.getDSpaceObjectService(dSpaceObject); + dSpaceObjectService.clearMetadata(context, dSpaceObject, Item.ANY, Item.ANY, Item.ANY, Item.ANY); + for (MetadataEntryRest mer : metadataEntryRestList) { + String[] metadatakey = mer.getKey().split("\\."); + dSpaceObjectService.addMetadata(context, dSpaceObject, metadatakey[0], metadatakey[1], + metadatakey.length == 3 ? metadatakey[2] : null, mer.getLanguage(), mer.getValue()); + } + dSpaceObjectService.update(context, dSpaceObject); + return dSpaceObject; + } +} diff --git a/dspace-spring-rest/src/test/java/org/dspace/app/rest/CollectionRestRepositoryIT.java b/dspace-spring-rest/src/test/java/org/dspace/app/rest/CollectionRestRepositoryIT.java index e9de5c69d6..d9f17406e5 100644 --- a/dspace-spring-rest/src/test/java/org/dspace/app/rest/CollectionRestRepositoryIT.java +++ b/dspace-spring-rest/src/test/java/org/dspace/app/rest/CollectionRestRepositoryIT.java @@ -7,22 +7,34 @@ */ package org.dspace.app.rest; +import static com.jayway.jsonpath.matchers.JsonPathMatchers.hasJsonPath; +import static org.hamcrest.Matchers.empty; import static org.hamcrest.Matchers.is; +import static org.hamcrest.Matchers.not; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.delete; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.put; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; +import java.util.Arrays; import java.util.UUID; +import com.fasterxml.jackson.databind.ObjectMapper; import org.dspace.app.rest.builder.CollectionBuilder; import org.dspace.app.rest.builder.CommunityBuilder; import org.dspace.app.rest.matcher.CollectionMatcher; +import org.dspace.app.rest.matcher.CommunityMetadataMatcher; +import org.dspace.app.rest.model.CollectionRest; +import org.dspace.app.rest.model.MetadataEntryRest; import org.dspace.app.rest.test.AbstractControllerIntegrationTest; import org.dspace.content.Collection; import org.dspace.content.Community; import org.hamcrest.Matchers; import org.junit.Test; +import org.springframework.http.MediaType; public class CollectionRestRepositoryIT extends AbstractControllerIntegrationTest { @@ -51,8 +63,10 @@ public class CollectionRestRepositoryIT extends AbstractControllerIntegrationTes .andExpect(status().isOk()) .andExpect(content().contentType(contentType)) .andExpect(jsonPath("$._embedded.collections", Matchers.containsInAnyOrder( - CollectionMatcher.matchCollectionEntry(col1.getName(), col1.getID(), col1.getHandle()), - CollectionMatcher.matchCollectionEntry(col2.getName(), col2.getID(), col2.getHandle()) + CollectionMatcher.matchCollectionEntry(col1.getName(), col1.getID(), col1.getHandle(), + col1.getCommunities().get(0).getID()), + CollectionMatcher.matchCollectionEntry(col2.getName(), col2.getID(), col2.getHandle(), + col2.getCommunities().get(0).getID()) ))); } @@ -81,11 +95,13 @@ public class CollectionRestRepositoryIT extends AbstractControllerIntegrationTes .andExpect(status().isOk()) .andExpect(content().contentType(contentType)) .andExpect(jsonPath("$._embedded.collections", Matchers.contains( - CollectionMatcher.matchCollectionEntry(col1.getName(), col1.getID(), col1.getHandle()) + CollectionMatcher.matchCollectionEntry(col1.getName(), col1.getID(), col1.getHandle(), + col1.getCommunities().get(0).getID()) ))) .andExpect(jsonPath("$._embedded.collections", Matchers.not( Matchers.contains( - CollectionMatcher.matchCollectionEntry(col2.getName(), col2.getID(), col2.getHandle()) + CollectionMatcher.matchCollectionEntry(col2.getName(), col2.getID(), col2.getHandle(), + col1.getCommunities().get(0).getID()) ) ))); @@ -95,11 +111,13 @@ public class CollectionRestRepositoryIT extends AbstractControllerIntegrationTes .andExpect(status().isOk()) .andExpect(content().contentType(contentType)) .andExpect(jsonPath("$._embedded.collections", Matchers.contains( - CollectionMatcher.matchCollectionEntry(col2.getName(), col2.getID(), col2.getHandle()) + CollectionMatcher.matchCollectionEntry(col2.getName(), col2.getID(), col2.getHandle(), + col2.getCommunities().get(0).getID()) ))) .andExpect(jsonPath("$._embedded.collections", Matchers.not( Matchers.contains( - CollectionMatcher.matchCollectionEntry(col1.getName(), col1.getID(), col1.getHandle()) + CollectionMatcher.matchCollectionEntry(col1.getName(), col1.getID(), col1.getHandle(), + col1.getCommunities().get(0).getID()) ) ))); } @@ -129,11 +147,13 @@ public class CollectionRestRepositoryIT extends AbstractControllerIntegrationTes .andExpect(status().isOk()) .andExpect(content().contentType(contentType)) .andExpect(jsonPath("$", is( - CollectionMatcher.matchCollectionEntry(col1.getName(), col1.getID(), col1.getHandle()) + CollectionMatcher.matchCollectionEntry(col1.getName(), col1.getID(), col1.getHandle(), + col1.getCommunities().get(0).getID()) ))) .andExpect(jsonPath("$", Matchers.not( is( - CollectionMatcher.matchCollectionEntry(col2.getName(), col2.getID(), col2.getHandle()) + CollectionMatcher.matchCollectionEntry(col2.getName(), col2.getID(), col2.getHandle(), + col1.getCommunities().get(0).getID()) )))); } @@ -161,11 +181,13 @@ public class CollectionRestRepositoryIT extends AbstractControllerIntegrationTes .andExpect(status().isOk()) .andExpect(content().contentType(contentType)) .andExpect(jsonPath("$", is( - CollectionMatcher.matchCollectionEntry(col1.getName(), col1.getID(), col1.getHandle()) + CollectionMatcher.matchCollectionEntry(col1.getName(), col1.getID(), col1.getHandle(), + col1.getCommunities().get(0).getID()) ))) .andExpect(jsonPath("$", Matchers.not( is( - CollectionMatcher.matchCollectionEntry(col2.getName(), col2.getID(), col2.getHandle()) + CollectionMatcher.matchCollectionEntry(col2.getName(), col2.getID(), col2.getHandle(), + col1.getCommunities().get(0).getID()) ))) ) ; @@ -276,4 +298,245 @@ public class CollectionRestRepositoryIT extends AbstractControllerIntegrationTes .andExpect(status().isNotFound()); } + @Test + public void findCollectionWithOwningCommunity() throws Exception { + + //We turn off the authorization system in order to create the structure as defined below + context.turnOffAuthorisationSystem(); + //** GIVEN ** + //1. A community-collection structure with one parent community with sub-community and one collection. + parentCommunity = CommunityBuilder.createCommunity(context) + .withName("Parent Community") + .build(); + Community child1 = CommunityBuilder.createSubCommunity(context, parentCommunity) + .withName("Sub Community") + .build(); + Community child2 = CommunityBuilder.createSubCommunity(context, parentCommunity) + .withName("Sub Community Two") + .build(); + Collection col1 = CollectionBuilder.createCollection(context, child1).withName("Collection 1").build(); + Collection col2 = CollectionBuilder.createCollection(context, child2).withName("Collection 2").build(); + + getClient().perform(get("/api/core/collections/" + col1.getID())) + .andExpect(status().isOk()) + .andExpect(content().contentType(contentType)) + .andExpect(jsonPath("$", is( + CollectionMatcher.matchCollectionEntry(col1.getName(), col1.getID(), col1.getHandle(), + col1.getCommunities().get(0).getID()) + ))) + .andExpect(jsonPath("$", Matchers.not( + is( + CollectionMatcher.matchCollectionEntry(col2.getName(), col2.getID(), col2.getHandle(), + col1.getCommunities().get(0).getID()) + )))); + } + + @Test + public void updateTest() throws Exception { + //We turn off the authorization system in order to create the structure as defined below + context.turnOffAuthorisationSystem(); + + //** GIVEN ** + //1. A community-collection structure with one parent community with sub-community and one collection. + parentCommunity = CommunityBuilder.createCommunity(context) + .withName("Parent Community") + .build(); + Community child1 = CommunityBuilder.createSubCommunity(context, parentCommunity) + .withName("Sub Community") + .build(); + Collection col1 = CollectionBuilder.createCollection(context, child1).withName("Collection 1").build(); + + getClient().perform(get("/api/core/collections/" + col1.getID().toString())) + .andExpect(status().isOk()) + .andExpect(content().contentType(contentType)) + .andExpect(jsonPath("$", Matchers.is( + CollectionMatcher.matchCollectionEntry(col1.getName(), col1.getID(), col1.getHandle(), + col1.getCommunities().get(0).getID()) + ))) + .andExpect(jsonPath("$._links.self.href", Matchers.containsString("/api/core/collections"))) + ; + + String token = getAuthToken(admin.getEmail(), password); + + getClient(token).perform(put("/api/core/collections/" + col1.getID().toString()) + .contentType(MediaType.APPLICATION_JSON).content( + "{\"id\": \"" + col1.getID() + "\",\"uuid\": " + + "\"" + col1.getID() + "\",\"name\": \"Electronic theses and " + + "dissertations (ETD)\",\"handle\": \"" + col1.getHandle() + "\",\"metadata\": " + + "[{\"key\": \"dc.description.abstract\",\"value\": \"\",\"language\": null}," + + "{\"key\": \"dc.title\",\"value\": \"Electronic theses and dissertations " + + "(ETD)\",\"language\": null}], \"owningCommunity\": \"" + + child1.getID() + "\",\"type\": \"collection\"}" + )) + .andExpect(status().isOk()) + ; + + getClient().perform(get("/api/core/collections/" + col1.getID().toString())) + .andExpect(status().isOk()) + .andExpect(content().contentType(contentType)) + .andExpect(jsonPath("$", Matchers.is( + CollectionMatcher.matchCollectionEntry("Electronic theses and dissertations (ETD)", + col1.getID(), col1.getHandle(), + col1.getCommunities().get(0).getID()) + ))) + .andExpect(jsonPath("$._links.self.href", + Matchers.containsString("/api/core/collections"))) + ; + } + + @Test + public void deleteTest() throws Exception { + //We turn off the authorization system in order to create the structure as defined below + context.turnOffAuthorisationSystem(); + + //** GIVEN ** + //1. A community-collection structure with one parent community with sub-community and one collection. + parentCommunity = CommunityBuilder.createCommunity(context) + .withName("Parent Community") + .withLogo("ThisIsSomeDummyText") + .build(); + + Community parentCommunity2 = CommunityBuilder.createCommunity(context) + .withName("Parent Community 2") + .withLogo("SomeTest") + .build(); + + Community parentCommunityChild1 = CommunityBuilder.createSubCommunity(context, parentCommunity) + .withName("Sub Community") + .build(); + + Collection col1 = CollectionBuilder.createCollection(context, parentCommunityChild1) + .withName("Collection 1") + .build(); + + String token = getAuthToken(admin.getEmail(), password); + + getClient(token).perform(get("/api/core/collections/" + col1.getID().toString())) + .andExpect(status().isOk()) + .andExpect(content().contentType(contentType)) + .andExpect(jsonPath("$", Matchers.is( + CollectionMatcher.matchCollectionEntry(col1.getName(), col1.getID(), col1.getHandle(), + col1.getCommunities().get(0).getID()) + ))) + .andExpect(jsonPath("$._links.self.href", + Matchers.containsString("/api/core/collections"))) ; + getClient(token).perform(delete("/api/core/collections/" + col1.getID().toString())) + .andExpect(status().isNoContent()) + ; + getClient(token).perform(get("/api/core/collections/" + col1.getID().toString())) + .andExpect(status().isNotFound()) + ; + } + + @Test + public void deleteTestUnAuthorized() throws Exception { + //We turn off the authorization system in order to create the structure as defined below + context.turnOffAuthorisationSystem(); + + //** GIVEN ** + //1. A community-collection structure with one parent community with sub-community and one collection. + parentCommunity = CommunityBuilder.createCommunity(context) + .withName("Parent Community") + .withLogo("ThisIsSomeDummyText") + .build(); + + Community parentCommunity2 = CommunityBuilder.createCommunity(context) + .withName("Parent Community 2") + .withLogo("SomeTest") + .build(); + + Community parentCommunityChild1 = CommunityBuilder.createSubCommunity(context, parentCommunity) + .withName("Sub Community") + .build(); + + Collection col1 = CollectionBuilder.createCollection(context, parentCommunityChild1) + .withName("Collection 1") + .build(); + + getClient().perform(get("/api/core/collections/" + col1.getID().toString())) + .andExpect(status().isOk()) + .andExpect(content().contentType(contentType)) + .andExpect(jsonPath("$", Matchers.is( + CollectionMatcher.matchCollectionEntry(col1.getName(), col1.getID(), col1.getHandle(), + col1.getCommunities().get(0).getID()) + ))) + .andExpect(jsonPath("$._links.self.href", + Matchers.containsString("/api/core/collections"))) ; + getClient().perform(delete("/api/core/collections/" + col1.getID().toString())) + .andExpect(status().isUnauthorized()) + ; + } + + @Test + public void createTest() throws Exception { + context.turnOffAuthorisationSystem(); + + + //** GIVEN ** + //1. A community-collection structure with one parent community with sub-community and one collection. + parentCommunity = CommunityBuilder.createCommunity(context) + .withName("Parent Community") + .withLogo("ThisIsSomeDummyText") + .build(); + + ObjectMapper mapper = new ObjectMapper(); + CollectionRest collectionRest = new CollectionRest(); + // We send a name but the created collection should set this to the title + collectionRest.setName("Collection"); + collectionRest.setOwningCommunity(parentCommunity.getID().toString()); + MetadataEntryRest description = new MetadataEntryRest(); + description.setKey("dc.description"); + description.setValue("

Some cool HTML code here

"); + + MetadataEntryRest abs = new MetadataEntryRest(); + abs.setKey("dc.description.abstract"); + abs.setValue("Sample top-level community created via the REST API"); + + MetadataEntryRest contents = new MetadataEntryRest(); + contents.setKey("dc.description.tableofcontents"); + contents.setValue("

HTML News

"); + + MetadataEntryRest copyright = new MetadataEntryRest(); + copyright.setKey("dc.rights"); + copyright.setValue("Custom Copyright Text"); + + MetadataEntryRest title = new MetadataEntryRest(); + title.setKey("dc.title"); + title.setValue("Title Text"); + + collectionRest.setMetadata(Arrays.asList(description, + abs, + contents, + copyright, + title)); + + + String authToken = getAuthToken(admin.getEmail(), password); + getClient(authToken).perform(post("/api/core/collections") + .content(mapper.writeValueAsBytes(collectionRest)) + .contentType(contentType)) + .andExpect(status().isCreated()) + .andExpect(content().contentType(contentType)) + .andExpect(jsonPath("$", Matchers.allOf( + hasJsonPath("$.id", not(empty())), + hasJsonPath("$.uuid", not(empty())), + hasJsonPath("$.name", is("Title Text")), + hasJsonPath("$.handle", not(empty())), + hasJsonPath("$.owningCommunity", is(collectionRest.getOwningCommunity())), + hasJsonPath("$.type", is("collection")), + hasJsonPath("$.metadata", Matchers.containsInAnyOrder( + CommunityMetadataMatcher.matchMetadata("dc.description", + "

Some cool HTML code here

"), + CommunityMetadataMatcher.matchMetadata("dc.description.abstract", + "Sample top-level community " + + "created via the REST API"), + CommunityMetadataMatcher.matchMetadata("dc.description.tableofcontents", + "

HTML News

"), + CommunityMetadataMatcher.matchMetadata("dc.rights", + "Custom Copyright Text"), + CommunityMetadataMatcher.matchMetadata("dc.title", + "Title Text") + ))))); + + } } diff --git a/dspace-spring-rest/src/test/java/org/dspace/app/rest/CommunityRestRepositoryIT.java b/dspace-spring-rest/src/test/java/org/dspace/app/rest/CommunityRestRepositoryIT.java index d1f77fcdd1..55e2cecc48 100644 --- a/dspace-spring-rest/src/test/java/org/dspace/app/rest/CommunityRestRepositoryIT.java +++ b/dspace-spring-rest/src/test/java/org/dspace/app/rest/CommunityRestRepositoryIT.java @@ -10,9 +10,12 @@ package org.dspace.app.rest; import static com.jayway.jsonpath.matchers.JsonPathMatchers.hasJsonPath; import static org.hamcrest.Matchers.empty; import static org.hamcrest.Matchers.is; +import static org.hamcrest.Matchers.isEmptyOrNullString; import static org.hamcrest.Matchers.not; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.delete; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.put; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; @@ -33,6 +36,7 @@ import org.dspace.content.Collection; import org.dspace.content.Community; import org.hamcrest.Matchers; import org.junit.Test; +import org.springframework.http.MediaType; public class CommunityRestRepositoryIT extends AbstractControllerIntegrationTest { @@ -82,6 +86,7 @@ public class CommunityRestRepositoryIT extends AbstractControllerIntegrationTest hasJsonPath("$.uuid", not(empty())), hasJsonPath("$.name", is("Title Text")), hasJsonPath("$.handle", not(empty())), + hasJsonPath("$.owningCommunity", isEmptyOrNullString()), hasJsonPath("$.type", is("community")), hasJsonPath("$._links.collections.href", not(empty())), hasJsonPath("$._links.logo.href", not(empty())), @@ -101,6 +106,84 @@ public class CommunityRestRepositoryIT extends AbstractControllerIntegrationTest ))))); } + @Test + public void createWithParentTest() throws Exception { + context.turnOffAuthorisationSystem(); + //We turn off the authorization system in order to create the structure as defined below + context.turnOffAuthorisationSystem(); + + //** GIVEN ** + //1. A community-collection structure with one parent community with sub-community and one collection. + parentCommunity = CommunityBuilder.createCommunity(context) + .withName("Parent Community") + .build(); + + ObjectMapper mapper = new ObjectMapper(); + CommunityRest comm = new CommunityRest(); + // We send a name but the created community should set this to the title + comm.setName("Test Sub-Level Community"); + comm.setOwningCommunity(parentCommunity.getID().toString()); + MetadataEntryRest description = new MetadataEntryRest(); + description.setKey("dc.description"); + description.setValue("

Some cool HTML code here

"); + + MetadataEntryRest abs = new MetadataEntryRest(); + abs.setKey("dc.description.abstract"); + abs.setValue("Sample top-level community created via the REST API"); + + MetadataEntryRest contents = new MetadataEntryRest(); + contents.setKey("dc.description.tableofcontents"); + contents.setValue("

HTML News

"); + + MetadataEntryRest copyright = new MetadataEntryRest(); + copyright.setKey("dc.rights"); + copyright.setValue("Custom Copyright Text"); + + MetadataEntryRest title = new MetadataEntryRest(); + title.setKey("dc.title"); + title.setValue("Title Text"); + + comm.setMetadata(Arrays.asList(description, + abs, + contents, + copyright, + title)); + + String authToken = getAuthToken(admin.getEmail(), password); + getClient(authToken).perform(post("/api/core/communities") + .content(mapper.writeValueAsBytes(comm)) + .contentType(contentType)) + .andExpect(status().isCreated()) + .andExpect(content().contentType(contentType)) + .andExpect(jsonPath("$", Matchers.allOf( + hasJsonPath("$.id", not(empty())), + hasJsonPath("$.uuid", not(empty())), + hasJsonPath("$.name", is("Title Text")), + hasJsonPath("$.handle", not(empty())), + hasJsonPath("$.owningCommunity", is(parentCommunity.getID().toString())), + hasJsonPath("$.type", is("community")), + hasJsonPath("$._links.collections.href", not(empty())), + hasJsonPath("$._links.logo.href", not(empty())), + hasJsonPath("$._links.subcommunities.href", not(empty())), + hasJsonPath("$._links.self.href", not(empty())), + hasJsonPath("$.metadata", Matchers.containsInAnyOrder( + CommunityMetadataMatcher.matchMetadata("dc.description", + "

Some cool HTML code here

"), + CommunityMetadataMatcher.matchMetadata("dc.description.abstract", + "Sample top-level community " + + "created via the REST API"), + CommunityMetadataMatcher.matchMetadata("dc.description.tableofcontents", + "

HTML News

"), + CommunityMetadataMatcher.matchMetadata("dc.rights", + "Custom Copyright Text"), + CommunityMetadataMatcher.matchMetadata("dc.title", + "Title Text") + ))))); + + + + } + @Test public void createUnauthorizedTest() throws Exception { @@ -511,4 +594,174 @@ public class CommunityRestRepositoryIT extends AbstractControllerIntegrationTest getClient().perform(get("/api/core/communities/" + UUID.randomUUID())).andExpect(status().isNotFound()); } + + @Test + public void updateTest() throws Exception { + //We turn off the authorization system in order to create the structure as defined below + context.turnOffAuthorisationSystem(); + + //** GIVEN ** + //1. A community-collection structure with one parent community with sub-community and one collection. + parentCommunity = CommunityBuilder.createCommunity(context) + .withName("Parent Community") + .build(); + Community child1 = CommunityBuilder.createSubCommunity(context, parentCommunity) + .withName("Sub Community") + .build(); + Collection col1 = CollectionBuilder.createCollection(context, child1).withName("Collection 1").build(); + + getClient().perform(get("/api/core/communities/" + parentCommunity.getID().toString())) + .andExpect(status().isOk()) + .andExpect(content().contentType(contentType)) + .andExpect(jsonPath("$", Matchers.is( + CommunityMatcher.matchCommunityEntry(parentCommunity.getName(), parentCommunity.getID(), + parentCommunity.getHandle()) + ))) + .andExpect(jsonPath("$", Matchers.not( + Matchers.is( + CommunityMatcher.matchCommunityEntry(child1.getName(), child1.getID(), child1.getHandle()) + ) + ))) + .andExpect(jsonPath("$._links.self.href", Matchers.containsString("/api/core/communities"))) + ; + + String token = getAuthToken(admin.getEmail(), password); + + getClient(token).perform(put("/api/core/communities/" + parentCommunity.getID().toString()) + .contentType(MediaType.APPLICATION_JSON).content( + "{\"id\": \"" + parentCommunity.getID() + "\",\"uuid\": " + + "\"" + parentCommunity.getID() + "\",\"name\": \"Electronic theses and " + + "dissertations (ETD)\",\"handle\": \"123456789/5286\",\"metadata\": " + + "[{\"key\": \"dc.description.abstract\",\"value\": \"\",\"language\": null}," + + "{\"key\": \"dc.title\",\"value\": \"Electronic theses and dissertations " + + "(ETD)\",\"language\": null}],\"type\": \"community\"}" + )) + .andExpect(status().isOk()) + ; + + getClient().perform(get("/api/core/communities/" + parentCommunity.getID().toString())) + .andExpect(status().isOk()) + .andExpect(content().contentType(contentType)) + .andExpect(jsonPath("$", Matchers.is( + CommunityMatcher.matchCommunityEntry("Electronic theses and dissertations (ETD)", + parentCommunity.getID(), + parentCommunity.getHandle()) + ))) + .andExpect(jsonPath("$._links.self.href", Matchers.containsString("/api/core/communities"))) + ; + } + + @Test + public void deleteTest() throws Exception { + //We turn off the authorization system in order to create the structure as defined below + context.turnOffAuthorisationSystem(); + + //** GIVEN ** + //1. A community-collection structure with one parent community with sub-community and one collection. + parentCommunity = CommunityBuilder.createCommunity(context) + .withName("Parent Community") + .withLogo("ThisIsSomeDummyText") + .build(); + + Community parentCommunity2 = CommunityBuilder.createCommunity(context) + .withName("Parent Community 2") + .withLogo("SomeTest") + .build(); + + Community parentCommunityChild1 = CommunityBuilder.createSubCommunity(context, parentCommunity) + .withName("Sub Community") + .build(); + + Community parentCommunityChild2 = CommunityBuilder.createSubCommunity(context, parentCommunity) + .withName("Sub Community2") + .build(); + + Community parentCommunityChild2Child1 = CommunityBuilder.createSubCommunity(context, parentCommunityChild2) + .withName("Sub Sub Community") + .build(); + + + Community parentCommunity2Child1 = CommunityBuilder.createSubCommunity(context, parentCommunity2) + .withName("Sub2 Community") + .build(); + + Collection col1 = CollectionBuilder.createCollection(context, parentCommunityChild1) + .withName("Collection 1") + .build(); + + String token = getAuthToken(admin.getEmail(), password); + + getClient(token).perform(get("/api/core/communities/" + parentCommunity.getID().toString())) + .andExpect(status().isOk()) + .andExpect(content().contentType(contentType)) + .andExpect(jsonPath("$", Matchers.is( + CommunityMatcher.matchCommunityEntry(parentCommunity.getName(), parentCommunity.getID(), + parentCommunity.getHandle()) + ))) + .andExpect(jsonPath("$._links.self.href", + Matchers.containsString("/api/core/communities"))) ; + getClient(token).perform(delete("/api/core/communities/" + parentCommunity.getID().toString())) + .andExpect(status().isNoContent()) + ; + getClient(token).perform(get("/api/core/communities/" + parentCommunity.getID().toString())) + .andExpect(status().isNotFound()) + ; + + getClient(token).perform(get("/api/core/communities/" + parentCommunityChild1.getID().toString())) + .andExpect(status().isNotFound()) + ; + } + + @Test + public void deleteTestUnAuthorized() throws Exception { + //We turn off the authorization system in order to create the structure as defined below + context.turnOffAuthorisationSystem(); + + //** GIVEN ** + //1. A community-collection structure with one parent community with sub-community and one collection. + parentCommunity = CommunityBuilder.createCommunity(context) + .withName("Parent Community") + .withLogo("ThisIsSomeDummyText") + .build(); + + Community parentCommunity2 = CommunityBuilder.createCommunity(context) + .withName("Parent Community 2") + .withLogo("SomeTest") + .build(); + + Community parentCommunityChild1 = CommunityBuilder.createSubCommunity(context, parentCommunity) + .withName("Sub Community") + .build(); + + Community parentCommunityChild2 = CommunityBuilder.createSubCommunity(context, parentCommunity) + .withName("Sub Community2") + .build(); + + Community parentCommunityChild2Child1 = CommunityBuilder.createSubCommunity(context, parentCommunityChild2) + .withName("Sub Sub Community") + .build(); + + + Community parentCommunity2Child1 = CommunityBuilder.createSubCommunity(context, parentCommunity2) + .withName("Sub2 Community") + .build(); + + Collection col1 = CollectionBuilder.createCollection(context, parentCommunityChild1) + .withName("Collection 1") + .build(); + + + getClient().perform(get("/api/core/communities/" + parentCommunity.getID().toString())) + .andExpect(status().isOk()) + .andExpect(content().contentType(contentType)) + .andExpect(jsonPath("$", Matchers.is( + CommunityMatcher.matchCommunityEntry(parentCommunity.getName(), parentCommunity.getID(), + parentCommunity.getHandle()) + ))) + .andExpect(jsonPath("$._links.self.href", + Matchers.containsString("/api/core/communities"))) ; + getClient().perform(delete("/api/core/communities/" + parentCommunity.getID().toString())) + .andExpect(status().isUnauthorized()) + ; + } } diff --git a/dspace-spring-rest/src/test/java/org/dspace/app/rest/WorkspaceItemRestRepositoryIT.java b/dspace-spring-rest/src/test/java/org/dspace/app/rest/WorkspaceItemRestRepositoryIT.java index 885ad2d123..f9643bf1e5 100644 --- a/dspace-spring-rest/src/test/java/org/dspace/app/rest/WorkspaceItemRestRepositoryIT.java +++ b/dspace-spring-rest/src/test/java/org/dspace/app/rest/WorkspaceItemRestRepositoryIT.java @@ -61,7 +61,7 @@ public class WorkspaceItemRestRepositoryIT extends AbstractControllerIntegration @Test /** * All the workspaceitem should be returned regardless of the collection where they were created - * + * * @throws Exception */ public void findAllTest() throws Exception { @@ -114,7 +114,7 @@ public class WorkspaceItemRestRepositoryIT extends AbstractControllerIntegration @Test /** * The workspaceitem endpoint must provide proper pagination - * + * * @throws Exception */ public void findAllWithPaginationTest() throws Exception { @@ -179,7 +179,7 @@ public class WorkspaceItemRestRepositoryIT extends AbstractControllerIntegration @Test /** * The workspaceitem resource endpoint must expose the proper structure - * + * * @throws Exception */ public void findOneTest() throws Exception { @@ -212,7 +212,7 @@ public class WorkspaceItemRestRepositoryIT extends AbstractControllerIntegration @Test /** * The workspaceitem resource endpoint must expose the proper structure - * + * * @throws Exception */ public void findOneRelsTest() throws Exception { @@ -239,7 +239,8 @@ public class WorkspaceItemRestRepositoryIT extends AbstractControllerIntegration getClient().perform(get("/api/submission/workspaceitems/" + witem.getID() + "/collection")) .andExpect(status().isOk()) .andExpect(jsonPath("$", Matchers - .is(CollectionMatcher.matchCollectionEntry(col1.getName(), col1.getID(), col1.getHandle())))); + .is(CollectionMatcher.matchCollectionEntry(col1.getName(), col1.getID(), col1.getHandle(), + col1.getCommunities().get(0).getID())))); getClient().perform(get("/api/submission/workspaceitems/" + witem.getID() + "/item")).andExpect(status().isOk()) .andExpect(jsonPath("$", Matchers.is(ItemMatcher.matchItemWithTitleAndDateIssued(witem.getItem(), @@ -254,7 +255,7 @@ public class WorkspaceItemRestRepositoryIT extends AbstractControllerIntegration @Test /** * Check the response code for unexistent workspaceitem - * + * * @throws Exception */ public void findOneWrongUUIDTest() throws Exception { @@ -267,7 +268,7 @@ public class WorkspaceItemRestRepositoryIT extends AbstractControllerIntegration @Test /** * Removing a workspaceitem should result in delete of all the underline resources (item and bitstreams) - * + * * @throws Exception */ public void deleteOneTest() throws Exception { @@ -321,7 +322,7 @@ public class WorkspaceItemRestRepositoryIT extends AbstractControllerIntegration /** * Create three workspaceitem with two different submitter and verify that the findBySubmitter return the proper * list of workspaceitem for each submitter also paginating - * + * * @throws Exception */ public void findBySubmitterTest() throws Exception { @@ -424,7 +425,7 @@ public class WorkspaceItemRestRepositoryIT extends AbstractControllerIntegration /** * Test the creation of workspaceitem POSTing to the resource collection endpoint. It should respect the collection * param if present or use a default if it is not used - * + * * @throws Exception */ public void createEmptyWorkspateItemTest() throws Exception { @@ -466,7 +467,7 @@ public class WorkspaceItemRestRepositoryIT extends AbstractControllerIntegration @Test /** * Test the creation of workspaceitems POSTing to the resource collection endpoint a bibtex file - * + * * @throws Exception */ public void createMultipleWorkspaceItemFromFileTest() throws Exception { @@ -538,7 +539,7 @@ public class WorkspaceItemRestRepositoryIT extends AbstractControllerIntegration /** * Test the creation of a workspaceitem POSTing to the resource collection endpoint a PDF file. As a single item * will be created we expect to have the pdf file stored as a bitstream - * + * * @throws Exception */ public void createWorkspaceItemFromPDFFileTest() throws Exception { @@ -585,7 +586,7 @@ public class WorkspaceItemRestRepositoryIT extends AbstractControllerIntegration /** * Test the exposition of validation error for missing required metadata both at the creation time than on existent * workspaceitems - * + * * @throws Exception */ public void validationErrorsRequiredMetadataTest() throws Exception { @@ -646,7 +647,7 @@ public class WorkspaceItemRestRepositoryIT extends AbstractControllerIntegration @Test /** * Test the update of metadata - * + * * @throws Exception */ public void patchUpdateMetadataTest() throws Exception { @@ -703,7 +704,7 @@ public class WorkspaceItemRestRepositoryIT extends AbstractControllerIntegration @Test /** * Test delete of a metadata - * + * * @throws Exception */ public void patchDeleteMetadataTest() throws Exception { @@ -896,7 +897,7 @@ public class WorkspaceItemRestRepositoryIT extends AbstractControllerIntegration @Test /** * Test the addition of metadata - * + * * @throws Exception */ public void patchAddMetadataTest() throws Exception { @@ -955,7 +956,7 @@ public class WorkspaceItemRestRepositoryIT extends AbstractControllerIntegration @Test /** * Test the addition of metadata - * + * * @throws Exception */ public void patchAddMultipleMetadataValuesTest() throws Exception { @@ -1162,7 +1163,7 @@ public class WorkspaceItemRestRepositoryIT extends AbstractControllerIntegration @Test /** * Test the acceptance of the deposit license - * + * * @throws Exception */ public void patchAcceptLicenseTest() throws Exception { @@ -1324,7 +1325,7 @@ public class WorkspaceItemRestRepositoryIT extends AbstractControllerIntegration @Test /** * Test the reject of the deposit license - * + * * @throws Exception */ public void patchRejectLicenseTest() throws Exception { @@ -1491,7 +1492,7 @@ public class WorkspaceItemRestRepositoryIT extends AbstractControllerIntegration @Test /** * Test update of bitstream metadata in the upload section - * + * * @throws Exception */ public void patchUploadTest() throws Exception { @@ -1618,7 +1619,7 @@ public class WorkspaceItemRestRepositoryIT extends AbstractControllerIntegration @Test /** * Test the upload of files in the upload over section - * + * * @throws Exception */ public void uploadTest() throws Exception { diff --git a/dspace-spring-rest/src/test/java/org/dspace/app/rest/matcher/CollectionMatcher.java b/dspace-spring-rest/src/test/java/org/dspace/app/rest/matcher/CollectionMatcher.java index 9986a1072a..1232deb381 100644 --- a/dspace-spring-rest/src/test/java/org/dspace/app/rest/matcher/CollectionMatcher.java +++ b/dspace-spring-rest/src/test/java/org/dspace/app/rest/matcher/CollectionMatcher.java @@ -22,17 +22,19 @@ public class CollectionMatcher { private CollectionMatcher() { } - public static Matcher matchCollectionEntry(String name, UUID uuid, String handle) { - return matchCollectionEntry(name, uuid, handle, null); + public static Matcher matchCollectionEntry(String name, UUID uuid, String handle, UUID parentUuid) { + return matchCollectionEntry(name, uuid, handle, null, parentUuid); } - public static Matcher matchCollectionEntry(String name, UUID uuid, String handle, Bitstream logo) { + public static Matcher matchCollectionEntry(String name, UUID uuid, String handle, + Bitstream logo, UUID parentUuid) { return allOf( hasJsonPath("$.uuid", is(uuid.toString())), hasJsonPath("$.name", is(name)), hasJsonPath("$.handle", is(handle)), hasJsonPath("$.type", is("collection")), - hasJsonPath("$.metadata", Matchers.contains( + hasJsonPath("$.owningCommunity", is(parentUuid.toString())), + hasJsonPath("$.metadata", Matchers.hasItem( CollectionMetadataMatcher.matchTitle(name) )), matchLinks(uuid), diff --git a/dspace-spring-rest/src/test/java/org/dspace/app/rest/matcher/CommunityMatcher.java b/dspace-spring-rest/src/test/java/org/dspace/app/rest/matcher/CommunityMatcher.java index 585d955c2f..dad9330ae2 100644 --- a/dspace-spring-rest/src/test/java/org/dspace/app/rest/matcher/CommunityMatcher.java +++ b/dspace-spring-rest/src/test/java/org/dspace/app/rest/matcher/CommunityMatcher.java @@ -11,6 +11,7 @@ import static com.jayway.jsonpath.matchers.JsonPathMatchers.hasJsonPath; import static org.hamcrest.Matchers.allOf; import static org.hamcrest.Matchers.is; +import java.sql.SQLException; import java.util.UUID; import org.dspace.content.Collection; @@ -36,7 +37,7 @@ public class CommunityMatcher { hasJsonPath("$.name", is(name)), hasJsonPath("$.handle", is(handle)), hasJsonPath("$.type", is("community")), - hasJsonPath("$.metadata", Matchers.contains( + hasJsonPath("$.metadata", Matchers.hasItem( CommunityMetadataMatcher.matchMetadata("dc.title", name) )) ); @@ -53,12 +54,13 @@ public class CommunityMatcher { } public static Matcher matchCommunityWithCollectionEntry(String name, UUID uuid, String handle, - Collection col) { + Collection col) throws SQLException { return allOf( matchProperties(name, uuid, handle), hasJsonPath("$._embedded.collections._embedded.collections[0]", CollectionMatcher - .matchCollectionEntry(col.getName(), col.getID(), col.getHandle(), col.getLogo())), + .matchCollectionEntry(col.getName(), col.getID(), col.getHandle(), + col.getLogo(), col.getCommunities().get(0).getID())), hasJsonPath("$._embedded.logo", Matchers.not(Matchers.empty())), matchLinks(uuid) ); From 94ed795d00ec91464db03ded4883c66d838680aa Mon Sep 17 00:00:00 2001 From: "Mark H. Wood" Date: Wed, 22 Aug 2018 20:22:51 -0400 Subject: [PATCH 02/68] [DS-3989] Accumulate reports; implement arbitrary output paths, not just standard output. --- .../java/org/dspace/curate/CurationCli.java | 17 ++++++++++++-- .../main/java/org/dspace/curate/Curator.java | 19 ++++++++-------- .../curate/WorkflowCuratorServiceImpl.java | 22 ++++++++++++++++--- 3 files changed, 43 insertions(+), 15 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/curate/CurationCli.java b/dspace-api/src/main/java/org/dspace/curate/CurationCli.java index d5577cf368..44e29c76fc 100644 --- a/dspace-api/src/main/java/org/dspace/curate/CurationCli.java +++ b/dspace-api/src/main/java/org/dspace/curate/CurationCli.java @@ -9,6 +9,10 @@ package org.dspace.curate; import java.io.BufferedReader; import java.io.FileReader; +import java.io.OutputStream; +import java.io.OutputStreamWriter; +import java.io.PrintStream; +import java.io.Writer; import java.util.HashMap; import java.util.Iterator; import java.util.Map; @@ -18,6 +22,7 @@ import org.apache.commons.cli.CommandLineParser; import org.apache.commons.cli.HelpFormatter; import org.apache.commons.cli.Options; import org.apache.commons.cli.PosixParser; +import org.apache.commons.io.output.NullOutputStream; import org.dspace.content.factory.ContentServiceFactory; import org.dspace.core.Context; import org.dspace.core.factory.CoreServiceFactory; @@ -165,9 +170,17 @@ public class CurationCli { } Curator curator = new Curator(); - if (reporterName != null) { - curator.setReporter(reporterName); + OutputStream reporter; + if (null == reporterName) { + reporter = new NullOutputStream(); + } else if ("-".equals(reporterName)) { + reporter = System.out; + } else { + reporter = new PrintStream(reporterName); } + Writer reportWriter = new OutputStreamWriter(reporter); + curator.setReporter(reportWriter); + if (scope != null) { Curator.TxScope txScope = Curator.TxScope.valueOf(scope.toUpperCase()); curator.setTransactionScope(txScope); diff --git a/dspace-api/src/main/java/org/dspace/curate/Curator.java b/dspace-api/src/main/java/org/dspace/curate/Curator.java index 1a78544bc0..f8cf47f798 100644 --- a/dspace-api/src/main/java/org/dspace/curate/Curator.java +++ b/dspace-api/src/main/java/org/dspace/curate/Curator.java @@ -14,8 +14,10 @@ import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; +import java.util.logging.Level; import org.apache.logging.log4j.Logger; +import org.apache.logging.log4j.LogManager; import org.dspace.content.Collection; import org.dspace.content.Community; import org.dspace.content.DSpaceObject; @@ -69,16 +71,12 @@ public class Curator { INTERACTIVE, BATCH, ANY } - ; - // transaction scopes public static enum TxScope { OBJECT, CURATION, OPEN } - ; - - private static final Logger log = org.apache.logging.log4j.LogManager.getLogger(Curator.class); + private static final Logger log = LogManager.getLogger(); protected static final ThreadLocal curationCtx = new ThreadLocal<>(); @@ -86,7 +84,7 @@ public class Curator { protected Map trMap = new HashMap<>(); protected List perfList = new ArrayList<>(); protected TaskQueue taskQ = null; - protected String reporter = null; + protected Appendable reporter = null; protected Invoked iMode = null; protected TaskResolver resolver = new TaskResolver(); protected TxScope txScope = TxScope.OPEN; @@ -193,7 +191,7 @@ public class Curator { * causes reporting to standard out. * @return return self (Curator instance) with reporter set */ - public Curator setReporter(String reporter) { + public Curator setReporter(Appendable reporter) { this.reporter = reporter; return this; } @@ -346,9 +344,10 @@ public class Curator { * @param message the message to output to the reporting stream. */ public void report(String message) { - // Stub for now - if ("-".equals(reporter)) { - System.out.println(message); + try { + reporter.append(message); + } catch (IOException ex) { + log.error("Task reporting failure", ex); } } diff --git a/dspace-api/src/main/java/org/dspace/curate/WorkflowCuratorServiceImpl.java b/dspace-api/src/main/java/org/dspace/curate/WorkflowCuratorServiceImpl.java index 5cecc13b89..c821c29b61 100644 --- a/dspace-api/src/main/java/org/dspace/curate/WorkflowCuratorServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/curate/WorkflowCuratorServiceImpl.java @@ -14,8 +14,14 @@ import static javax.xml.stream.XMLStreamConstants.START_ELEMENT; import java.io.File; import java.io.FileInputStream; import java.io.IOException; +import java.io.PrintWriter; +import java.nio.file.Path; +import java.nio.file.Paths; import java.sql.SQLException; +import java.text.SimpleDateFormat; import java.util.ArrayList; +import java.util.Date; +import java.util.GregorianCalendar; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -56,9 +62,10 @@ import org.springframework.beans.factory.annotation.Autowired; public class WorkflowCuratorServiceImpl implements WorkflowCuratorService { /** - * log4j logger + * Logging category */ - private Logger log = org.apache.logging.log4j.LogManager.getLogger(WorkflowCuratorServiceImpl.class); + private static final Logger log + = org.apache.logging.log4j.LogManager.getLogger(); protected Map tsMap = new HashMap(); @@ -118,6 +125,7 @@ public class WorkflowCuratorServiceImpl implements WorkflowCuratorService { Curator curator = new Curator(); // are we going to perform, or just put on queue? if (step.queue != null) { + // The queue runner will call setReporter for (Task task : step.tasks) { curator.addTask(task.name); } @@ -125,7 +133,15 @@ public class WorkflowCuratorServiceImpl implements WorkflowCuratorService { basicWorkflowItemService.update(c, wfi); return false; } else { - return curate(curator, c, wfi); + Date now = GregorianCalendar.getInstance().getTime(); + SimpleDateFormat sdf = new SimpleDateFormat("YYYYMMDDThhmmssSSS"); + String filename = String.format("curation-%s.log", sdf.format(now)); + Path logPath = Paths.get( + configurationService.getProperty("dspace.dir"), "logs", filename); + try (PrintWriter reporter = new PrintWriter(logPath.toFile())) { + curator.setReporter(reporter); + return curate(curator, c, wfi); + } } } return true; From f23fef032f43d827eafc9d6c5d75a7e7d3de3139 Mon Sep 17 00:00:00 2001 From: "Mark H. Wood" Date: Fri, 24 Aug 2018 13:49:12 -0400 Subject: [PATCH 03/68] [DS-3989] Fix the date format. --- .../main/java/org/dspace/curate/WorkflowCuratorServiceImpl.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dspace-api/src/main/java/org/dspace/curate/WorkflowCuratorServiceImpl.java b/dspace-api/src/main/java/org/dspace/curate/WorkflowCuratorServiceImpl.java index c821c29b61..66c18c2cf3 100644 --- a/dspace-api/src/main/java/org/dspace/curate/WorkflowCuratorServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/curate/WorkflowCuratorServiceImpl.java @@ -134,7 +134,7 @@ public class WorkflowCuratorServiceImpl implements WorkflowCuratorService { return false; } else { Date now = GregorianCalendar.getInstance().getTime(); - SimpleDateFormat sdf = new SimpleDateFormat("YYYYMMDDThhmmssSSS"); + SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMddThhmmssSSS"); String filename = String.format("curation-%s.log", sdf.format(now)); Path logPath = Paths.get( configurationService.getProperty("dspace.dir"), "logs", filename); From d6d0e67017cd6dd5ce26eb88ea94c8f52a1422db Mon Sep 17 00:00:00 2001 From: "Mark H. Wood" Date: Mon, 27 Aug 2018 10:30:06 -0400 Subject: [PATCH 04/68] [DS-3989] Placate Checkstyle: unused import. --- dspace-api/src/main/java/org/dspace/curate/Curator.java | 1 - 1 file changed, 1 deletion(-) diff --git a/dspace-api/src/main/java/org/dspace/curate/Curator.java b/dspace-api/src/main/java/org/dspace/curate/Curator.java index f8cf47f798..da415aeca1 100644 --- a/dspace-api/src/main/java/org/dspace/curate/Curator.java +++ b/dspace-api/src/main/java/org/dspace/curate/Curator.java @@ -14,7 +14,6 @@ import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; -import java.util.logging.Level; import org.apache.logging.log4j.Logger; import org.apache.logging.log4j.LogManager; From 1478ab36fabd2c57a7ecd474d33163fb6805bbeb Mon Sep 17 00:00:00 2001 From: "Mark H. Wood" Date: Mon, 10 Sep 2018 21:07:36 -0400 Subject: [PATCH 05/68] [DS-3989] Let tests set multiple values on a configuration property. --- .../config/DSpaceConfigurationService.java | 19 +++++++++++++++++++ .../dspace/services/ConfigurationService.java | 10 ++++++++++ 2 files changed, 29 insertions(+) diff --git a/dspace-services/src/main/java/org/dspace/servicemanager/config/DSpaceConfigurationService.java b/dspace-services/src/main/java/org/dspace/servicemanager/config/DSpaceConfigurationService.java index 3d511a1e67..6e75d7f5d2 100644 --- a/dspace-services/src/main/java/org/dspace/servicemanager/config/DSpaceConfigurationService.java +++ b/dspace-services/src/main/java/org/dspace/servicemanager/config/DSpaceConfigurationService.java @@ -346,6 +346,25 @@ public final class DSpaceConfigurationService implements ConfigurationService { } } + @Override + public synchronized boolean addPropertyValue(String name, Object value) { + if (name == null) { + throw new IllegalArgumentException("name cannot be null for setting configuration"); + } + if (value == null) { + throw new IllegalArgumentException("configuration value may not be null"); + } + + // If the value is a type of String, trim any leading/trailing spaces before saving it. + if (String.class.isInstance(value)) { + value = ((String) value).trim(); + } + + boolean isNew = !configuration.containsKey(name); + configuration.addProperty(name, value); + return isNew; + } + /* (non-Javadoc) * @see org.dspace.services.ConfigurationService#setProperty(java.lang.String, java.lang.Object) */ diff --git a/dspace-services/src/main/java/org/dspace/services/ConfigurationService.java b/dspace-services/src/main/java/org/dspace/services/ConfigurationService.java index 050e4c089c..526a518a09 100644 --- a/dspace-services/src/main/java/org/dspace/services/ConfigurationService.java +++ b/dspace-services/src/main/java/org/dspace/services/ConfigurationService.java @@ -237,6 +237,16 @@ public interface ConfigurationService { */ public boolean hasProperty(String name); + /** + * Add a value to a configuration property. + * + * @param name the property name. May not be null. + * @param value the property value. May not be null. + * @return true if a new property was created. + * @throws IllegalArgumentException if the name or value is null. + */ + public boolean addPropertyValue(String name, Object value); + /** * Set a configuration property (setting) in the system. * Type is not important here since conversion happens automatically From ab224b25081bf3389f2c248b7cfc19a1509d1b79 Mon Sep 17 00:00:00 2001 From: "Mark H. Wood" Date: Mon, 10 Sep 2018 21:08:19 -0400 Subject: [PATCH 06/68] [DS-3989] Integration test for Curator's reporting. --- .../java/org/dspace/curate/ITCurator.java | 202 ++++++++++++++++++ 1 file changed, 202 insertions(+) create mode 100644 dspace-api/src/test/java/org/dspace/curate/ITCurator.java diff --git a/dspace-api/src/test/java/org/dspace/curate/ITCurator.java b/dspace-api/src/test/java/org/dspace/curate/ITCurator.java new file mode 100644 index 0000000000..ecb2667f08 --- /dev/null +++ b/dspace-api/src/test/java/org/dspace/curate/ITCurator.java @@ -0,0 +1,202 @@ +/** + * 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.curate; + +import java.io.IOException; +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.List; +import java.util.regex.Pattern; + +import org.dspace.AbstractUnitTest; +import org.dspace.authorize.AuthorizeException; +import org.dspace.content.Community; +import org.dspace.content.DSpaceObject; +import org.dspace.content.Site; +import org.dspace.content.factory.ContentServiceFactory; +import org.dspace.services.ConfigurationService; +import org.junit.After; +import org.junit.AfterClass; +import org.junit.Assert; +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.Test; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Drive the Curator and check results. + * + * @author mhwood + */ +public class ITCurator + extends AbstractUnitTest { + Logger LOG = LoggerFactory.getLogger(ITCurator.class); + + public ITCurator() { + } + + @BeforeClass + public static void setUpClass() { + } + + @AfterClass + public static void tearDownClass() { + } + + @Before + public void setUp() { + } + + @After + public void tearDown() { + } + + /** + * The report should contain contributions from all tasks and all curated objects. + * + * @throws SQLException passed through. + * @throws IOException passed through. + * @throws AuthorizeException passed through. + */ + @Test + public void testCurate_Reporting() + throws SQLException, IOException, AuthorizeException { + // Configure for testing. + ConfigurationService cfg = kernelImpl.getConfigurationService(); + cfg.setProperty("plugin.named.org.dspace.curate.CurationTask", + Task1.class.getName() + " = task1"); + cfg.addPropertyValue("plugin.named.org.dspace.curate.CurationTask", + Task2.class.getName() + " = task2"); + + // Create some structure. + context.turnOffAuthorisationSystem(); + Site site = ContentServiceFactory.getInstance() + .getSiteService() + .findSite(context); + Community community = ContentServiceFactory.getInstance() + .getCommunityService() + .create(null, context); + + // Run some tasks. + ListReporter reporter = new ListReporter(); + Curator curator = new Curator(); + curator.setReporter(reporter); + curator.addTask("task1"); + curator.addTask("task2"); + curator.curate(context, site); + + // Validate the results. + List report = reporter.getReport(); + for (String aReport : report) { + LOG.info("Report: {}", aReport); + } + Pattern pattern; + pattern = Pattern.compile(String.format("task1.*%s", site.getHandle())); + Assert.assertTrue("A report should mention 'task1' and site's handle", + reportMatcher(report, pattern)); + pattern = Pattern.compile(String.format("task1.*%s", community.getHandle())); + Assert.assertTrue("A report should mention 'task1' and the community's handle", + reportMatcher(report, pattern)); + pattern = Pattern.compile(String.format("task2.*%s", site.getHandle())); + Assert.assertTrue("A report should mention 'task2' and the Site's handle", + reportMatcher(report, pattern)); + pattern = Pattern.compile(String.format("task2.*%s", community.getHandle())); + Assert.assertTrue("A report should mention 'task2' and the community's handle", + reportMatcher(report, pattern)); + } + + /** + * Match a collection of strings against a regular expression.\ + * + * @param reports strings to be searched. + * @param pattern expression to be matched. + * @return true if at least one string matches the expression. + */ + private boolean reportMatcher(List reports, Pattern pattern) { + for (String aReport : reports) { + if (pattern.matcher(aReport).find()) { + return true; + } + } + return false; + } + + /** + * Dummy curation task for testing. Reports how it was invoked. + * + * @author mhwood + */ + public static class Task1 extends AbstractCurationTask { + public Task1() { + } + + @Override + public int perform(DSpaceObject dso) + throws IOException { + curator.report(String.format( + "Task1 received 'perform' on taskId '%s' for object '%s'%n", + taskId, dso.getHandle())); + return Curator.CURATE_SUCCESS; + } + } + + /** + * Dummy curation task for testing. Reports how it was invoked. + * + * @author mhwood + */ + public static class Task2 extends AbstractCurationTask { + public Task2() { + } + + @Override + public int perform(DSpaceObject dso) throws IOException { + curator.report(String.format( + "Task2 received 'perform' on taskId '%s' for object '%s'%n", + taskId, dso.getHandle())); + return Curator.CURATE_SUCCESS; + } + } + + /** + * Absorb report strings into a sequential collection. + */ + class ListReporter + implements Appendable { + private final List report = new ArrayList<>(); + + /** + * Get the content of the report accumulator. + * @return accumulated reports. + */ + List getReport() { + return report; + } + + @Override + public Appendable append(CharSequence cs) + throws IOException { + report.add(cs.toString()); + return this; + } + + @Override + public Appendable append(CharSequence cs, int i, int i1) + throws IOException { + throw new UnsupportedOperationException("Not supported yet."); + } + + @Override + public Appendable append(char c) + throws IOException { + throw new UnsupportedOperationException("Not supported yet."); + } + } +} From de33ece1c9ecb42d69c5b1fcc88f21ffa3cfc72f Mon Sep 17 00:00:00 2001 From: "Mark H. Wood" Date: Wed, 19 Sep 2018 12:07:26 -0400 Subject: [PATCH 07/68] [DS-3989] Pull report writing out through a pluggable interface, provide log and file plugins --- .../java/org/dspace/curate/FileReporter.java | 81 +++++++++++++++++++ .../java/org/dspace/curate/LogReporter.java | 70 ++++++++++++++++ .../main/java/org/dspace/curate/Reporter.java | 11 +++ .../curate/WorkflowCuratorServiceImpl.java | 15 ++-- 4 files changed, 171 insertions(+), 6 deletions(-) create mode 100644 dspace-api/src/main/java/org/dspace/curate/FileReporter.java create mode 100644 dspace-api/src/main/java/org/dspace/curate/LogReporter.java create mode 100644 dspace-api/src/main/java/org/dspace/curate/Reporter.java diff --git a/dspace-api/src/main/java/org/dspace/curate/FileReporter.java b/dspace-api/src/main/java/org/dspace/curate/FileReporter.java new file mode 100644 index 0000000000..7a82347d10 --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/curate/FileReporter.java @@ -0,0 +1,81 @@ + +package org.dspace.curate; + +import java.io.FileWriter; +import java.io.IOException; +import java.io.Writer; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.text.SimpleDateFormat; +import java.util.Date; +import java.util.GregorianCalendar; + +import org.dspace.services.ConfigurationService; +import org.dspace.utils.DSpace; + +/** + * Save a curation report to a unique file in the reports directory. + * Reports are named by the date and time of day, for example: + * "curation-20180916T113903045.report". + * + * @author mhwood + */ +public class FileReporter + implements Reporter { + private final Writer writer; + + /** + * Open a writer to a file in a directory named by the configuration + * property {@code reports.dir}, or in {@code [DSpace]/reports} if not + * configured. + * + * @throws IOException if there is a problem with the file path. + */ + public FileReporter() + throws IOException { + // Calculate a unique(?) file name. + Date now = GregorianCalendar.getInstance().getTime(); + SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMddThhmmssSSS"); + String filename = String.format("curation-%s.report", sdf.format(now)); + + // Build a path to the directory which is to receive the file. + ConfigurationService cfg = new DSpace().getConfigurationService(); + String reportDir = cfg.getProperty("report.dir"); + Path reportPath; + if (null == reportDir) { + reportPath = Paths.get(cfg.getProperty("dspace.dir"), + "reports", + filename); + } else { + reportPath = Paths.get(reportDir, filename); + } + + // Open the file. + writer = new FileWriter(reportPath.toFile()); + } + + @Override + public Appendable append(CharSequence cs) + throws IOException { + writer.append(cs); + return this; + } + + @Override + public Appendable append(CharSequence cs, int i, int i1) + throws IOException { + writer.append(cs, i, i1); + return this; + } + + @Override + public Appendable append(char c) throws IOException { + writer.append(c); + return this; + } + + @Override + public void close() throws Exception { + writer.close(); + } +} diff --git a/dspace-api/src/main/java/org/dspace/curate/LogReporter.java b/dspace-api/src/main/java/org/dspace/curate/LogReporter.java new file mode 100644 index 0000000000..115ae0193c --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/curate/LogReporter.java @@ -0,0 +1,70 @@ + +package org.dspace.curate; + +import java.io.IOException; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Write curation report records through the logging framework. + * Whole lines (strings ending in '\n') are written to a log category named by + * the task ID, for example "curation.virusscan". + * Any partial line is flushed when the reporter is {@code close()}d. + * + * @author mhwood + */ +public class LogReporter + implements Reporter { + private static Logger LOG; + private final String taskID; + private final StringBuilder buffer = new StringBuilder(); + + private LogReporter() { + taskID = null; + } + + public LogReporter(String object, String task) { + taskID = task; + } + + private Logger getLogger() { + if (null == LOG) { + LOG = LoggerFactory.getLogger("curation." + taskID); + } + return LOG; + } + + @Override + public Appendable append(CharSequence cs) + throws IOException { + for (int pos = 0; pos < cs.length(); pos++) { + char c = cs.charAt(pos); + if (c == '\n') { + getLogger().info(buffer.toString()); + buffer.delete(0, buffer.length()); // Clear the buffer + } else { + buffer.append(c); + } + } + return this; + } + + @Override + public Appendable append(CharSequence cs, int i, int i1) + throws IOException { + return append(cs.subSequence(i, i1)); + } + + @Override + public Appendable append(char c) + throws IOException { + return append(String.valueOf(c)); + } + + @Override + public void close() + throws Exception { + getLogger().info(buffer.toString()); + } +} diff --git a/dspace-api/src/main/java/org/dspace/curate/Reporter.java b/dspace-api/src/main/java/org/dspace/curate/Reporter.java new file mode 100644 index 0000000000..b48f833e57 --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/curate/Reporter.java @@ -0,0 +1,11 @@ + +package org.dspace.curate; + +/** + * A marker interface needed to make curation reporter classes into plugins. + * + * @author mhwood + */ +public interface Reporter + extends Appendable, AutoCloseable { +} diff --git a/dspace-api/src/main/java/org/dspace/curate/WorkflowCuratorServiceImpl.java b/dspace-api/src/main/java/org/dspace/curate/WorkflowCuratorServiceImpl.java index 66c18c2cf3..7389e4517b 100644 --- a/dspace-api/src/main/java/org/dspace/curate/WorkflowCuratorServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/curate/WorkflowCuratorServiceImpl.java @@ -36,6 +36,8 @@ import org.dspace.content.Item; import org.dspace.content.service.CollectionService; import org.dspace.core.Context; import org.dspace.core.LogManager; +import org.dspace.core.factory.CoreServiceFactory; +import org.dspace.core.service.PluginService; import org.dspace.curate.service.WorkflowCuratorService; import org.dspace.eperson.EPerson; import org.dspace.eperson.Group; @@ -133,14 +135,15 @@ public class WorkflowCuratorServiceImpl implements WorkflowCuratorService { basicWorkflowItemService.update(c, wfi); return false; } else { - Date now = GregorianCalendar.getInstance().getTime(); - SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMddThhmmssSSS"); - String filename = String.format("curation-%s.log", sdf.format(now)); - Path logPath = Paths.get( - configurationService.getProperty("dspace.dir"), "logs", filename); - try (PrintWriter reporter = new PrintWriter(logPath.toFile())) { + PluginService plugins = CoreServiceFactory.getInstance() + .getPluginService(); + try (Reporter reporter + = (Reporter) plugins + .getSinglePlugin(Reporter.class);) { curator.setReporter(reporter); return curate(curator, c, wfi); + } catch (Exception e) { + log.error("Failed to close report", e); } } } From 9f64b9aa954383d9de00e8558811b7dd161c5a4c Mon Sep 17 00:00:00 2001 From: "Mark H. Wood" Date: Wed, 19 Sep 2018 12:42:29 -0400 Subject: [PATCH 08/68] [DS-3989] Fix license blocks for new classes. --- .../src/main/java/org/dspace/curate/FileReporter.java | 9 ++++++++- .../src/main/java/org/dspace/curate/LogReporter.java | 7 +++++++ dspace-api/src/main/java/org/dspace/curate/Reporter.java | 7 +++++++ 3 files changed, 22 insertions(+), 1 deletion(-) diff --git a/dspace-api/src/main/java/org/dspace/curate/FileReporter.java b/dspace-api/src/main/java/org/dspace/curate/FileReporter.java index 7a82347d10..d0a2b1c261 100644 --- a/dspace-api/src/main/java/org/dspace/curate/FileReporter.java +++ b/dspace-api/src/main/java/org/dspace/curate/FileReporter.java @@ -1,3 +1,10 @@ +/** + * 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.curate; @@ -26,7 +33,7 @@ public class FileReporter /** * Open a writer to a file in a directory named by the configuration - * property {@code reports.dir}, or in {@code [DSpace]/reports} if not + * property {@code report.dir}, or in {@code [DSpace]/reports} if not * configured. * * @throws IOException if there is a problem with the file path. diff --git a/dspace-api/src/main/java/org/dspace/curate/LogReporter.java b/dspace-api/src/main/java/org/dspace/curate/LogReporter.java index 115ae0193c..8a2397b8ce 100644 --- a/dspace-api/src/main/java/org/dspace/curate/LogReporter.java +++ b/dspace-api/src/main/java/org/dspace/curate/LogReporter.java @@ -1,3 +1,10 @@ +/** + * 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.curate; diff --git a/dspace-api/src/main/java/org/dspace/curate/Reporter.java b/dspace-api/src/main/java/org/dspace/curate/Reporter.java index b48f833e57..c4c58f75cf 100644 --- a/dspace-api/src/main/java/org/dspace/curate/Reporter.java +++ b/dspace-api/src/main/java/org/dspace/curate/Reporter.java @@ -1,3 +1,10 @@ +/** + * 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.curate; From d9c80d8afceb0c2e920724e749e03039bc5f397d Mon Sep 17 00:00:00 2001 From: "Mark H. Wood" Date: Wed, 19 Sep 2018 13:47:03 -0400 Subject: [PATCH 09/68] [DS-3989] Fix checkstyle issues. --- .../java/org/dspace/curate/WorkflowCuratorServiceImpl.java | 6 ------ 1 file changed, 6 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/curate/WorkflowCuratorServiceImpl.java b/dspace-api/src/main/java/org/dspace/curate/WorkflowCuratorServiceImpl.java index 7389e4517b..64577e74f0 100644 --- a/dspace-api/src/main/java/org/dspace/curate/WorkflowCuratorServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/curate/WorkflowCuratorServiceImpl.java @@ -14,14 +14,8 @@ import static javax.xml.stream.XMLStreamConstants.START_ELEMENT; import java.io.File; import java.io.FileInputStream; import java.io.IOException; -import java.io.PrintWriter; -import java.nio.file.Path; -import java.nio.file.Paths; import java.sql.SQLException; -import java.text.SimpleDateFormat; import java.util.ArrayList; -import java.util.Date; -import java.util.GregorianCalendar; import java.util.HashMap; import java.util.List; import java.util.Map; From dbcb1f291c80ec53019e40d691c2c52a361f986e Mon Sep 17 00:00:00 2001 From: "Mark H. Wood" Date: Wed, 26 Sep 2018 20:49:25 -0400 Subject: [PATCH 10/68] [DS-3989] Fix invalid date format. --- dspace-api/src/main/java/org/dspace/curate/FileReporter.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dspace-api/src/main/java/org/dspace/curate/FileReporter.java b/dspace-api/src/main/java/org/dspace/curate/FileReporter.java index d0a2b1c261..796b35eff5 100644 --- a/dspace-api/src/main/java/org/dspace/curate/FileReporter.java +++ b/dspace-api/src/main/java/org/dspace/curate/FileReporter.java @@ -42,7 +42,7 @@ public class FileReporter throws IOException { // Calculate a unique(?) file name. Date now = GregorianCalendar.getInstance().getTime(); - SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMddThhmmssSSS"); + SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMdd'T'hhmmssSSS"); String filename = String.format("curation-%s.report", sdf.format(now)); // Build a path to the directory which is to receive the file. From ff8a5fac970e3a2213e89ae0dd3d21be96777580 Mon Sep 17 00:00:00 2001 From: "Mark H. Wood" Date: Wed, 26 Sep 2018 20:50:20 -0400 Subject: [PATCH 11/68] [DS-3989] Add configuration for the Reporter plugin. --- dspace/config/modules/submission-curation.cfg | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/dspace/config/modules/submission-curation.cfg b/dspace/config/modules/submission-curation.cfg index b69ba71cbb..ccb81da2bf 100644 --- a/dspace/config/modules/submission-curation.cfg +++ b/dspace/config/modules/submission-curation.cfg @@ -5,4 +5,8 @@ # to the scheduling of curation tasks during submission. # #---------------------------------------------------------------# # Scan for viruses -submission-curation.virus-scan = false \ No newline at end of file +submission-curation.virus-scan = false + +# Report serializer plugin. Uncomment exactly one, or configure your own. +plugin.single.org.dspace.curate.Reporter = org.dspace.curate.FileReporter +#plugin.single.org.dspace.curate.Reporter = org.dspace.curate.LogReporter From 6084f892b2d18a232de223d6c035073853e5b3b4 Mon Sep 17 00:00:00 2001 From: "Mark H. Wood" Date: Thu, 27 Sep 2018 07:49:38 -0400 Subject: [PATCH 12/68] [DS-3989] Remove unworkable task-id logging; ensure closure of Reporter. --- .../java/org/dspace/curate/LogReporter.java | 25 +++---------------- .../curate/WorkflowCuratorServiceImpl.java | 4 ++- 2 files changed, 7 insertions(+), 22 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/curate/LogReporter.java b/dspace-api/src/main/java/org/dspace/curate/LogReporter.java index 8a2397b8ce..d977d49da7 100644 --- a/dspace-api/src/main/java/org/dspace/curate/LogReporter.java +++ b/dspace-api/src/main/java/org/dspace/curate/LogReporter.java @@ -15,40 +15,23 @@ import org.slf4j.LoggerFactory; /** * Write curation report records through the logging framework. - * Whole lines (strings ending in '\n') are written to a log category named by - * the task ID, for example "curation.virusscan". + * Whole lines (strings ending in '\n') are written to the log category "curation". * Any partial line is flushed when the reporter is {@code close()}d. * * @author mhwood */ public class LogReporter implements Reporter { - private static Logger LOG; - private final String taskID; + private static final Logger LOG = LoggerFactory.getLogger("curation"); private final StringBuilder buffer = new StringBuilder(); - private LogReporter() { - taskID = null; - } - - public LogReporter(String object, String task) { - taskID = task; - } - - private Logger getLogger() { - if (null == LOG) { - LOG = LoggerFactory.getLogger("curation." + taskID); - } - return LOG; - } - @Override public Appendable append(CharSequence cs) throws IOException { for (int pos = 0; pos < cs.length(); pos++) { char c = cs.charAt(pos); if (c == '\n') { - getLogger().info(buffer.toString()); + LOG.info(buffer.toString()); buffer.delete(0, buffer.length()); // Clear the buffer } else { buffer.append(c); @@ -72,6 +55,6 @@ public class LogReporter @Override public void close() throws Exception { - getLogger().info(buffer.toString()); + LOG.info(buffer.toString()); } } diff --git a/dspace-api/src/main/java/org/dspace/curate/WorkflowCuratorServiceImpl.java b/dspace-api/src/main/java/org/dspace/curate/WorkflowCuratorServiceImpl.java index 64577e74f0..a3272c61b4 100644 --- a/dspace-api/src/main/java/org/dspace/curate/WorkflowCuratorServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/curate/WorkflowCuratorServiceImpl.java @@ -135,7 +135,9 @@ public class WorkflowCuratorServiceImpl implements WorkflowCuratorService { = (Reporter) plugins .getSinglePlugin(Reporter.class);) { curator.setReporter(reporter); - return curate(curator, c, wfi); + boolean status = curate(curator, c, wfi); + reporter.close(); + return status; } catch (Exception e) { log.error("Failed to close report", e); } From 85db4fe2bb5d9e7f3bd999113dd7057eb0c77c3e Mon Sep 17 00:00:00 2001 From: "Mark H. Wood" Date: Thu, 27 Sep 2018 22:32:58 -0400 Subject: [PATCH 13/68] [DS-3989] Commit testing task. --- .../dspace/ctask/test/WorkflowReportTest.java | 39 +++++++++++++++++++ .../java/org/dspace/curate/LogReporter.java | 4 +- 2 files changed, 42 insertions(+), 1 deletion(-) create mode 100644 dspace-api/src/main/java/org/dspace/ctask/test/WorkflowReportTest.java diff --git a/dspace-api/src/main/java/org/dspace/ctask/test/WorkflowReportTest.java b/dspace-api/src/main/java/org/dspace/ctask/test/WorkflowReportTest.java new file mode 100644 index 0000000000..21ffdd0260 --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/ctask/test/WorkflowReportTest.java @@ -0,0 +1,39 @@ +/** + * 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.ctask.test; + +import java.io.IOException; + +import org.dspace.content.DSpaceObject; +import org.dspace.curate.AbstractCurationTask; +import org.dspace.curate.Curator; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Curation task which simply reports its invocation without changing anything. + * Meant for testing. + * + * @author mhwood + */ +public class WorkflowReportTest + extends AbstractCurationTask { + private static final Logger LOG = LoggerFactory.getLogger(WorkflowReportTest.class); + + @Override + public int perform(DSpaceObject dso) + throws IOException { + LOG.info("Class {} as task {} received 'perform' for object {}", + WorkflowReportTest.class.getSimpleName(), taskId, dso); + curator.report(String.format( + "Class %s as task %s received 'perform' for object %s%n", + WorkflowReportTest.class.getSimpleName(), taskId, dso)); + return Curator.CURATE_SUCCESS; + } +} diff --git a/dspace-api/src/main/java/org/dspace/curate/LogReporter.java b/dspace-api/src/main/java/org/dspace/curate/LogReporter.java index d977d49da7..bd3ee3cffb 100644 --- a/dspace-api/src/main/java/org/dspace/curate/LogReporter.java +++ b/dspace-api/src/main/java/org/dspace/curate/LogReporter.java @@ -55,6 +55,8 @@ public class LogReporter @Override public void close() throws Exception { - LOG.info(buffer.toString()); + if (buffer.length() > 0) { + LOG.info(buffer.toString()); + } } } From c795c8b4b437972ca5dd4cd2d7c146e0a0cd3704 Mon Sep 17 00:00:00 2001 From: "Mark H. Wood" Date: Mon, 22 Oct 2018 16:08:54 -0400 Subject: [PATCH 14/68] [DS-3989] Clarify submission task configuration. --- dspace/config/modules/submission-curation.cfg | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/dspace/config/modules/submission-curation.cfg b/dspace/config/modules/submission-curation.cfg index ccb81da2bf..60442a9955 100644 --- a/dspace/config/modules/submission-curation.cfg +++ b/dspace/config/modules/submission-curation.cfg @@ -2,11 +2,15 @@ #------------SUBMISSION CURATION CONFIGURATIONS-----------------# #---------------------------------------------------------------# # This file contains configuration properties solely relating # -# to the scheduling of curation tasks during submission. # +# to the scheduling of curation tasks during submission -- that # +# is: when tasks are attached to a workflow. # #---------------------------------------------------------------# # Scan for viruses submission-curation.virus-scan = false -# Report serializer plugin. Uncomment exactly one, or configure your own. +# Report serializer plugin, to capture submission task reports. +# Uncomment exactly one, or configure your own. +# FileReporter writes reports to ${report.dir}/curation-yyyyMMddThhmmssSSS.report plugin.single.org.dspace.curate.Reporter = org.dspace.curate.FileReporter +# LogReporter writes report lines to the DSpace log. #plugin.single.org.dspace.curate.Reporter = org.dspace.curate.LogReporter From 1e5bcd47ce6209c3c3ab367da81e9ac0b8d800a4 Mon Sep 17 00:00:00 2001 From: "Mark H. Wood" Date: Mon, 22 Oct 2018 16:17:07 -0400 Subject: [PATCH 15/68] [DS-3989] Clarify help for --reporter option. --- dspace-api/src/main/java/org/dspace/curate/CurationCli.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/dspace-api/src/main/java/org/dspace/curate/CurationCli.java b/dspace-api/src/main/java/org/dspace/curate/CurationCli.java index 44e29c76fc..3832ddf3ec 100644 --- a/dspace-api/src/main/java/org/dspace/curate/CurationCli.java +++ b/dspace-api/src/main/java/org/dspace/curate/CurationCli.java @@ -62,7 +62,9 @@ public class CurationCli { options.addOption("e", "eperson", true, "email address of curating eperson"); options.addOption("r", "reporter", true, - "reporter to manage results - use '-' to report to console. If absent, no reporting"); + "relative or absolute path to the desired report file. " + + "Use '-' to report to console. " + + "If absent, no reporting"); options.addOption("s", "scope", true, "transaction scope to impose: use 'object', 'curation', or 'open'. If absent, 'open' " + "applies"); From 1d3a58958eb78bdb734a645e6debcf217e59a2c9 Mon Sep 17 00:00:00 2001 From: "Mark H. Wood" Date: Sat, 10 Nov 2018 07:47:35 -0500 Subject: [PATCH 16/68] [DS-3989] Update new method for Commons Configuration v2. --- .../dspace/servicemanager/config/DSpaceConfigurationService.java | 1 + 1 file changed, 1 insertion(+) diff --git a/dspace-services/src/main/java/org/dspace/servicemanager/config/DSpaceConfigurationService.java b/dspace-services/src/main/java/org/dspace/servicemanager/config/DSpaceConfigurationService.java index 6e75d7f5d2..8bf037cbe3 100644 --- a/dspace-services/src/main/java/org/dspace/servicemanager/config/DSpaceConfigurationService.java +++ b/dspace-services/src/main/java/org/dspace/servicemanager/config/DSpaceConfigurationService.java @@ -360,6 +360,7 @@ public final class DSpaceConfigurationService implements ConfigurationService { value = ((String) value).trim(); } + Configuration configuration = getConfiguration(); boolean isNew = !configuration.containsKey(name); configuration.addProperty(name, value); return isNew; From 844ef85583b2e24f3d877c342794cdf5977ac903 Mon Sep 17 00:00:00 2001 From: "Mark H. Wood" Date: Sat, 10 Nov 2018 07:48:10 -0500 Subject: [PATCH 17/68] [DS-3989] Placate Checkstyle. --- dspace-api/src/main/java/org/dspace/curate/Curator.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dspace-api/src/main/java/org/dspace/curate/Curator.java b/dspace-api/src/main/java/org/dspace/curate/Curator.java index da415aeca1..44733174df 100644 --- a/dspace-api/src/main/java/org/dspace/curate/Curator.java +++ b/dspace-api/src/main/java/org/dspace/curate/Curator.java @@ -15,8 +15,8 @@ import java.util.Iterator; import java.util.List; import java.util.Map; -import org.apache.logging.log4j.Logger; import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; import org.dspace.content.Collection; import org.dspace.content.Community; import org.dspace.content.DSpaceObject; From 8df33db457459b26d891240863840a757e421c25 Mon Sep 17 00:00:00 2001 From: "Mark H. Wood" Date: Wed, 5 Dec 2018 16:13:24 -0500 Subject: [PATCH 18/68] [DS-3989] Fix code from DS-3990 that was (expectedly) broken by DS-3989. --- dspace-api/src/test/java/org/dspace/curate/CuratorTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dspace-api/src/test/java/org/dspace/curate/CuratorTest.java b/dspace-api/src/test/java/org/dspace/curate/CuratorTest.java index 5761ee7ec7..8ca6b6c172 100644 --- a/dspace-api/src/test/java/org/dspace/curate/CuratorTest.java +++ b/dspace-api/src/test/java/org/dspace/curate/CuratorTest.java @@ -58,7 +58,7 @@ public class CuratorTest // Get and configure a Curator. Curator instance = new Curator(); - instance.setReporter("-"); // Send any report to standard out. FIXME when DS-3989 is merged + instance.setReporter(System.out); // Send any report to standard out. instance.addTask(TASK_NAME); // Configure the run. From e283f434675721d2675bf08537411dca2c5c6db7 Mon Sep 17 00:00:00 2001 From: Samuel Date: Tue, 18 Dec 2018 11:09:12 +0100 Subject: [PATCH 19/68] Added support for the CRUD operations on the Collection and Community REST endpoints - send parentCommunity as request parameter --- .../repository/CollectionRestRepository.java | 25 ++++++++++--------- .../repository/CommunityRestRepository.java | 25 ++++++++++--------- .../app/rest/CollectionRestRepositoryIT.java | 4 +-- .../app/rest/CommunityRestRepositoryIT.java | 2 +- 4 files changed, 29 insertions(+), 27 deletions(-) diff --git a/dspace-spring-rest/src/main/java/org/dspace/app/rest/repository/CollectionRestRepository.java b/dspace-spring-rest/src/main/java/org/dspace/app/rest/repository/CollectionRestRepository.java index 2c0b9bf44a..c3ca8a82cc 100644 --- a/dspace-spring-rest/src/main/java/org/dspace/app/rest/repository/CollectionRestRepository.java +++ b/dspace-spring-rest/src/main/java/org/dspace/app/rest/repository/CollectionRestRepository.java @@ -164,7 +164,7 @@ public class CollectionRestRepository extends DSpaceRestRepositorySome cool HTML code here

"); @@ -514,6 +513,7 @@ public class CollectionRestRepositoryIT extends AbstractControllerIntegrationTes String authToken = getAuthToken(admin.getEmail(), password); getClient(authToken).perform(post("/api/core/collections") .content(mapper.writeValueAsBytes(collectionRest)) + .param("parentCommunity", parentCommunity.getID().toString()) .contentType(contentType)) .andExpect(status().isCreated()) .andExpect(content().contentType(contentType)) @@ -522,7 +522,7 @@ public class CollectionRestRepositoryIT extends AbstractControllerIntegrationTes hasJsonPath("$.uuid", not(empty())), hasJsonPath("$.name", is("Title Text")), hasJsonPath("$.handle", not(empty())), - hasJsonPath("$.owningCommunity", is(collectionRest.getOwningCommunity())), + hasJsonPath("$.owningCommunity", is(parentCommunity.getID().toString())), hasJsonPath("$.type", is("collection")), hasJsonPath("$.metadata", Matchers.containsInAnyOrder( CommunityMetadataMatcher.matchMetadata("dc.description", diff --git a/dspace-spring-rest/src/test/java/org/dspace/app/rest/CommunityRestRepositoryIT.java b/dspace-spring-rest/src/test/java/org/dspace/app/rest/CommunityRestRepositoryIT.java index 55e2cecc48..079a748213 100644 --- a/dspace-spring-rest/src/test/java/org/dspace/app/rest/CommunityRestRepositoryIT.java +++ b/dspace-spring-rest/src/test/java/org/dspace/app/rest/CommunityRestRepositoryIT.java @@ -122,7 +122,6 @@ public class CommunityRestRepositoryIT extends AbstractControllerIntegrationTest CommunityRest comm = new CommunityRest(); // We send a name but the created community should set this to the title comm.setName("Test Sub-Level Community"); - comm.setOwningCommunity(parentCommunity.getID().toString()); MetadataEntryRest description = new MetadataEntryRest(); description.setKey("dc.description"); description.setValue("

Some cool HTML code here

"); @@ -152,6 +151,7 @@ public class CommunityRestRepositoryIT extends AbstractControllerIntegrationTest String authToken = getAuthToken(admin.getEmail(), password); getClient(authToken).perform(post("/api/core/communities") .content(mapper.writeValueAsBytes(comm)) + .param("parentCommunity", parentCommunity.getID().toString()) .contentType(contentType)) .andExpect(status().isCreated()) .andExpect(content().contentType(contentType)) From fb1a8b8c331773a6cc0ac482641f461124d7a45e Mon Sep 17 00:00:00 2001 From: Samuel Date: Tue, 18 Dec 2018 14:30:36 +0100 Subject: [PATCH 20/68] Added support for the CRUD operations on the Collection and Community REST endpoints - remove owningCommunity field from CommunityRest and CollectionRest --- .../rest/converter/CollectionConverter.java | 5 -- .../rest/converter/CommunityConverter.java | 8 +-- .../dspace/app/rest/model/CollectionRest.java | 10 ---- .../dspace/app/rest/model/CommunityRest.java | 10 ---- .../app/rest/CollectionRestRepositoryIT.java | 52 ++++++------------- .../app/rest/CommunityRestRepositoryIT.java | 3 -- .../rest/WorkspaceItemRestRepositoryIT.java | 4 +- .../app/rest/matcher/CollectionMatcher.java | 8 ++- .../app/rest/matcher/CommunityMatcher.java | 6 +-- 9 files changed, 25 insertions(+), 81 deletions(-) diff --git a/dspace-spring-rest/src/main/java/org/dspace/app/rest/converter/CollectionConverter.java b/dspace-spring-rest/src/main/java/org/dspace/app/rest/converter/CollectionConverter.java index d3d641510f..a4c368d2b4 100644 --- a/dspace-spring-rest/src/main/java/org/dspace/app/rest/converter/CollectionConverter.java +++ b/dspace-spring-rest/src/main/java/org/dspace/app/rest/converter/CollectionConverter.java @@ -66,11 +66,6 @@ public class CollectionConverter col.setDefaultAccessConditions(getDefaultBitstreamPoliciesForCollection(obj.getID())); - try { - col.setOwningCommunity(obj.getCommunities().get(0).getID().toString()); - } catch (SQLException e) { - log.error(e.getMessage(), e); - } return col; } diff --git a/dspace-spring-rest/src/main/java/org/dspace/app/rest/converter/CommunityConverter.java b/dspace-spring-rest/src/main/java/org/dspace/app/rest/converter/CommunityConverter.java index edb4528482..b1883ccff0 100644 --- a/dspace-spring-rest/src/main/java/org/dspace/app/rest/converter/CommunityConverter.java +++ b/dspace-spring-rest/src/main/java/org/dspace/app/rest/converter/CommunityConverter.java @@ -64,13 +64,7 @@ public class CommunityConverter } } com.setSubCommunities(communityRest); - List parentCommunities = obj.getParentCommunities(); - if (parentCommunities.size() > 0) { - Community parentCommunity = parentCommunities.get(0); - if (parentCommunity != null) { - com.setOwningCommunity(parentCommunity.getID().toString()); - } - } + return com; } diff --git a/dspace-spring-rest/src/main/java/org/dspace/app/rest/model/CollectionRest.java b/dspace-spring-rest/src/main/java/org/dspace/app/rest/model/CollectionRest.java index cc55ef3f55..77bb992004 100644 --- a/dspace-spring-rest/src/main/java/org/dspace/app/rest/model/CollectionRest.java +++ b/dspace-spring-rest/src/main/java/org/dspace/app/rest/model/CollectionRest.java @@ -29,8 +29,6 @@ public class CollectionRest extends DSpaceObjectRest { @JsonIgnore private BitstreamRest logo; - private String owningCommunity; - @JsonIgnore private List defaultAccessConditions; @@ -63,12 +61,4 @@ public class CollectionRest extends DSpaceObjectRest { this.defaultAccessConditions = defaultAccessConditions; } - public String getOwningCommunity() { - return owningCommunity; - } - - public void setOwningCommunity(String owningCommunity) { - this.owningCommunity = owningCommunity; - } - } diff --git a/dspace-spring-rest/src/main/java/org/dspace/app/rest/model/CommunityRest.java b/dspace-spring-rest/src/main/java/org/dspace/app/rest/model/CommunityRest.java index 53b9f1333c..19d3decaee 100644 --- a/dspace-spring-rest/src/main/java/org/dspace/app/rest/model/CommunityRest.java +++ b/dspace-spring-rest/src/main/java/org/dspace/app/rest/model/CommunityRest.java @@ -24,16 +24,6 @@ public class CommunityRest extends DSpaceObjectRest { @JsonIgnore private BitstreamRest logo; - private String owningCommunity; - - public String getOwningCommunity() { - return owningCommunity; - } - - public void setOwningCommunity(String owningCommunity) { - this.owningCommunity = owningCommunity; - } - private List collections; @LinkRest(linkClass = CollectionRest.class) diff --git a/dspace-spring-rest/src/test/java/org/dspace/app/rest/CollectionRestRepositoryIT.java b/dspace-spring-rest/src/test/java/org/dspace/app/rest/CollectionRestRepositoryIT.java index 744f8c43f0..39f79d4601 100644 --- a/dspace-spring-rest/src/test/java/org/dspace/app/rest/CollectionRestRepositoryIT.java +++ b/dspace-spring-rest/src/test/java/org/dspace/app/rest/CollectionRestRepositoryIT.java @@ -63,10 +63,8 @@ public class CollectionRestRepositoryIT extends AbstractControllerIntegrationTes .andExpect(status().isOk()) .andExpect(content().contentType(contentType)) .andExpect(jsonPath("$._embedded.collections", Matchers.containsInAnyOrder( - CollectionMatcher.matchCollectionEntry(col1.getName(), col1.getID(), col1.getHandle(), - col1.getCommunities().get(0).getID()), - CollectionMatcher.matchCollectionEntry(col2.getName(), col2.getID(), col2.getHandle(), - col2.getCommunities().get(0).getID()) + CollectionMatcher.matchCollectionEntry(col1.getName(), col1.getID(), col1.getHandle()), + CollectionMatcher.matchCollectionEntry(col2.getName(), col2.getID(), col2.getHandle()) ))); } @@ -95,13 +93,11 @@ public class CollectionRestRepositoryIT extends AbstractControllerIntegrationTes .andExpect(status().isOk()) .andExpect(content().contentType(contentType)) .andExpect(jsonPath("$._embedded.collections", Matchers.contains( - CollectionMatcher.matchCollectionEntry(col1.getName(), col1.getID(), col1.getHandle(), - col1.getCommunities().get(0).getID()) + CollectionMatcher.matchCollectionEntry(col1.getName(), col1.getID(), col1.getHandle()) ))) .andExpect(jsonPath("$._embedded.collections", Matchers.not( Matchers.contains( - CollectionMatcher.matchCollectionEntry(col2.getName(), col2.getID(), col2.getHandle(), - col1.getCommunities().get(0).getID()) + CollectionMatcher.matchCollectionEntry(col2.getName(), col2.getID(), col2.getHandle()) ) ))); @@ -111,13 +107,11 @@ public class CollectionRestRepositoryIT extends AbstractControllerIntegrationTes .andExpect(status().isOk()) .andExpect(content().contentType(contentType)) .andExpect(jsonPath("$._embedded.collections", Matchers.contains( - CollectionMatcher.matchCollectionEntry(col2.getName(), col2.getID(), col2.getHandle(), - col2.getCommunities().get(0).getID()) + CollectionMatcher.matchCollectionEntry(col2.getName(), col2.getID(), col2.getHandle()) ))) .andExpect(jsonPath("$._embedded.collections", Matchers.not( Matchers.contains( - CollectionMatcher.matchCollectionEntry(col1.getName(), col1.getID(), col1.getHandle(), - col1.getCommunities().get(0).getID()) + CollectionMatcher.matchCollectionEntry(col1.getName(), col1.getID(), col1.getHandle()) ) ))); } @@ -147,13 +141,11 @@ public class CollectionRestRepositoryIT extends AbstractControllerIntegrationTes .andExpect(status().isOk()) .andExpect(content().contentType(contentType)) .andExpect(jsonPath("$", is( - CollectionMatcher.matchCollectionEntry(col1.getName(), col1.getID(), col1.getHandle(), - col1.getCommunities().get(0).getID()) + CollectionMatcher.matchCollectionEntry(col1.getName(), col1.getID(), col1.getHandle()) ))) .andExpect(jsonPath("$", Matchers.not( is( - CollectionMatcher.matchCollectionEntry(col2.getName(), col2.getID(), col2.getHandle(), - col1.getCommunities().get(0).getID()) + CollectionMatcher.matchCollectionEntry(col2.getName(), col2.getID(), col2.getHandle()) )))); } @@ -181,13 +173,11 @@ public class CollectionRestRepositoryIT extends AbstractControllerIntegrationTes .andExpect(status().isOk()) .andExpect(content().contentType(contentType)) .andExpect(jsonPath("$", is( - CollectionMatcher.matchCollectionEntry(col1.getName(), col1.getID(), col1.getHandle(), - col1.getCommunities().get(0).getID()) + CollectionMatcher.matchCollectionEntry(col1.getName(), col1.getID(), col1.getHandle()) ))) .andExpect(jsonPath("$", Matchers.not( is( - CollectionMatcher.matchCollectionEntry(col2.getName(), col2.getID(), col2.getHandle(), - col1.getCommunities().get(0).getID()) + CollectionMatcher.matchCollectionEntry(col2.getName(), col2.getID(), col2.getHandle()) ))) ) ; @@ -321,13 +311,11 @@ public class CollectionRestRepositoryIT extends AbstractControllerIntegrationTes .andExpect(status().isOk()) .andExpect(content().contentType(contentType)) .andExpect(jsonPath("$", is( - CollectionMatcher.matchCollectionEntry(col1.getName(), col1.getID(), col1.getHandle(), - col1.getCommunities().get(0).getID()) + CollectionMatcher.matchCollectionEntry(col1.getName(), col1.getID(), col1.getHandle()) ))) .andExpect(jsonPath("$", Matchers.not( is( - CollectionMatcher.matchCollectionEntry(col2.getName(), col2.getID(), col2.getHandle(), - col1.getCommunities().get(0).getID()) + CollectionMatcher.matchCollectionEntry(col2.getName(), col2.getID(), col2.getHandle()) )))); } @@ -350,8 +338,7 @@ public class CollectionRestRepositoryIT extends AbstractControllerIntegrationTes .andExpect(status().isOk()) .andExpect(content().contentType(contentType)) .andExpect(jsonPath("$", Matchers.is( - CollectionMatcher.matchCollectionEntry(col1.getName(), col1.getID(), col1.getHandle(), - col1.getCommunities().get(0).getID()) + CollectionMatcher.matchCollectionEntry(col1.getName(), col1.getID(), col1.getHandle()) ))) .andExpect(jsonPath("$._links.self.href", Matchers.containsString("/api/core/collections"))) ; @@ -365,8 +352,7 @@ public class CollectionRestRepositoryIT extends AbstractControllerIntegrationTes "dissertations (ETD)\",\"handle\": \"" + col1.getHandle() + "\",\"metadata\": " + "[{\"key\": \"dc.description.abstract\",\"value\": \"\",\"language\": null}," + "{\"key\": \"dc.title\",\"value\": \"Electronic theses and dissertations " + - "(ETD)\",\"language\": null}], \"owningCommunity\": \"" + - child1.getID() + "\",\"type\": \"collection\"}" + "(ETD)\",\"language\": null}],\"type\": \"collection\"}" )) .andExpect(status().isOk()) ; @@ -376,8 +362,7 @@ public class CollectionRestRepositoryIT extends AbstractControllerIntegrationTes .andExpect(content().contentType(contentType)) .andExpect(jsonPath("$", Matchers.is( CollectionMatcher.matchCollectionEntry("Electronic theses and dissertations (ETD)", - col1.getID(), col1.getHandle(), - col1.getCommunities().get(0).getID()) + col1.getID(), col1.getHandle()) ))) .andExpect(jsonPath("$._links.self.href", Matchers.containsString("/api/core/collections"))) @@ -415,8 +400,7 @@ public class CollectionRestRepositoryIT extends AbstractControllerIntegrationTes .andExpect(status().isOk()) .andExpect(content().contentType(contentType)) .andExpect(jsonPath("$", Matchers.is( - CollectionMatcher.matchCollectionEntry(col1.getName(), col1.getID(), col1.getHandle(), - col1.getCommunities().get(0).getID()) + CollectionMatcher.matchCollectionEntry(col1.getName(), col1.getID(), col1.getHandle()) ))) .andExpect(jsonPath("$._links.self.href", Matchers.containsString("/api/core/collections"))) ; @@ -457,8 +441,7 @@ public class CollectionRestRepositoryIT extends AbstractControllerIntegrationTes .andExpect(status().isOk()) .andExpect(content().contentType(contentType)) .andExpect(jsonPath("$", Matchers.is( - CollectionMatcher.matchCollectionEntry(col1.getName(), col1.getID(), col1.getHandle(), - col1.getCommunities().get(0).getID()) + CollectionMatcher.matchCollectionEntry(col1.getName(), col1.getID(), col1.getHandle()) ))) .andExpect(jsonPath("$._links.self.href", Matchers.containsString("/api/core/collections"))) ; @@ -522,7 +505,6 @@ public class CollectionRestRepositoryIT extends AbstractControllerIntegrationTes hasJsonPath("$.uuid", not(empty())), hasJsonPath("$.name", is("Title Text")), hasJsonPath("$.handle", not(empty())), - hasJsonPath("$.owningCommunity", is(parentCommunity.getID().toString())), hasJsonPath("$.type", is("collection")), hasJsonPath("$.metadata", Matchers.containsInAnyOrder( CommunityMetadataMatcher.matchMetadata("dc.description", diff --git a/dspace-spring-rest/src/test/java/org/dspace/app/rest/CommunityRestRepositoryIT.java b/dspace-spring-rest/src/test/java/org/dspace/app/rest/CommunityRestRepositoryIT.java index 079a748213..7a5bf09e72 100644 --- a/dspace-spring-rest/src/test/java/org/dspace/app/rest/CommunityRestRepositoryIT.java +++ b/dspace-spring-rest/src/test/java/org/dspace/app/rest/CommunityRestRepositoryIT.java @@ -10,7 +10,6 @@ package org.dspace.app.rest; import static com.jayway.jsonpath.matchers.JsonPathMatchers.hasJsonPath; import static org.hamcrest.Matchers.empty; import static org.hamcrest.Matchers.is; -import static org.hamcrest.Matchers.isEmptyOrNullString; import static org.hamcrest.Matchers.not; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.delete; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; @@ -86,7 +85,6 @@ public class CommunityRestRepositoryIT extends AbstractControllerIntegrationTest hasJsonPath("$.uuid", not(empty())), hasJsonPath("$.name", is("Title Text")), hasJsonPath("$.handle", not(empty())), - hasJsonPath("$.owningCommunity", isEmptyOrNullString()), hasJsonPath("$.type", is("community")), hasJsonPath("$._links.collections.href", not(empty())), hasJsonPath("$._links.logo.href", not(empty())), @@ -160,7 +158,6 @@ public class CommunityRestRepositoryIT extends AbstractControllerIntegrationTest hasJsonPath("$.uuid", not(empty())), hasJsonPath("$.name", is("Title Text")), hasJsonPath("$.handle", not(empty())), - hasJsonPath("$.owningCommunity", is(parentCommunity.getID().toString())), hasJsonPath("$.type", is("community")), hasJsonPath("$._links.collections.href", not(empty())), hasJsonPath("$._links.logo.href", not(empty())), diff --git a/dspace-spring-rest/src/test/java/org/dspace/app/rest/WorkspaceItemRestRepositoryIT.java b/dspace-spring-rest/src/test/java/org/dspace/app/rest/WorkspaceItemRestRepositoryIT.java index f9643bf1e5..eac8721031 100644 --- a/dspace-spring-rest/src/test/java/org/dspace/app/rest/WorkspaceItemRestRepositoryIT.java +++ b/dspace-spring-rest/src/test/java/org/dspace/app/rest/WorkspaceItemRestRepositoryIT.java @@ -239,8 +239,8 @@ public class WorkspaceItemRestRepositoryIT extends AbstractControllerIntegration getClient().perform(get("/api/submission/workspaceitems/" + witem.getID() + "/collection")) .andExpect(status().isOk()) .andExpect(jsonPath("$", Matchers - .is(CollectionMatcher.matchCollectionEntry(col1.getName(), col1.getID(), col1.getHandle(), - col1.getCommunities().get(0).getID())))); + .is(CollectionMatcher.matchCollectionEntry(col1.getName(), col1.getID(), col1.getHandle())) + )); getClient().perform(get("/api/submission/workspaceitems/" + witem.getID() + "/item")).andExpect(status().isOk()) .andExpect(jsonPath("$", Matchers.is(ItemMatcher.matchItemWithTitleAndDateIssued(witem.getItem(), diff --git a/dspace-spring-rest/src/test/java/org/dspace/app/rest/matcher/CollectionMatcher.java b/dspace-spring-rest/src/test/java/org/dspace/app/rest/matcher/CollectionMatcher.java index 1232deb381..5381c62d93 100644 --- a/dspace-spring-rest/src/test/java/org/dspace/app/rest/matcher/CollectionMatcher.java +++ b/dspace-spring-rest/src/test/java/org/dspace/app/rest/matcher/CollectionMatcher.java @@ -22,18 +22,16 @@ public class CollectionMatcher { private CollectionMatcher() { } - public static Matcher matchCollectionEntry(String name, UUID uuid, String handle, UUID parentUuid) { - return matchCollectionEntry(name, uuid, handle, null, parentUuid); + public static Matcher matchCollectionEntry(String name, UUID uuid, String handle) { + return matchCollectionEntry(name, uuid, handle, null); } - public static Matcher matchCollectionEntry(String name, UUID uuid, String handle, - Bitstream logo, UUID parentUuid) { + public static Matcher matchCollectionEntry(String name, UUID uuid, String handle, Bitstream logo) { return allOf( hasJsonPath("$.uuid", is(uuid.toString())), hasJsonPath("$.name", is(name)), hasJsonPath("$.handle", is(handle)), hasJsonPath("$.type", is("collection")), - hasJsonPath("$.owningCommunity", is(parentUuid.toString())), hasJsonPath("$.metadata", Matchers.hasItem( CollectionMetadataMatcher.matchTitle(name) )), diff --git a/dspace-spring-rest/src/test/java/org/dspace/app/rest/matcher/CommunityMatcher.java b/dspace-spring-rest/src/test/java/org/dspace/app/rest/matcher/CommunityMatcher.java index dad9330ae2..600f67ab52 100644 --- a/dspace-spring-rest/src/test/java/org/dspace/app/rest/matcher/CommunityMatcher.java +++ b/dspace-spring-rest/src/test/java/org/dspace/app/rest/matcher/CommunityMatcher.java @@ -11,7 +11,6 @@ import static com.jayway.jsonpath.matchers.JsonPathMatchers.hasJsonPath; import static org.hamcrest.Matchers.allOf; import static org.hamcrest.Matchers.is; -import java.sql.SQLException; import java.util.UUID; import org.dspace.content.Collection; @@ -54,13 +53,12 @@ public class CommunityMatcher { } public static Matcher matchCommunityWithCollectionEntry(String name, UUID uuid, String handle, - Collection col) throws SQLException { + Collection col) { return allOf( matchProperties(name, uuid, handle), hasJsonPath("$._embedded.collections._embedded.collections[0]", CollectionMatcher - .matchCollectionEntry(col.getName(), col.getID(), col.getHandle(), - col.getLogo(), col.getCommunities().get(0).getID())), + .matchCollectionEntry(col.getName(), col.getID(), col.getHandle(), col.getLogo())), hasJsonPath("$._embedded.logo", Matchers.not(Matchers.empty())), matchLinks(uuid) ); From b96cf91ef8d66825cad7c5d346c25e9b4c47f4af Mon Sep 17 00:00:00 2001 From: Raf Ponsaerts Date: Fri, 11 Jan 2019 09:38:21 +0100 Subject: [PATCH 21/68] Applied community feedback and fixed tests --- .../app/rest/RestResourceController.java | 30 +++++++++++++++- .../rest/converter/CollectionConverter.java | 10 ++++-- .../repository/CollectionRestRepository.java | 26 ++++++++------ .../repository/CommunityRestRepository.java | 29 +++++++++------- .../rest/repository/DSpaceRestRepository.java | 10 ++++++ .../utils/CollectionRestEqualityUtils.java | 33 ++++++++++++++++++ .../utils/CommunityRestEqualityUtils.java | 33 ++++++++++++++++++ .../utils/DSpaceObjectRestEqualityUtils.java | 34 +++++++++++++++++++ .../app/rest/utils/DSpaceObjectUtils.java | 19 +++++++++++ .../app/rest/CollectionRestRepositoryIT.java | 30 ++++++++++------ .../app/rest/CommunityRestRepositoryIT.java | 30 ++++++++++------ 11 files changed, 238 insertions(+), 46 deletions(-) create mode 100644 dspace-spring-rest/src/main/java/org/dspace/app/rest/utils/CollectionRestEqualityUtils.java create mode 100644 dspace-spring-rest/src/main/java/org/dspace/app/rest/utils/CommunityRestEqualityUtils.java create mode 100644 dspace-spring-rest/src/main/java/org/dspace/app/rest/utils/DSpaceObjectRestEqualityUtils.java diff --git a/dspace-spring-rest/src/main/java/org/dspace/app/rest/RestResourceController.java b/dspace-spring-rest/src/main/java/org/dspace/app/rest/RestResourceController.java index 74775a9314..0fcc8953ce 100644 --- a/dspace-spring-rest/src/main/java/org/dspace/app/rest/RestResourceController.java +++ b/dspace-spring-rest/src/main/java/org/dspace/app/rest/RestResourceController.java @@ -983,7 +983,25 @@ public class RestResourceController implements InitializingBean { } - + /** + * Execute a PUT request for an entity with id of type UUID; + * + * curl -X PUT http:///dspace-spring-rest/api/{apiCategory}/{model} + * + * Example: + *
+     * {@code
+     *      curl -X PUT http:///dspace-spring-rest/api/collection
+     * }
+     * 
+ * + * @param request the http request + * @param apiCategory the API category e.g. "api" + * @param model the DSpace model e.g. "metadatafield" + * @param uuid the ID of the target REST object + * @param jsonNode the part of the request body representing the updated rest object + * @return the relevant REST resource + */ @RequestMapping(method = RequestMethod.PUT, value = REGEX_REQUESTMAPPING_IDENTIFIER_AS_UUID) public DSpaceResource put(HttpServletRequest request, @PathVariable String apiCategory, @PathVariable String model, @@ -992,6 +1010,16 @@ public class RestResourceController implements InitializingBean { return putOneInternal(request, apiCategory, model, uuid, jsonNode); } + /** + * Internal method to update a single entity + * + * @param request the http request + * @param apiCategory the API category e.g. "api" + * @param model the DSpace model e.g. "metadatafield" + * @param uuid the ID of the target REST object + * @param jsonNode the part of the request body representing the updated rest object + * @return the relevant REST resource + */ private DSpaceResource putOneInternal(HttpServletRequest request, String apiCategory, String model, ID uuid, diff --git a/dspace-spring-rest/src/main/java/org/dspace/app/rest/converter/CollectionConverter.java b/dspace-spring-rest/src/main/java/org/dspace/app/rest/converter/CollectionConverter.java index a4c368d2b4..c1a13686db 100644 --- a/dspace-spring-rest/src/main/java/org/dspace/app/rest/converter/CollectionConverter.java +++ b/dspace-spring-rest/src/main/java/org/dspace/app/rest/converter/CollectionConverter.java @@ -25,6 +25,7 @@ import org.dspace.content.service.CollectionService; import org.dspace.core.Constants; import org.dspace.core.Context; import org.dspace.services.RequestService; +import org.dspace.services.model.Request; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; @@ -71,12 +72,17 @@ public class CollectionConverter private List getDefaultBitstreamPoliciesForCollection(UUID uuid) { - HttpServletRequest request = requestService.getCurrentRequest().getHttpServletRequest(); Context context = null; + Request currentRequest = requestService.getCurrentRequest(); + if (currentRequest != null) { + HttpServletRequest request = currentRequest.getHttpServletRequest(); + context = ContextUtil.obtainContext(request); + } else { + context = new Context(); + } Collection collection = null; List defaultCollectionPolicies = null; try { - context = ContextUtil.obtainContext(request); collection = collectionService.find(context, uuid); defaultCollectionPolicies = authorizeService.getPoliciesActionFilter(context, collection, Constants.DEFAULT_BITSTREAM_READ); diff --git a/dspace-spring-rest/src/main/java/org/dspace/app/rest/repository/CollectionRestRepository.java b/dspace-spring-rest/src/main/java/org/dspace/app/rest/repository/CollectionRestRepository.java index c3ca8a82cc..ab6f56ed61 100644 --- a/dspace-spring-rest/src/main/java/org/dspace/app/rest/repository/CollectionRestRepository.java +++ b/dspace-spring-rest/src/main/java/org/dspace/app/rest/repository/CollectionRestRepository.java @@ -30,6 +30,7 @@ import org.dspace.app.rest.model.CollectionRest; import org.dspace.app.rest.model.CommunityRest; import org.dspace.app.rest.model.MetadataEntryRest; import org.dspace.app.rest.model.hateoas.CollectionResource; +import org.dspace.app.rest.utils.CollectionRestEqualityUtils; import org.dspace.app.rest.utils.DSpaceObjectUtils; import org.dspace.authorize.AuthorizeException; import org.dspace.content.Collection; @@ -70,6 +71,9 @@ public class CollectionRestRepository extends DSpaceRestRepository metadataEntryRestList = collectionRest.getMetadata(); collection = (Collection) dspaceObjectUtils.replaceMetadataValues(context, collection, diff --git a/dspace-spring-rest/src/main/java/org/dspace/app/rest/repository/CommunityRestRepository.java b/dspace-spring-rest/src/main/java/org/dspace/app/rest/repository/CommunityRestRepository.java index 25306d81f0..3c180caf08 100644 --- a/dspace-spring-rest/src/main/java/org/dspace/app/rest/repository/CommunityRestRepository.java +++ b/dspace-spring-rest/src/main/java/org/dspace/app/rest/repository/CommunityRestRepository.java @@ -29,6 +29,7 @@ import org.dspace.app.rest.exception.UnprocessableEntityException; import org.dspace.app.rest.model.CommunityRest; import org.dspace.app.rest.model.MetadataEntryRest; import org.dspace.app.rest.model.hateoas.CommunityResource; +import org.dspace.app.rest.utils.CommunityRestEqualityUtils; import org.dspace.app.rest.utils.DSpaceObjectUtils; import org.dspace.authorize.AuthorizeException; import org.dspace.content.Community; @@ -61,6 +62,9 @@ public class CommunityRestRepository extends DSpaceRestRepository metadataEntryRestList = communityRest.getMetadata(); community = (Community) dspaceObjectUtils.replaceMetadataValues(context, community, metadataEntryRestList); } else { - throw new IllegalArgumentException("The UUID in the Json and the UUID in the url do not match: " - + id + ", " - + communityRest.getId()); + throw new UnprocessableEntityException("The given JSON and the original Community differ more " + + "than just the metadata"); } return converter.fromModel(community); } diff --git a/dspace-spring-rest/src/main/java/org/dspace/app/rest/repository/DSpaceRestRepository.java b/dspace-spring-rest/src/main/java/org/dspace/app/rest/repository/DSpaceRestRepository.java index 4be7596ad4..5b7e3915c9 100644 --- a/dspace-spring-rest/src/main/java/org/dspace/app/rest/repository/DSpaceRestRepository.java +++ b/dspace-spring-rest/src/main/java/org/dspace/app/rest/repository/DSpaceRestRepository.java @@ -418,6 +418,16 @@ public abstract class DSpaceRestRepository metadataEntryRestList) diff --git a/dspace-spring-rest/src/test/java/org/dspace/app/rest/CollectionRestRepositoryIT.java b/dspace-spring-rest/src/test/java/org/dspace/app/rest/CollectionRestRepositoryIT.java index 39f79d4601..13139740db 100644 --- a/dspace-spring-rest/src/test/java/org/dspace/app/rest/CollectionRestRepositoryIT.java +++ b/dspace-spring-rest/src/test/java/org/dspace/app/rest/CollectionRestRepositoryIT.java @@ -25,6 +25,7 @@ import java.util.UUID; import com.fasterxml.jackson.databind.ObjectMapper; import org.dspace.app.rest.builder.CollectionBuilder; import org.dspace.app.rest.builder.CommunityBuilder; +import org.dspace.app.rest.converter.CollectionConverter; import org.dspace.app.rest.matcher.CollectionMatcher; import org.dspace.app.rest.matcher.CommunityMetadataMatcher; import org.dspace.app.rest.model.CollectionRest; @@ -34,10 +35,13 @@ import org.dspace.content.Collection; import org.dspace.content.Community; import org.hamcrest.Matchers; import org.junit.Test; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.MediaType; public class CollectionRestRepositoryIT extends AbstractControllerIntegrationTest { + @Autowired + CollectionConverter collectionConverter; @Test public void findAllTest() throws Exception { @@ -289,7 +293,7 @@ public class CollectionRestRepositoryIT extends AbstractControllerIntegrationTes } @Test - public void findCollectionWithOwningCommunity() throws Exception { + public void findCollectionWithParentCommunity() throws Exception { //We turn off the authorization system in order to create the structure as defined below context.turnOffAuthorisationSystem(); @@ -345,15 +349,19 @@ public class CollectionRestRepositoryIT extends AbstractControllerIntegrationTes String token = getAuthToken(admin.getEmail(), password); + ObjectMapper mapper = new ObjectMapper(); + + CollectionRest collectionRest = collectionConverter.fromModel(col1); + + MetadataEntryRest metadataEntryRest = new MetadataEntryRest(); + metadataEntryRest.setKey("dc.title"); + metadataEntryRest.setValue("Electronic theses and dissertations"); + + collectionRest.setMetadata(Arrays.asList(metadataEntryRest)); + getClient(token).perform(put("/api/core/collections/" + col1.getID().toString()) - .contentType(MediaType.APPLICATION_JSON).content( - "{\"id\": \"" + col1.getID() + "\",\"uuid\": " + - "\"" + col1.getID() + "\",\"name\": \"Electronic theses and " + - "dissertations (ETD)\",\"handle\": \"" + col1.getHandle() + "\",\"metadata\": " + - "[{\"key\": \"dc.description.abstract\",\"value\": \"\",\"language\": null}," + - "{\"key\": \"dc.title\",\"value\": \"Electronic theses and dissertations " + - "(ETD)\",\"language\": null}],\"type\": \"collection\"}" - )) + .contentType(MediaType.APPLICATION_JSON) + .content(mapper.writeValueAsBytes(collectionRest))) .andExpect(status().isOk()) ; @@ -361,7 +369,7 @@ public class CollectionRestRepositoryIT extends AbstractControllerIntegrationTes .andExpect(status().isOk()) .andExpect(content().contentType(contentType)) .andExpect(jsonPath("$", Matchers.is( - CollectionMatcher.matchCollectionEntry("Electronic theses and dissertations (ETD)", + CollectionMatcher.matchCollectionEntry("Electronic theses and dissertations", col1.getID(), col1.getHandle()) ))) .andExpect(jsonPath("$._links.self.href", @@ -496,7 +504,7 @@ public class CollectionRestRepositoryIT extends AbstractControllerIntegrationTes String authToken = getAuthToken(admin.getEmail(), password); getClient(authToken).perform(post("/api/core/collections") .content(mapper.writeValueAsBytes(collectionRest)) - .param("parentCommunity", parentCommunity.getID().toString()) + .param("parent", parentCommunity.getID().toString()) .contentType(contentType)) .andExpect(status().isCreated()) .andExpect(content().contentType(contentType)) diff --git a/dspace-spring-rest/src/test/java/org/dspace/app/rest/CommunityRestRepositoryIT.java b/dspace-spring-rest/src/test/java/org/dspace/app/rest/CommunityRestRepositoryIT.java index 7a5bf09e72..051a4ae0a8 100644 --- a/dspace-spring-rest/src/test/java/org/dspace/app/rest/CommunityRestRepositoryIT.java +++ b/dspace-spring-rest/src/test/java/org/dspace/app/rest/CommunityRestRepositoryIT.java @@ -26,6 +26,7 @@ import com.fasterxml.jackson.databind.ObjectMapper; import org.dspace.app.rest.builder.CollectionBuilder; import org.dspace.app.rest.builder.CommunityBuilder; +import org.dspace.app.rest.converter.CommunityConverter; import org.dspace.app.rest.matcher.CommunityMatcher; import org.dspace.app.rest.matcher.CommunityMetadataMatcher; import org.dspace.app.rest.model.CommunityRest; @@ -35,10 +36,14 @@ import org.dspace.content.Collection; import org.dspace.content.Community; import org.hamcrest.Matchers; import org.junit.Test; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.MediaType; public class CommunityRestRepositoryIT extends AbstractControllerIntegrationTest { + @Autowired + CommunityConverter communityConverter; + @Test public void createTest() throws Exception { context.turnOffAuthorisationSystem(); @@ -149,7 +154,7 @@ public class CommunityRestRepositoryIT extends AbstractControllerIntegrationTest String authToken = getAuthToken(admin.getEmail(), password); getClient(authToken).perform(post("/api/core/communities") .content(mapper.writeValueAsBytes(comm)) - .param("parentCommunity", parentCommunity.getID().toString()) + .param("parent", parentCommunity.getID().toString()) .contentType(contentType)) .andExpect(status().isCreated()) .andExpect(content().contentType(contentType)) @@ -624,15 +629,20 @@ public class CommunityRestRepositoryIT extends AbstractControllerIntegrationTest String token = getAuthToken(admin.getEmail(), password); + ObjectMapper mapper = new ObjectMapper(); + + CommunityRest communityRest = communityConverter.fromModel(parentCommunity); + + MetadataEntryRest metadataEntryRest = new MetadataEntryRest(); + metadataEntryRest.setKey("dc.title"); + metadataEntryRest.setValue("Electronic theses and dissertations"); + + communityRest.setMetadata(Arrays.asList(metadataEntryRest)); + + getClient(token).perform(put("/api/core/communities/" + parentCommunity.getID().toString()) - .contentType(MediaType.APPLICATION_JSON).content( - "{\"id\": \"" + parentCommunity.getID() + "\",\"uuid\": " + - "\"" + parentCommunity.getID() + "\",\"name\": \"Electronic theses and " + - "dissertations (ETD)\",\"handle\": \"123456789/5286\",\"metadata\": " + - "[{\"key\": \"dc.description.abstract\",\"value\": \"\",\"language\": null}," + - "{\"key\": \"dc.title\",\"value\": \"Electronic theses and dissertations " + - "(ETD)\",\"language\": null}],\"type\": \"community\"}" - )) + .contentType(MediaType.APPLICATION_JSON) + .content(mapper.writeValueAsBytes(communityRest))) .andExpect(status().isOk()) ; @@ -640,7 +650,7 @@ public class CommunityRestRepositoryIT extends AbstractControllerIntegrationTest .andExpect(status().isOk()) .andExpect(content().contentType(contentType)) .andExpect(jsonPath("$", Matchers.is( - CommunityMatcher.matchCommunityEntry("Electronic theses and dissertations (ETD)", + CommunityMatcher.matchCommunityEntry("Electronic theses and dissertations", parentCommunity.getID(), parentCommunity.getHandle()) ))) From 3d011c5411242e88ef871a7639344c5e37abe641 Mon Sep 17 00:00:00 2001 From: Raf Ponsaerts Date: Fri, 11 Jan 2019 15:01:59 +0100 Subject: [PATCH 22/68] Applied fixes to catch clauses and added more docs --- .../repository/CollectionRestRepository.java | 7 +++---- .../rest/repository/CommunityRestRepository.java | 4 +++- .../rest/repository/DSpaceRestRepository.java | 16 +++++++++++++++- 3 files changed, 21 insertions(+), 6 deletions(-) diff --git a/dspace-spring-rest/src/main/java/org/dspace/app/rest/repository/CollectionRestRepository.java b/dspace-spring-rest/src/main/java/org/dspace/app/rest/repository/CollectionRestRepository.java index ab6f56ed61..31f496b079 100644 --- a/dspace-spring-rest/src/main/java/org/dspace/app/rest/repository/CollectionRestRepository.java +++ b/dspace-spring-rest/src/main/java/org/dspace/app/rest/repository/CollectionRestRepository.java @@ -20,7 +20,6 @@ import javax.ws.rs.BadRequestException; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; import org.apache.commons.lang3.StringUtils; -import org.apache.log4j.Logger; import org.dspace.app.rest.Parameter; import org.dspace.app.rest.SearchRestMethod; import org.dspace.app.rest.converter.CollectionConverter; @@ -57,8 +56,6 @@ import org.springframework.stereotype.Component; @Component(CollectionRest.CATEGORY + "." + CollectionRest.NAME) public class CollectionRestRepository extends DSpaceRestRepository { - private static final Logger log = Logger.getLogger(CollectionRestRepository.class); - @Autowired CommunityService communityService; @@ -256,8 +253,10 @@ public class CollectionRestRepository extends DSpaceRestRepository Date: Wed, 16 Jan 2019 09:39:14 +0100 Subject: [PATCH 23/68] Fixed RunTimeException messages and addressed nullpointer error --- .../rest/repository/CollectionRestRepository.java | 13 ++++++++----- .../rest/repository/CommunityRestRepository.java | 4 ++-- .../app/rest/repository/DSpaceRestRepository.java | 2 +- 3 files changed, 11 insertions(+), 8 deletions(-) diff --git a/dspace-spring-rest/src/main/java/org/dspace/app/rest/repository/CollectionRestRepository.java b/dspace-spring-rest/src/main/java/org/dspace/app/rest/repository/CollectionRestRepository.java index 31f496b079..54957d3068 100644 --- a/dspace-spring-rest/src/main/java/org/dspace/app/rest/repository/CollectionRestRepository.java +++ b/dspace-spring-rest/src/main/java/org/dspace/app/rest/repository/CollectionRestRepository.java @@ -176,9 +176,9 @@ public class CollectionRestRepository extends DSpaceRestRepository Date: Fri, 18 Jan 2019 07:30:47 +1300 Subject: [PATCH 24/68] WIP commit for oai master port --- dspace-oai/src/main/java/org/dspace/xoai/app/XOAI.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/dspace-oai/src/main/java/org/dspace/xoai/app/XOAI.java b/dspace-oai/src/main/java/org/dspace/xoai/app/XOAI.java index eae538b50b..21b7f4bae3 100644 --- a/dspace-oai/src/main/java/org/dspace/xoai/app/XOAI.java +++ b/dspace-oai/src/main/java/org/dspace/xoai/app/XOAI.java @@ -57,6 +57,7 @@ import org.dspace.content.service.ItemService; import org.dspace.core.ConfigurationManager; import org.dspace.core.Constants; import org.dspace.core.Context; +import org.dspace.services.factory.DSpaceServicesFactory; import org.dspace.xoai.exceptions.CompilingException; import org.dspace.xoai.services.api.CollectionsService; import org.dspace.xoai.services.api.cache.XOAICacheService; @@ -283,6 +284,7 @@ public class XOAI { throws DSpaceSolrIndexerException { try { int i = 0; + int batchSize = configurationService.getIntProperty("oai.import.batch.size", 1000); SolrServer server = solrServerResolver.getServer(); while (iterator.hasNext()) { try { From 6d40a57df7de15033e29c1a3d9c37858da8bbf90 Mon Sep 17 00:00:00 2001 From: Kim Shepherd Date: Sat, 19 Jan 2019 10:33:00 +1300 Subject: [PATCH 25/68] [DS-4136] Apply same changes as dspace 6.x version (PR #2320); replace xoai services configurationservice with proper DSpace configuration service; and replace all instances of legacy configuration manager with proper DSpace configuration service --- .../main/java/org/dspace/xoai/app/XOAI.java | 32 +++++++++++++------ dspace/config/modules/oai.cfg | 7 ++++ 2 files changed, 29 insertions(+), 10 deletions(-) diff --git a/dspace-oai/src/main/java/org/dspace/xoai/app/XOAI.java b/dspace-oai/src/main/java/org/dspace/xoai/app/XOAI.java index 21b7f4bae3..ed7a66db66 100644 --- a/dspace-oai/src/main/java/org/dspace/xoai/app/XOAI.java +++ b/dspace-oai/src/main/java/org/dspace/xoai/app/XOAI.java @@ -54,16 +54,15 @@ import org.dspace.content.MetadataField; import org.dspace.content.MetadataValue; import org.dspace.content.factory.ContentServiceFactory; import org.dspace.content.service.ItemService; -import org.dspace.core.ConfigurationManager; import org.dspace.core.Constants; import org.dspace.core.Context; +import org.dspace.services.ConfigurationService; //added import org.dspace.services.factory.DSpaceServicesFactory; import org.dspace.xoai.exceptions.CompilingException; import org.dspace.xoai.services.api.CollectionsService; import org.dspace.xoai.services.api.cache.XOAICacheService; import org.dspace.xoai.services.api.cache.XOAIItemCacheService; import org.dspace.xoai.services.api.cache.XOAILastCompilationCacheService; -import org.dspace.xoai.services.api.config.ConfigurationService; import org.dspace.xoai.services.api.solr.SolrServerResolver; import org.dspace.xoai.solr.DSpaceSolrSearch; import org.dspace.xoai.solr.exceptions.DSpaceSolrException; @@ -95,6 +94,8 @@ public class XOAI { private final AuthorizeService authorizeService; private final ItemService itemService; + private final static ConfigurationService configurationService = DSpaceServicesFactory + .getInstance().getConfigurationService(); private List getFileFormats(Item item) { List formats = new ArrayList<>(); @@ -286,13 +287,14 @@ public class XOAI { int i = 0; int batchSize = configurationService.getIntProperty("oai.import.batch.size", 1000); SolrServer server = solrServerResolver.getServer(); + ArrayList list = new ArrayList<>(); while (iterator.hasNext()) { try { Item item = iterator.next(); if (item.getHandle() == null) { log.warn("Skipped item without handle: " + item.getID()); } else { - server.add(this.index(item)); + list.add(this.index(item)); } //Uncache the item to keep memory consumption low context.uncacheEntity(item); @@ -302,12 +304,20 @@ public class XOAI { log.error(ex.getMessage(), ex); } i++; - if (i % 100 == 0) { + if (i % 1000 == 0) { System.out.println(i + " items imported so far..."); } + if (i % batchSize == 0) { + System.out.println(i + " items imported so far..."); + server.add(list); + server.commit(); + list.clear(); + } } System.out.println("Total: " + i + " items"); - server.commit(); + server.add(list); + server.commit(true, true); + list.clear(); return i; } catch (SolrServerException | IOException ex) { throw new DSpaceSolrIndexerException(ex.getMessage(), ex); @@ -336,6 +346,7 @@ public class XOAI { dates.add(policy.getEndDate()); } } + context.uncacheEntity(policy); } dates.add(item.getLastModified()); Collections.sort(dates); @@ -460,6 +471,7 @@ public class XOAI { return true; } } + context.uncacheEntity(policy); } return false; } @@ -479,8 +491,8 @@ public class XOAI { private static boolean getKnownExplanation(Throwable t) { if (t instanceof ConnectException) { System.err.println("Solr server (" - + ConfigurationManager.getProperty("oai", "solr.url") - + ") is down, turn it on."); + + configurationService.getProperty("oai.solr.url", "") + + ") is down, turn it on."); return true; } @@ -527,7 +539,7 @@ public class XOAI { BasicConfiguration.class }); - ConfigurationService configurationService = applicationContext.getBean(ConfigurationService.class); + //ConfigurationService configurationService = applicationContext.getBean(ConfigurationService.class); XOAICacheService cacheService = applicationContext.getBean(XOAICacheService.class); XOAIItemCacheService itemCacheService = applicationContext.getBean(XOAIItemCacheService.class); @@ -549,7 +561,7 @@ public class XOAI { boolean solr = true; // Assuming solr by default - solr = !("database").equals(configurationService.getProperty("oai", "storage")); + solr = !("database").equals(configurationService.getProperty("oai.storage", "solr")); boolean run = false; @@ -654,7 +666,7 @@ public class XOAI { private static void usage() { boolean solr = true; // Assuming solr by default - solr = !("database").equals(ConfigurationManager.getProperty("oai", "storage")); + solr = !("database").equals(configurationService.getProperty("oai.storage","solr")); if (solr) { System.out.println("OAI Manager Script"); diff --git a/dspace/config/modules/oai.cfg b/dspace/config/modules/oai.cfg index 5fbce8e880..493eb2510e 100644 --- a/dspace/config/modules/oai.cfg +++ b/dspace/config/modules/oai.cfg @@ -31,6 +31,13 @@ oai.cache.enabled = true # Base Cache Directory oai.cache.dir = ${dspace.dir}/var/oai +#---------------------------------------------------------------# +#--------------OAI IMPORT CONFIGURATION ------------------------# +#---------------------------------------------------------------# + +# Size of batches to commit to solr at a time +oai.import.batch.size = 1000 + #---------------------------------------------------------------# #--------------OAI HARVESTING CONFIGURATIONS--------------------# #---------------------------------------------------------------# From 929312d7e8728d33d3425e2b106c444444ea0468 Mon Sep 17 00:00:00 2001 From: Kim Shepherd Date: Sat, 19 Jan 2019 10:40:21 +1300 Subject: [PATCH 26/68] [DS-4136] tidy up commented out (old) configurationService instantiation in main() --- dspace-oai/src/main/java/org/dspace/xoai/app/XOAI.java | 1 - 1 file changed, 1 deletion(-) diff --git a/dspace-oai/src/main/java/org/dspace/xoai/app/XOAI.java b/dspace-oai/src/main/java/org/dspace/xoai/app/XOAI.java index ed7a66db66..8c2ac8a1f9 100644 --- a/dspace-oai/src/main/java/org/dspace/xoai/app/XOAI.java +++ b/dspace-oai/src/main/java/org/dspace/xoai/app/XOAI.java @@ -539,7 +539,6 @@ public class XOAI { BasicConfiguration.class }); - //ConfigurationService configurationService = applicationContext.getBean(ConfigurationService.class); XOAICacheService cacheService = applicationContext.getBean(XOAICacheService.class); XOAIItemCacheService itemCacheService = applicationContext.getBean(XOAIItemCacheService.class); From da1346ef531cf8bd9b1f0693a3ac3d9f915766e6 Mon Sep 17 00:00:00 2001 From: Terry Brady Date: Mon, 28 Jan 2019 10:05:48 -0800 Subject: [PATCH 27/68] Match dspace.restUrl to module name --- dspace/config/dspace.cfg | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dspace/config/dspace.cfg b/dspace/config/dspace.cfg index 6cebe39bdc..76c504201a 100644 --- a/dspace/config/dspace.cfg +++ b/dspace/config/dspace.cfg @@ -36,7 +36,7 @@ dspace.url = ${dspace.baseUrl} # This is the URL that will be used for the REST endpoints to be served on. # This will typically be followed by /api to determine the root endpoints. -dspace.restUrl = ${dspace.baseUrl}/rest +dspace.restUrl = ${dspace.baseUrl}/spring-rest # Optional: DSpace URL for mobile access # This From 1957f36988e67a1472a3875c380507daa88176f2 Mon Sep 17 00:00:00 2001 From: Tim Donohue Date: Wed, 30 Jan 2019 15:24:37 -0600 Subject: [PATCH 28/68] Notes on running tests --- README.md | 51 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 51 insertions(+) diff --git a/README.md b/README.md index a62fc1943d..aac6b7e6cc 100644 --- a/README.md +++ b/README.md @@ -79,6 +79,57 @@ install, upgrade, customize or host DSpace, then we recommend getting in touch w The DSpace Issue Tracker can be found at: https://jira.duraspace.org/projects/DS/summary +## Testing + +### Running Tests + +By default, in DSpace, Unit Tests and Integration Tests are disabled. However, they are +run automatically by [Travis CI](https://travis-ci.org/DSpace/DSpace/) for all Pull Requests and code commits. + +* How to run both Unit Tests (via `maven-surefire-plugin`) and Integration Tests (via `maven-failsafe-plugin`): + ``` + # NOTE: while "mvn test" runs Unit Tests, + # Integration Tests only run for "verify" or "install" phases + mvn clean install -Dmaven.test.skip=false -DskipITs=false + ``` +* How to run just Unit Tests: + ``` + mvn clean test -Dmaven.test.skip=false + ``` +* How to run a *single* Unit Test + ``` + # Run all tests in a specific test class + # NOTE: testClassName is just the class name, do not include package + mvn clean test -Dmaven.test.skip=false -Dtest=[testClassName] + + # Run one test method in a specific test class + mvn clean test -Dmaven.test.skip=false -Dtest=[testClassName]#[testMethodName] + ``` +* How to run Integration Tests (requires running Unit tests too) + ``` + mvn clean verify -Dmaven.test.skip=false -DskipITs=false + ``` +* How to run a *single* Integration Test (requires running Unit tests too) + ``` + # Run all integration tests in a specific test class + # NOTE: Integration Tests only run for "verify" or "install" phases + # NOTE: testClassName is just the class name, do not include package + mvn clean verify -Dmaven.test.skip=false -DskipITs=false -Dit.test=[testClassName] + + # Run one test method in a specific test class + mvn clean verify -Dmaven.test.skip=false -DskipITs=false -Dit.test=[testClassName]#[testMethodName] + ``` +* How to run only tests of a specific DSpace module + ``` + # Before you can run only one module's tests, other modules may need installing into your ~/.m2 + cd [dspace-src] + mvn clean install + + # Then, move into a module subdirectory, and run the test command + cd [dspace-src]/dspace-spring-rest + # Choose your test command from the lists above + ``` + ## License DSpace source code is freely available under a standard [BSD 3-Clause license](https://opensource.org/licenses/BSD-3-Clause). From 2db2b70e8201d9616b3ed3be2462a081df413c6e Mon Sep 17 00:00:00 2001 From: Raf Ponsaerts Date: Mon, 4 Feb 2019 10:11:52 +0100 Subject: [PATCH 29/68] Altered permissions on the Collection and Community Repositories and added ITs --- .../repository/CollectionRestRepository.java | 4 +- .../repository/CommunityRestRepository.java | 4 +- .../app/rest/CollectionRestRepositoryIT.java | 118 ++++++++++++++++++ .../app/rest/CommunityRestRepositoryIT.java | 106 ++++++++++++++++ 4 files changed, 228 insertions(+), 4 deletions(-) diff --git a/dspace-spring-rest/src/main/java/org/dspace/app/rest/repository/CollectionRestRepository.java b/dspace-spring-rest/src/main/java/org/dspace/app/rest/repository/CollectionRestRepository.java index 54957d3068..9f25e12d19 100644 --- a/dspace-spring-rest/src/main/java/org/dspace/app/rest/repository/CollectionRestRepository.java +++ b/dspace-spring-rest/src/main/java/org/dspace/app/rest/repository/CollectionRestRepository.java @@ -214,7 +214,7 @@ public class CollectionRestRepository extends DSpaceRestRepository Date: Mon, 26 Nov 2018 11:59:50 +0100 Subject: [PATCH 30/68] Implemented the create method in the ItemRestRepository --- .../app/rest/converter/ItemConverter.java | 1 + .../org/dspace/app/rest/model/ItemRest.java | 10 ++ .../rest/repository/ItemRestRepository.java | 56 +++++++++++ .../dspace/app/rest/ItemRestRepositoryIT.java | 95 +++++++++++++++++++ .../dspace/app/rest/matcher/ItemMatcher.java | 2 + 5 files changed, 164 insertions(+) diff --git a/dspace-spring-rest/src/main/java/org/dspace/app/rest/converter/ItemConverter.java b/dspace-spring-rest/src/main/java/org/dspace/app/rest/converter/ItemConverter.java index 8c9743457c..1a8221161f 100644 --- a/dspace-spring-rest/src/main/java/org/dspace/app/rest/converter/ItemConverter.java +++ b/dspace-spring-rest/src/main/java/org/dspace/app/rest/converter/ItemConverter.java @@ -46,6 +46,7 @@ public class ItemConverter extends DSpaceObjectConverter { @Autowired ItemPatch itemPatch; + @Autowired + WorkspaceItemService workspaceItemService; + + @Autowired + CollectionService collectionService; + + @Autowired + DSpaceObjectUtils dspaceObjectUtils; + + @Autowired + InstallItemService installItemService; public ItemRestRepository() { System.out.println("Repository initialized by Spring"); @@ -177,4 +198,39 @@ public class ItemRestRepository extends DSpaceRestRepository { } } + @Override + @PreAuthorize("hasAuthority('ADMIN')") + protected ItemRest createAndReturn(Context context) throws AuthorizeException, SQLException { + HttpServletRequest req = getRequestService().getCurrentRequest().getHttpServletRequest(); + ObjectMapper mapper = new ObjectMapper(); + ItemRest itemRest = null; + try { + ServletInputStream input = req.getInputStream(); + itemRest = mapper.readValue(input, ItemRest.class); + } catch (IOException e1) { + throw new UnprocessableEntityException("Error parsing request body: " + e1.toString()); + } + + if (itemRest.getInArchive() == false) { + throw new BadRequestException("InArchive attribute should not be set to false for the create"); + } + Collection collection = collectionService.find(context, + UUIDUtils.fromString(itemRest.getOwningCollectionUuid())); + if (collection == null) { + throw new BadRequestException("The given collection in the body is invalid: " + + itemRest.getOwningCollectionUuid()); + } + WorkspaceItem workspaceItem = workspaceItemService.create(context, collection, false); + Item item = workspaceItem.getItem(); + item.setArchived(true); + item.setOwningCollection(collection); + item.setDiscoverable(itemRest.getDiscoverable()); + item.setLastModified(itemRest.getLastModified()); + dspaceObjectUtils.replaceMetadataValues(context, item, itemRest.getMetadata()); + + Item itemToReturn = installItemService.installItem(context, workspaceItem); + + return converter.fromModel(itemToReturn); + } + } \ No newline at end of file diff --git a/dspace-spring-rest/src/test/java/org/dspace/app/rest/ItemRestRepositoryIT.java b/dspace-spring-rest/src/test/java/org/dspace/app/rest/ItemRestRepositoryIT.java index 55de139f29..8ed44ca86f 100644 --- a/dspace-spring-rest/src/test/java/org/dspace/app/rest/ItemRestRepositoryIT.java +++ b/dspace-spring-rest/src/test/java/org/dspace/app/rest/ItemRestRepositoryIT.java @@ -7,20 +7,26 @@ */ package org.dspace.app.rest; +import static com.jayway.jsonpath.matchers.JsonPathMatchers.hasJsonPath; +import static org.hamcrest.Matchers.contains; import static org.hamcrest.Matchers.is; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.delete; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.patch; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; import java.io.InputStream; import java.util.ArrayList; +import java.util.Arrays; import java.util.List; +import java.util.Map; import java.util.UUID; import javax.ws.rs.core.MediaType; +import com.fasterxml.jackson.databind.ObjectMapper; import org.apache.commons.io.IOUtils; import org.apache.commons.lang3.CharEncoding; import org.dspace.app.rest.builder.BitstreamBuilder; @@ -31,6 +37,8 @@ import org.dspace.app.rest.builder.GroupBuilder; import org.dspace.app.rest.builder.ItemBuilder; import org.dspace.app.rest.builder.WorkspaceItemBuilder; import org.dspace.app.rest.matcher.ItemMatcher; +import org.dspace.app.rest.model.ItemRest; +import org.dspace.app.rest.model.MetadataEntryRest; import org.dspace.app.rest.model.patch.Operation; import org.dspace.app.rest.model.patch.ReplaceOperation; import org.dspace.app.rest.test.AbstractControllerIntegrationTest; @@ -43,6 +51,7 @@ import org.dspace.eperson.EPerson; import org.dspace.eperson.Group; import org.hamcrest.Matchers; import org.junit.Test; +import org.springframework.test.web.servlet.MvcResult; public class ItemRestRepositoryIT extends AbstractControllerIntegrationTest { @@ -1414,4 +1423,90 @@ public class ItemRestRepositoryIT extends AbstractControllerIntegrationTest { Matchers.containsString("/api/core/items"))); } + @Test + public void testCreateItem() throws Exception { + + context.turnOffAuthorisationSystem(); + + //** GIVEN ** + //1. A community-collection structure with one parent community with sub-community and two collections. + parentCommunity = CommunityBuilder.createCommunity(context) + .withName("Parent Community") + .build(); + Community child1 = CommunityBuilder.createSubCommunity(context, parentCommunity) + .withName("Sub Community") + .build(); + Collection col1 = CollectionBuilder.createCollection(context, child1).withName("Collection 1").build(); + + + + ObjectMapper mapper = new ObjectMapper(); + ItemRest itemRest = new ItemRest(); + itemRest.setName("Practices of research data curation in institutional repositories:" + + " A qualitative view from repository staff"); + itemRest.setOwningCollectionUuid(col1.getID().toString()); + itemRest.setInArchive(true); + itemRest.setDiscoverable(true); + itemRest.setWithdrawn(false); + + MetadataEntryRest description = new MetadataEntryRest(); + description.setKey("dc.description"); + description.setValue("

Some cool HTML code here

"); + + MetadataEntryRest abs = new MetadataEntryRest(); + abs.setKey("dc.description.abstract"); + abs.setValue("Sample item created via the REST API"); + + MetadataEntryRest contents = new MetadataEntryRest(); + contents.setKey("dc.description.tableofcontents"); + contents.setValue("

HTML News

"); + + MetadataEntryRest copyright = new MetadataEntryRest(); + copyright.setKey("dc.rights"); + copyright.setValue("Custom Copyright Text"); + + MetadataEntryRest title = new MetadataEntryRest(); + title.setKey("dc.title"); + title.setValue("Title Text"); + + itemRest.setMetadata(Arrays.asList(description, + abs, + contents, + copyright, + title)); + + String token = getAuthToken(admin.getEmail(), password); + MvcResult mvcResult = getClient(token).perform(post("/api/core/items") + .content(mapper.writeValueAsBytes(itemRest)).contentType(contentType)) + .andExpect(status().isCreated()) + .andReturn(); + + String content = mvcResult.getResponse().getContentAsString(); + Map map = mapper.readValue(content, Map.class); + String itemUuidString = String.valueOf(map.get("uuid")); + String itemHandleString = String.valueOf(map.get("handle")); + + //TODO Refactor this to use the converter to Item instead of checking every property separately + getClient(token).perform(get("/api/core/items/" + itemUuidString)) + .andExpect(status().isOk()) + .andExpect(jsonPath("$", Matchers.allOf( + hasJsonPath("$.id", is(itemUuidString)), + hasJsonPath("$.uuid", is(itemUuidString)), + hasJsonPath("$.name", is("Title Text")), + hasJsonPath("$.handle", is(itemHandleString)), + hasJsonPath("$.type", is("item")), + hasJsonPath("$.metadata[?(@.key=='dc.title')].value", + contains("Title Text")), + hasJsonPath("$.metadata[?(@.key=='dc.rights')].value", + contains("Custom Copyright Text")), + hasJsonPath("$.metadata[?(@.key=='dc.description.tableofcontents')].value", + contains("

HTML News

")), + hasJsonPath("$.metadata[?(@.key=='dc.description.abstract')].value", + contains("Sample item created via the REST API")), + hasJsonPath("$.metadata[?(@.key=='dc.description')].value", + contains("

Some cool HTML code here

")) + + ))); + } + } \ No newline at end of file diff --git a/dspace-spring-rest/src/test/java/org/dspace/app/rest/matcher/ItemMatcher.java b/dspace-spring-rest/src/test/java/org/dspace/app/rest/matcher/ItemMatcher.java index e5176eea26..28850fc6ff 100644 --- a/dspace-spring-rest/src/test/java/org/dspace/app/rest/matcher/ItemMatcher.java +++ b/dspace-spring-rest/src/test/java/org/dspace/app/rest/matcher/ItemMatcher.java @@ -64,4 +64,6 @@ public class ItemMatcher { ); } + + } From 2e446bf9a33a2b7a2f2f2a34dca484f50a7a550b Mon Sep 17 00:00:00 2001 From: Raf Ponsaerts Date: Tue, 4 Dec 2018 16:06:42 +0100 Subject: [PATCH 31/68] Added admin access to item delete and wrote tests --- .../rest/repository/ItemRestRepository.java | 1 + .../dspace/app/rest/ItemRestRepositoryIT.java | 187 ++++++++++++++++++ 2 files changed, 188 insertions(+) diff --git a/dspace-spring-rest/src/main/java/org/dspace/app/rest/repository/ItemRestRepository.java b/dspace-spring-rest/src/main/java/org/dspace/app/rest/repository/ItemRestRepository.java index 411c693c83..3764f3f272 100644 --- a/dspace-spring-rest/src/main/java/org/dspace/app/rest/repository/ItemRestRepository.java +++ b/dspace-spring-rest/src/main/java/org/dspace/app/rest/repository/ItemRestRepository.java @@ -176,6 +176,7 @@ public class ItemRestRepository extends DSpaceRestRepository { } @Override + @PreAuthorize("hasAuthority('ADMIN')") protected void delete(Context context, UUID id) throws AuthorizeException { Item item = null; try { diff --git a/dspace-spring-rest/src/test/java/org/dspace/app/rest/ItemRestRepositoryIT.java b/dspace-spring-rest/src/test/java/org/dspace/app/rest/ItemRestRepositoryIT.java index 8ed44ca86f..d1f46541e3 100644 --- a/dspace-spring-rest/src/test/java/org/dspace/app/rest/ItemRestRepositoryIT.java +++ b/dspace-spring-rest/src/test/java/org/dspace/app/rest/ItemRestRepositoryIT.java @@ -1509,4 +1509,191 @@ public class ItemRestRepositoryIT extends AbstractControllerIntegrationTest { ))); } + + @Test + public void testDeleteItem() throws Exception { + + context.turnOffAuthorisationSystem(); + + //** GIVEN ** + //1. A community-collection structure with one parent community with sub-community and two collections. + parentCommunity = CommunityBuilder.createCommunity(context) + .withName("Parent Community") + .build(); + Community child1 = CommunityBuilder.createSubCommunity(context, parentCommunity) + .withName("Sub Community") + .build(); + Collection col1 = CollectionBuilder.createCollection(context, child1).withName("Collection 1").build(); + + + + ObjectMapper mapper = new ObjectMapper(); + ItemRest itemRest = new ItemRest(); + itemRest.setName("Practices of research data curation in institutional repositories:" + + " A qualitative view from repository staff"); + itemRest.setOwningCollectionUuid(col1.getID().toString()); + itemRest.setInArchive(true); + itemRest.setDiscoverable(true); + itemRest.setWithdrawn(false); + + MetadataEntryRest description = new MetadataEntryRest(); + description.setKey("dc.description"); + description.setValue("

Some cool HTML code here

"); + + MetadataEntryRest abs = new MetadataEntryRest(); + abs.setKey("dc.description.abstract"); + abs.setValue("Sample item created via the REST API"); + + MetadataEntryRest contents = new MetadataEntryRest(); + contents.setKey("dc.description.tableofcontents"); + contents.setValue("

HTML News

"); + + MetadataEntryRest copyright = new MetadataEntryRest(); + copyright.setKey("dc.rights"); + copyright.setValue("Custom Copyright Text"); + + MetadataEntryRest title = new MetadataEntryRest(); + title.setKey("dc.title"); + title.setValue("Title Text"); + + itemRest.setMetadata(Arrays.asList(description, + abs, + contents, + copyright, + title)); + + String token = getAuthToken(admin.getEmail(), password); + MvcResult mvcResult = getClient(token).perform(post("/api/core/items") + .content(mapper.writeValueAsBytes(itemRest)) + .contentType(contentType)) + .andExpect(status().isCreated()) + .andReturn(); + + String content = mvcResult.getResponse().getContentAsString(); + Map map = mapper.readValue(content, Map.class); + String itemUuidString = String.valueOf(map.get("uuid")); + String itemHandleString = String.valueOf(map.get("handle")); + + //TODO Refactor this to use the converter to Item instead of checking every property separately + getClient(token).perform(get("/api/core/items/" + itemUuidString)) + .andExpect(status().isOk()) + .andExpect(jsonPath("$", Matchers.allOf( + hasJsonPath("$.id", is(itemUuidString)), + hasJsonPath("$.uuid", is(itemUuidString)), + hasJsonPath("$.name", is("Title Text")), + hasJsonPath("$.handle", is(itemHandleString)), + hasJsonPath("$.type", is("item")), + hasJsonPath("$.metadata[?(@.key=='dc.title')].value", + contains("Title Text")), + hasJsonPath("$.metadata[?(@.key=='dc.rights')].value", + contains("Custom Copyright Text")), + hasJsonPath("$.metadata[?(@.key=='dc.description.tableofcontents')].value", + contains("

HTML News

")), + hasJsonPath("$.metadata[?(@.key=='dc.description.abstract')].value", + contains("Sample item created via the REST API")), + hasJsonPath("$.metadata[?(@.key=='dc.description')].value", + contains("

Some cool HTML code here

")) + + ))); + + getClient(token).perform(delete("/api/core/items/" + itemUuidString)) + .andExpect(status().isNoContent()); + + getClient(token).perform(get("/api/core/items/" + itemUuidString)) + .andExpect(status().isNotFound()); + } + + @Test + public void testDeleteItemUnauthorized() throws Exception { + + context.turnOffAuthorisationSystem(); + + //** GIVEN ** + //1. A community-collection structure with one parent community with sub-community and two collections. + parentCommunity = CommunityBuilder.createCommunity(context) + .withName("Parent Community") + .build(); + Community child1 = CommunityBuilder.createSubCommunity(context, parentCommunity) + .withName("Sub Community") + .build(); + Collection col1 = CollectionBuilder.createCollection(context, child1).withName("Collection 1").build(); + + + + ObjectMapper mapper = new ObjectMapper(); + ItemRest itemRest = new ItemRest(); + itemRest.setName("Practices of research data curation in institutional repositories:" + + " A qualitative view from repository staff"); + itemRest.setOwningCollectionUuid(col1.getID().toString()); + itemRest.setInArchive(true); + itemRest.setDiscoverable(true); + itemRest.setWithdrawn(false); + + MetadataEntryRest description = new MetadataEntryRest(); + description.setKey("dc.description"); + description.setValue("

Some cool HTML code here

"); + + MetadataEntryRest abs = new MetadataEntryRest(); + abs.setKey("dc.description.abstract"); + abs.setValue("Sample item created via the REST API"); + + MetadataEntryRest contents = new MetadataEntryRest(); + contents.setKey("dc.description.tableofcontents"); + contents.setValue("

HTML News

"); + + MetadataEntryRest copyright = new MetadataEntryRest(); + copyright.setKey("dc.rights"); + copyright.setValue("Custom Copyright Text"); + + MetadataEntryRest title = new MetadataEntryRest(); + title.setKey("dc.title"); + title.setValue("Title Text"); + + itemRest.setMetadata(Arrays.asList(description, + abs, + contents, + copyright, + title)); + + String token = getAuthToken(admin.getEmail(), password); + MvcResult mvcResult = getClient(token).perform(post("/api/core/items") + .content(mapper.writeValueAsBytes(itemRest)) + .contentType(contentType)) + .andExpect(status().isCreated()) + .andReturn(); + + String content = mvcResult.getResponse().getContentAsString(); + Map map = mapper.readValue(content, Map.class); + String itemUuidString = String.valueOf(map.get("uuid")); + String itemHandleString = String.valueOf(map.get("handle")); + + //TODO Refactor this to use the converter to Item instead of checking every property separately + getClient(token).perform(get("/api/core/items/" + itemUuidString)) + .andExpect(status().isOk()) + .andExpect(jsonPath("$", Matchers.allOf( + hasJsonPath("$.id", is(itemUuidString)), + hasJsonPath("$.uuid", is(itemUuidString)), + hasJsonPath("$.name", is("Title Text")), + hasJsonPath("$.handle", is(itemHandleString)), + hasJsonPath("$.type", is("item")), + hasJsonPath("$.metadata[?(@.key=='dc.title')].value", + contains("Title Text")), + hasJsonPath("$.metadata[?(@.key=='dc.rights')].value", + contains("Custom Copyright Text")), + hasJsonPath("$.metadata[?(@.key=='dc.description.tableofcontents')].value", + contains("

HTML News

")), + hasJsonPath("$.metadata[?(@.key=='dc.description.abstract')].value", + contains("Sample item created via the REST API")), + hasJsonPath("$.metadata[?(@.key=='dc.description')].value", + contains("

Some cool HTML code here

")) + + ))); + + getClient().perform(delete("/api/core/items/" + itemUuidString)) + .andExpect(status().isUnauthorized()); + + getClient(token).perform(get("/api/core/items/" + itemUuidString)) + .andExpect(status().isOk()); + } + } \ No newline at end of file From db8f6f1bb8bbccbf7a6e6993e5937aadefc56f08 Mon Sep 17 00:00:00 2001 From: Raf Ponsaerts Date: Fri, 7 Dec 2018 09:58:13 +0100 Subject: [PATCH 32/68] Fixed the 500 internal server error when calling item delete endpoint with invalid UUID --- .../rest/repository/ItemRestRepository.java | 4 +++ .../dspace/app/rest/ItemRestRepositoryIT.java | 29 +++++++++++++++++++ 2 files changed, 33 insertions(+) diff --git a/dspace-spring-rest/src/main/java/org/dspace/app/rest/repository/ItemRestRepository.java b/dspace-spring-rest/src/main/java/org/dspace/app/rest/repository/ItemRestRepository.java index 3764f3f272..50082bf4d5 100644 --- a/dspace-spring-rest/src/main/java/org/dspace/app/rest/repository/ItemRestRepository.java +++ b/dspace-spring-rest/src/main/java/org/dspace/app/rest/repository/ItemRestRepository.java @@ -181,6 +181,10 @@ public class ItemRestRepository extends DSpaceRestRepository { Item item = null; try { item = is.find(context, id); + if (item == null) { + throw new ResourceNotFoundException(ItemRest.CATEGORY + "." + ItemRest.NAME + + " with id: " + id + " not found"); + } if (is.isInProgressSubmission(context, item)) { throw new UnprocessableEntityException("The item cannot be deleted. " + "It's part of a in-progress submission."); diff --git a/dspace-spring-rest/src/test/java/org/dspace/app/rest/ItemRestRepositoryIT.java b/dspace-spring-rest/src/test/java/org/dspace/app/rest/ItemRestRepositoryIT.java index d1f46541e3..181f1dd075 100644 --- a/dspace-spring-rest/src/test/java/org/dspace/app/rest/ItemRestRepositoryIT.java +++ b/dspace-spring-rest/src/test/java/org/dspace/app/rest/ItemRestRepositoryIT.java @@ -1696,4 +1696,33 @@ public class ItemRestRepositoryIT extends AbstractControllerIntegrationTest { .andExpect(status().isOk()); } + @Test + public void deleteOneWrongUuidResourceNotFoundTest() throws Exception { + context.turnOffAuthorisationSystem(); + + //** GIVEN ** + //1. A community with one collection. + parentCommunity = CommunityBuilder.createCommunity(context) + .withName("Parent Community") + .build(); + Collection col1 = CollectionBuilder + .createCollection(context, parentCommunity).withName("Collection 1").build(); + + //2. One public item, one workspace item and one template item. + Item publicItem = ItemBuilder.createItem(context, col1) + .withTitle("Public item 1") + .withIssueDate("2017-10-17") + .withAuthor("Smith, Donald").withAuthor("Doe, John") + .withSubject("ExtraEntry") + .build(); + + + String token = getAuthToken(admin.getEmail(), password); + + //Delete public item + getClient(token).perform(delete("/api/core/items/" + parentCommunity.getID())) + .andExpect(status().is(404)); + + } + } \ No newline at end of file From 02ff91259f8768b6373e52149dfe93c8ea3978ae Mon Sep 17 00:00:00 2001 From: Kevin Van de Velde Date: Wed, 12 Dec 2018 13:13:04 +0100 Subject: [PATCH 33/68] [DS-4108] Rest api item CRUD: Updating item metadata --- .../app/rest/RestResourceController.java | 2 +- .../rest/repository/DSpaceRestRepository.java | 2 +- .../rest/repository/ItemRestRepository.java | 38 +++++++ .../dspace/app/rest/ItemRestRepositoryIT.java | 107 ++++++++++++++++++ 4 files changed, 147 insertions(+), 2 deletions(-) diff --git a/dspace-spring-rest/src/main/java/org/dspace/app/rest/RestResourceController.java b/dspace-spring-rest/src/main/java/org/dspace/app/rest/RestResourceController.java index 5a27ec4087..51d673c1d6 100644 --- a/dspace-spring-rest/src/main/java/org/dspace/app/rest/RestResourceController.java +++ b/dspace-spring-rest/src/main/java/org/dspace/app/rest/RestResourceController.java @@ -1039,4 +1039,4 @@ public class RestResourceController implements InitializingBean { linkService.addLinks(result); return result; } -} +} \ No newline at end of file diff --git a/dspace-spring-rest/src/main/java/org/dspace/app/rest/repository/DSpaceRestRepository.java b/dspace-spring-rest/src/main/java/org/dspace/app/rest/repository/DSpaceRestRepository.java index ab317aef4a..5c3b335a65 100644 --- a/dspace-spring-rest/src/main/java/org/dspace/app/rest/repository/DSpaceRestRepository.java +++ b/dspace-spring-rest/src/main/java/org/dspace/app/rest/repository/DSpaceRestRepository.java @@ -447,4 +447,4 @@ public abstract class DSpaceRestRepository { @Autowired WorkspaceItemService workspaceItemService; + @Autowired + ItemService itemService; + @Autowired CollectionService collectionService; @@ -238,4 +245,35 @@ public class ItemRestRepository extends DSpaceRestRepository { return converter.fromModel(itemToReturn); } + @Override + @PreAuthorize("hasPermission(#id, 'ITEM', 'WRITE')") + protected ItemRest put(Context context, HttpServletRequest request, String apiCategory, String model, UUID uuid, + JsonNode jsonNode) + throws RepositoryMethodNotImplementedException, SQLException, AuthorizeException { + HttpServletRequest req = getRequestService().getCurrentRequest().getHttpServletRequest(); + ObjectMapper mapper = new ObjectMapper(); + ItemRest itemRest = null; + try { + itemRest = mapper.readValue(jsonNode.toString(), ItemRest.class); + } catch (IOException e1) { + throw new UnprocessableEntityException("Error parsing request body: " + e1.toString()); + } + + Item item = itemService.find(context, uuid); + if (item == null) { + throw new ResourceNotFoundException(apiCategory + "." + model + " with id: " + uuid + " not found"); + } + + if (StringUtils.equals(uuid.toString(), itemRest.getId())) { + List metadataEntryRestList = itemRest.getMetadata(); + item = (Item) dspaceObjectUtils.replaceMetadataValues(context, + item, + metadataEntryRestList); + } else { + throw new IllegalArgumentException("The UUID in the Json and the UUID in the url do not match: " + + uuid + ", " + + itemRest.getId()); + } + return converter.fromModel(item); + } } \ No newline at end of file diff --git a/dspace-spring-rest/src/test/java/org/dspace/app/rest/ItemRestRepositoryIT.java b/dspace-spring-rest/src/test/java/org/dspace/app/rest/ItemRestRepositoryIT.java index 181f1dd075..2323430819 100644 --- a/dspace-spring-rest/src/test/java/org/dspace/app/rest/ItemRestRepositoryIT.java +++ b/dspace-spring-rest/src/test/java/org/dspace/app/rest/ItemRestRepositoryIT.java @@ -14,6 +14,7 @@ import static org.springframework.test.web.servlet.request.MockMvcRequestBuilder import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.patch; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.put; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; @@ -1509,6 +1510,112 @@ public class ItemRestRepositoryIT extends AbstractControllerIntegrationTest { ))); } + @Test + public void updateTest() throws Exception { + //We turn off the authorization system in order to create the structure as defined below + context.turnOffAuthorisationSystem(); + //** GIVEN ** + //1. A community-collection structure with one parent community with sub-community and two collections. + parentCommunity = CommunityBuilder.createCommunity(context) + .withName("Parent Community") + .build(); + Community child1 = CommunityBuilder.createSubCommunity(context, parentCommunity) + .withName("Sub Community") + .build(); + Collection col1 = CollectionBuilder.createCollection(context, child1).withName("Collection 1").build(); + + ObjectMapper mapper = new ObjectMapper(); + ItemRest itemRest = new ItemRest(); + itemRest.setName("Practices of research data curation in institutional repositories:" + + " A qualitative view from repository staff"); + itemRest.setOwningCollectionUuid(col1.getID().toString()); + itemRest.setInArchive(true); + itemRest.setDiscoverable(true); + itemRest.setWithdrawn(false); + + MetadataEntryRest description = new MetadataEntryRest(); + description.setKey("dc.description"); + description.setValue("

Some cool HTML code here

"); + + MetadataEntryRest abs = new MetadataEntryRest(); + abs.setKey("dc.description.abstract"); + abs.setValue("Sample item created via the REST API"); + + MetadataEntryRest contents = new MetadataEntryRest(); + contents.setKey("dc.description.tableofcontents"); + contents.setValue("

HTML News

"); + + MetadataEntryRest copyright = new MetadataEntryRest(); + copyright.setKey("dc.rights"); + copyright.setValue("Custom Copyright Text"); + + MetadataEntryRest title = new MetadataEntryRest(); + title.setKey("dc.title"); + title.setValue("Title Text"); + + itemRest.setMetadata(Arrays.asList(description, + abs, + contents, + copyright, + title)); + + String token = getAuthToken(admin.getEmail(), password); + MvcResult mvcResult = getClient(token).perform(post("/api/core/items") + .content(mapper.writeValueAsBytes(itemRest)) + .contentType(contentType)) + .andExpect(status().isCreated()) + .andReturn(); + + String content = mvcResult.getResponse().getContentAsString(); + Map map = mapper.readValue(content, Map.class); + String itemUuidString = String.valueOf(map.get("uuid")); + String itemHandleString = String.valueOf(map.get("handle")); + + + title.setValue("New title"); + copyright.setValue("New Custom Copyright Text"); + + itemRest.setUuid(itemUuidString); + itemRest.setHandle(itemHandleString); + itemRest.setMetadata(Arrays.asList(description, + abs, + contents, + copyright, + title)); + itemRest.setName("New title"); + + mvcResult = getClient(token).perform(put("/api/core/items/" + itemUuidString) + .content(mapper.writeValueAsBytes(itemRest)) + .contentType(contentType)) + .andExpect(status().isOk()) + .andReturn(); + map = mapper.readValue(content, Map.class); + itemUuidString = String.valueOf(map.get("uuid")); + itemHandleString = String.valueOf(map.get("handle")); + + //TODO Refactor this to use the converter to Item instead of checking every property separately + getClient(token).perform(get("/api/core/items/" + itemUuidString)) + .andExpect(status().isOk()) + .andExpect(jsonPath("$", Matchers.allOf( + hasJsonPath("$.id", is(itemUuidString)), + hasJsonPath("$.uuid", is(itemUuidString)), + hasJsonPath("$.name", is("New title")), + hasJsonPath("$.handle", is(itemHandleString)), + hasJsonPath("$.type", is("item")), + hasJsonPath("$.metadata[?(@.key=='dc.title')].value", + contains("New title")), + hasJsonPath("$.metadata[?(@.key=='dc.rights')].value", + contains("New Custom Copyright Text")), + hasJsonPath("$.metadata[?(@.key=='dc.description.tableofcontents')].value", + contains("

HTML News

")), + hasJsonPath("$.metadata[?(@.key=='dc.description.abstract')].value", + contains("Sample item created via the REST API")), + hasJsonPath("$.metadata[?(@.key=='dc.description')].value", + contains("

Some cool HTML code here

")) + + ))); + } + @Test public void testDeleteItem() throws Exception { From 47d7021350a411d2ed8d1efb0ecdfe1f4d2d5a0c Mon Sep 17 00:00:00 2001 From: Raf Ponsaerts Date: Mon, 7 Jan 2019 09:56:06 +0100 Subject: [PATCH 34/68] Added owningCollection as parameter to the POST Item endpoint and removed this from ItemRest. Also added documentation for several public methods. --- .../dspace/app/rest/RestResourceController.java | 6 +++--- .../dspace/app/rest/converter/ItemConverter.java | 1 - .../java/org/dspace/app/rest/model/ItemRest.java | 11 ----------- .../rest/repository/DSpaceRestRepository.java | 10 +++++++--- .../app/rest/repository/ItemRestRepository.java | 9 +++++---- .../dspace/app/rest/ItemRestRepositoryIT.java | 16 ++++++++-------- 6 files changed, 23 insertions(+), 30 deletions(-) diff --git a/dspace-spring-rest/src/main/java/org/dspace/app/rest/RestResourceController.java b/dspace-spring-rest/src/main/java/org/dspace/app/rest/RestResourceController.java index 51d673c1d6..dea2395731 100644 --- a/dspace-spring-rest/src/main/java/org/dspace/app/rest/RestResourceController.java +++ b/dspace-spring-rest/src/main/java/org/dspace/app/rest/RestResourceController.java @@ -990,18 +990,18 @@ public class RestResourceController implements InitializingBean { /** * Execute a PUT request for an entity with id of type UUID; * - * curl -X PUT http:///dspace-spring-rest/api/{apiCategory}/{model} + * curl -X PUT http:///dspace-spring-rest/api/{apiCategory}/{model}/{uuid} * * Example: *
      * {@code
-     *      curl -X PUT http:///dspace-spring-rest/api/collection
+     *      curl -X PUT http:///dspace-spring-rest/api/collection/320c0492-de1d-4646-9e69-193d36b366e9
      * }
      * 
* * @param request the http request * @param apiCategory the API category e.g. "api" - * @param model the DSpace model e.g. "metadatafield" + * @param model the DSpace model e.g. "collection" * @param uuid the ID of the target REST object * @param jsonNode the part of the request body representing the updated rest object * @return the relevant REST resource diff --git a/dspace-spring-rest/src/main/java/org/dspace/app/rest/converter/ItemConverter.java b/dspace-spring-rest/src/main/java/org/dspace/app/rest/converter/ItemConverter.java index 1a8221161f..8c9743457c 100644 --- a/dspace-spring-rest/src/main/java/org/dspace/app/rest/converter/ItemConverter.java +++ b/dspace-spring-rest/src/main/java/org/dspace/app/rest/converter/ItemConverter.java @@ -46,7 +46,6 @@ public class ItemConverter extends DSpaceObjectConverter bitstreams; @Override @@ -102,12 +99,4 @@ public class ItemRest extends DSpaceObjectRest { public void setBitstreams(List bitstreams) { this.bitstreams = bitstreams; } - - public String getOwningCollectionUuid() { - return owningCollectionUuid; - } - - public void setOwningCollectionUuid(String owningCollectionUuid) { - this.owningCollectionUuid = owningCollectionUuid; - } } \ No newline at end of file diff --git a/dspace-spring-rest/src/main/java/org/dspace/app/rest/repository/DSpaceRestRepository.java b/dspace-spring-rest/src/main/java/org/dspace/app/rest/repository/DSpaceRestRepository.java index 5c3b335a65..0be6f7475a 100644 --- a/dspace-spring-rest/src/main/java/org/dspace/app/rest/repository/DSpaceRestRepository.java +++ b/dspace-spring-rest/src/main/java/org/dspace/app/rest/repository/DSpaceRestRepository.java @@ -433,14 +433,18 @@ public abstract class DSpaceRestRepository { @PreAuthorize("hasAuthority('ADMIN')") protected ItemRest createAndReturn(Context context) throws AuthorizeException, SQLException { HttpServletRequest req = getRequestService().getCurrentRequest().getHttpServletRequest(); + String owningCollectionUuidString = req.getParameter("owningCollection"); ObjectMapper mapper = new ObjectMapper(); ItemRest itemRest = null; try { @@ -226,11 +227,11 @@ public class ItemRestRepository extends DSpaceRestRepository { if (itemRest.getInArchive() == false) { throw new BadRequestException("InArchive attribute should not be set to false for the create"); } - Collection collection = collectionService.find(context, - UUIDUtils.fromString(itemRest.getOwningCollectionUuid())); + UUID owningCollectionUuid = UUIDUtils.fromString(owningCollectionUuidString); + Collection collection = collectionService.find(context, owningCollectionUuid); if (collection == null) { - throw new BadRequestException("The given collection in the body is invalid: " - + itemRest.getOwningCollectionUuid()); + throw new BadRequestException("The given owningCollection parameter is invalid: " + + owningCollectionUuid); } WorkspaceItem workspaceItem = workspaceItemService.create(context, collection, false); Item item = workspaceItem.getItem(); diff --git a/dspace-spring-rest/src/test/java/org/dspace/app/rest/ItemRestRepositoryIT.java b/dspace-spring-rest/src/test/java/org/dspace/app/rest/ItemRestRepositoryIT.java index 2323430819..e0098d3453 100644 --- a/dspace-spring-rest/src/test/java/org/dspace/app/rest/ItemRestRepositoryIT.java +++ b/dspace-spring-rest/src/test/java/org/dspace/app/rest/ItemRestRepositoryIT.java @@ -1445,7 +1445,6 @@ public class ItemRestRepositoryIT extends AbstractControllerIntegrationTest { ItemRest itemRest = new ItemRest(); itemRest.setName("Practices of research data curation in institutional repositories:" + " A qualitative view from repository staff"); - itemRest.setOwningCollectionUuid(col1.getID().toString()); itemRest.setInArchive(true); itemRest.setDiscoverable(true); itemRest.setWithdrawn(false); @@ -1477,7 +1476,8 @@ public class ItemRestRepositoryIT extends AbstractControllerIntegrationTest { title)); String token = getAuthToken(admin.getEmail(), password); - MvcResult mvcResult = getClient(token).perform(post("/api/core/items") + MvcResult mvcResult = getClient(token).perform(post("/api/core/items?owningCollection=" + + col1.getID().toString()) .content(mapper.writeValueAsBytes(itemRest)).contentType(contentType)) .andExpect(status().isCreated()) .andReturn(); @@ -1528,7 +1528,6 @@ public class ItemRestRepositoryIT extends AbstractControllerIntegrationTest { ItemRest itemRest = new ItemRest(); itemRest.setName("Practices of research data curation in institutional repositories:" + " A qualitative view from repository staff"); - itemRest.setOwningCollectionUuid(col1.getID().toString()); itemRest.setInArchive(true); itemRest.setDiscoverable(true); itemRest.setWithdrawn(false); @@ -1560,7 +1559,8 @@ public class ItemRestRepositoryIT extends AbstractControllerIntegrationTest { title)); String token = getAuthToken(admin.getEmail(), password); - MvcResult mvcResult = getClient(token).perform(post("/api/core/items") + MvcResult mvcResult = getClient(token).perform(post("/api/core/items?owningCollection=" + + col1.getID().toString()) .content(mapper.writeValueAsBytes(itemRest)) .contentType(contentType)) .andExpect(status().isCreated()) @@ -1638,7 +1638,6 @@ public class ItemRestRepositoryIT extends AbstractControllerIntegrationTest { ItemRest itemRest = new ItemRest(); itemRest.setName("Practices of research data curation in institutional repositories:" + " A qualitative view from repository staff"); - itemRest.setOwningCollectionUuid(col1.getID().toString()); itemRest.setInArchive(true); itemRest.setDiscoverable(true); itemRest.setWithdrawn(false); @@ -1670,7 +1669,8 @@ public class ItemRestRepositoryIT extends AbstractControllerIntegrationTest { title)); String token = getAuthToken(admin.getEmail(), password); - MvcResult mvcResult = getClient(token).perform(post("/api/core/items") + MvcResult mvcResult = getClient(token).perform(post("/api/core/items?owningCollection=" + + col1.getID().toString()) .content(mapper.writeValueAsBytes(itemRest)) .contentType(contentType)) .andExpect(status().isCreated()) @@ -1731,7 +1731,6 @@ public class ItemRestRepositoryIT extends AbstractControllerIntegrationTest { ItemRest itemRest = new ItemRest(); itemRest.setName("Practices of research data curation in institutional repositories:" + " A qualitative view from repository staff"); - itemRest.setOwningCollectionUuid(col1.getID().toString()); itemRest.setInArchive(true); itemRest.setDiscoverable(true); itemRest.setWithdrawn(false); @@ -1763,7 +1762,8 @@ public class ItemRestRepositoryIT extends AbstractControllerIntegrationTest { title)); String token = getAuthToken(admin.getEmail(), password); - MvcResult mvcResult = getClient(token).perform(post("/api/core/items") + MvcResult mvcResult = getClient(token).perform(post("/api/core/items?owningCollection=" + + col1.getID().toString()) .content(mapper.writeValueAsBytes(itemRest)) .contentType(contentType)) .andExpect(status().isCreated()) From 05cb94965547002272bc372ea918da23264e6e81 Mon Sep 17 00:00:00 2001 From: Kevin Van de Velde Date: Thu, 17 Jan 2019 15:42:43 +0100 Subject: [PATCH 35/68] Small changes in how exceptions are handled in the ItemRestRepository, DSpaceRestRepository classes --- .../app/rest/exception/UnprocessableEntityException.java | 4 ++++ .../org/dspace/app/rest/repository/ItemRestRepository.java | 4 ++-- .../test/java/org/dspace/app/rest/matcher/ItemMatcher.java | 2 -- 3 files changed, 6 insertions(+), 4 deletions(-) diff --git a/dspace-spring-rest/src/main/java/org/dspace/app/rest/exception/UnprocessableEntityException.java b/dspace-spring-rest/src/main/java/org/dspace/app/rest/exception/UnprocessableEntityException.java index aa2a9106a3..8bbd7c3909 100644 --- a/dspace-spring-rest/src/main/java/org/dspace/app/rest/exception/UnprocessableEntityException.java +++ b/dspace-spring-rest/src/main/java/org/dspace/app/rest/exception/UnprocessableEntityException.java @@ -27,6 +27,10 @@ import org.springframework.web.bind.annotation.ResponseStatus; @ResponseStatus(value = HttpStatus.UNPROCESSABLE_ENTITY, reason = "Unprocessable request") public class UnprocessableEntityException extends RuntimeException { + public UnprocessableEntityException(String message, Throwable cause) { + super(message, cause); + } + public UnprocessableEntityException(String message) { super(message); } diff --git a/dspace-spring-rest/src/main/java/org/dspace/app/rest/repository/ItemRestRepository.java b/dspace-spring-rest/src/main/java/org/dspace/app/rest/repository/ItemRestRepository.java index 3863ab2ced..4b70b06ac6 100644 --- a/dspace-spring-rest/src/main/java/org/dspace/app/rest/repository/ItemRestRepository.java +++ b/dspace-spring-rest/src/main/java/org/dspace/app/rest/repository/ItemRestRepository.java @@ -221,7 +221,7 @@ public class ItemRestRepository extends DSpaceRestRepository { ServletInputStream input = req.getInputStream(); itemRest = mapper.readValue(input, ItemRest.class); } catch (IOException e1) { - throw new UnprocessableEntityException("Error parsing request body: " + e1.toString()); + throw new UnprocessableEntityException("Error parsing request body", e1); } if (itemRest.getInArchive() == false) { @@ -257,7 +257,7 @@ public class ItemRestRepository extends DSpaceRestRepository { try { itemRest = mapper.readValue(jsonNode.toString(), ItemRest.class); } catch (IOException e1) { - throw new UnprocessableEntityException("Error parsing request body: " + e1.toString()); + throw new UnprocessableEntityException("Error parsing request body", e1); } Item item = itemService.find(context, uuid); diff --git a/dspace-spring-rest/src/test/java/org/dspace/app/rest/matcher/ItemMatcher.java b/dspace-spring-rest/src/test/java/org/dspace/app/rest/matcher/ItemMatcher.java index 28850fc6ff..e5176eea26 100644 --- a/dspace-spring-rest/src/test/java/org/dspace/app/rest/matcher/ItemMatcher.java +++ b/dspace-spring-rest/src/test/java/org/dspace/app/rest/matcher/ItemMatcher.java @@ -64,6 +64,4 @@ public class ItemMatcher { ); } - - } From 78f7066c94d0e9e5dc92eaff67c9d8a6555d40c2 Mon Sep 17 00:00:00 2001 From: Raf Ponsaerts Date: Wed, 6 Feb 2019 16:19:10 +0100 Subject: [PATCH 36/68] [DS-4164] fixed the bug in the error handling --- .../org/dspace/app/rest/OpenSearchController.java | 13 +------------ 1 file changed, 1 insertion(+), 12 deletions(-) diff --git a/dspace-spring-rest/src/main/java/org/dspace/app/rest/OpenSearchController.java b/dspace-spring-rest/src/main/java/org/dspace/app/rest/OpenSearchController.java index ebba89fff9..37664d0f51 100644 --- a/dspace-spring-rest/src/main/java/org/dspace/app/rest/OpenSearchController.java +++ b/dspace-spring-rest/src/main/java/org/dspace/app/rest/OpenSearchController.java @@ -43,7 +43,6 @@ import org.dspace.discovery.SearchUtils; import org.dspace.discovery.configuration.DiscoveryConfiguration; import org.dspace.discovery.configuration.DiscoverySearchFilter; -import org.springframework.boot.autoconfigure.web.ErrorController; import org.springframework.stereotype.Controller; import org.springframework.ui.Model; import org.springframework.web.bind.annotation.GetMapping; @@ -60,7 +59,7 @@ import org.w3c.dom.Document; */ @Controller @RequestMapping("/opensearch") -public class OpenSearchController implements ErrorController { +public class OpenSearchController { private static final Logger log = Logger.getLogger(ScopeResolver.class); private static final String errorpath = "/error"; @@ -192,16 +191,6 @@ public class OpenSearchController implements ErrorController { } } - @RequestMapping(value = errorpath) - public String error() { - return "Error handling"; - } - - @Override - public String getErrorPath() { - return errorpath; - } - /** * Internal method for controller initialization */ From d489636641f067c999efbdc9d9bb09a14569e462 Mon Sep 17 00:00:00 2001 From: Terry Brady Date: Wed, 6 Feb 2019 10:24:35 -0800 Subject: [PATCH 37/68] Migrate PR2307 --- .dockerignore | 1 + Dockerfile.dependencies | 24 +++++++++++++++++++++ Dockerfile.jdk8 | 34 ++++++++++++++++-------------- Dockerfile.jdk8-test | 46 ++++++++++++++++++++++------------------- 4 files changed, 68 insertions(+), 37 deletions(-) create mode 100644 Dockerfile.dependencies diff --git a/.dockerignore b/.dockerignore index 670bcb2e25..762ee391c2 100644 --- a/.dockerignore +++ b/.dockerignore @@ -3,3 +3,4 @@ .settings/ */target/ dspace/modules/*/target/ +Dockerfile.* \ No newline at end of file diff --git a/Dockerfile.dependencies b/Dockerfile.dependencies new file mode 100644 index 0000000000..a7107d46b5 --- /dev/null +++ b/Dockerfile.dependencies @@ -0,0 +1,24 @@ +# This image will be published as dspace/dspace-dependencies +# The purpose of this image is to make the build for dspace/dspace run faster + +# Step 1 - Run Maven Build +FROM maven:3-jdk-8 as build +ARG TARGET_DIR=dspace-installer +WORKDIR /app + +RUN useradd dspace \ + && mkdir /home/dspace \ + && chown -Rv dspace: /home/dspace +USER dspace + +# Copy the DSpace source code into the workdir (excluding .dockerignore contents) +ADD --chown=dspace . /app/ +COPY dspace/src/main/docker/local.cfg /app/local.cfg + +# Trigger the installation of all maven dependencies +# Clean up the built artifacts in the same step to keep the docker image small +RUN mvn package && mvn clean + +# Clear the contents of the /app directory so no artifacts are left when dspace:dspace is built +USER root +RUN rm -rf /app/* \ No newline at end of file diff --git a/Dockerfile.jdk8 b/Dockerfile.jdk8 index ec7084f49c..ec465b50b3 100644 --- a/Dockerfile.jdk8 +++ b/Dockerfile.jdk8 @@ -9,20 +9,30 @@ # - default tag for branch: dspace/dspace: dspace/dspace:dspace-7_x-jdk8 # Step 1 - Run Maven Build -FROM maven:3-jdk-8 as build +FROM dspace/dspace-dependencies:dspace-7_x as build +ARG TARGET_DIR=dspace-installer WORKDIR /app +# The dspace-install directory will be written to /install +RUN mkdir /install \ + && chown -Rv dspace: /install + +USER dspace + # Copy the DSpace source code into the workdir (excluding .dockerignore contents) -ADD . /app/ +ADD --chown=dspace . /app/ COPY dspace/src/main/docker/local.cfg /app/local.cfg -RUN mvn package +# Build DSpace. Copy the dspace-install directory to /install. Clean up the build to keep the docker image small +RUN mvn package && \ + mv /app/dspace/target/${TARGET_DIR}/* /install && \ + mvn clean # Step 2 - Run Ant Deploy FROM tomcat:8-jre8 as ant_build ARG TARGET_DIR=dspace-installer -COPY --from=build /app /dspace-src -WORKDIR /dspace-src/dspace/target/${TARGET_DIR} +COPY --from=build /install /dspace-src +WORKDIR /dspace-src # Create the initial install deployment using ANT ENV ANT_VERSION 1.10.5 @@ -32,23 +42,15 @@ ENV PATH $ANT_HOME/bin:$PATH RUN mkdir $ANT_HOME && \ wget -qO- "https://www.apache.org/dist/ant/binaries/apache-ant-$ANT_VERSION-bin.tar.gz" | tar -zx --strip-components=1 -C $ANT_HOME -RUN ant update_configs update_code update_webapps update_solr_indexes +RUN ant init_installation update_configs update_code update_webapps update_solr_indexes # Step 3 - Run tomcat # Create a new tomcat image that does not retain the the build directory contents FROM tomcat:8-jre8 -COPY --from=ant_build /dspace /dspace +ENV DSPACE_INSTALL=/dspace +COPY --from=ant_build /dspace $DSPACE_INSTALL EXPOSE 8080 8009 -# Ant will be embedded in the final container to allow additional deployments -ENV ANT_VERSION 1.10.5 -ENV ANT_HOME /tmp/ant-$ANT_VERSION -ENV PATH $ANT_HOME/bin:$PATH - -RUN mkdir $ANT_HOME && \ - wget -qO- "https://www.apache.org/dist/ant/binaries/apache-ant-$ANT_VERSION-bin.tar.gz" | tar -zx --strip-components=1 -C $ANT_HOME - -ENV DSPACE_INSTALL=/dspace ENV JAVA_OPTS=-Xmx2000m RUN ln -s $DSPACE_INSTALL/webapps/solr /usr/local/tomcat/webapps/solr && \ diff --git a/Dockerfile.jdk8-test b/Dockerfile.jdk8-test index 94d85a9e8f..e4f398d867 100644 --- a/Dockerfile.jdk8-test +++ b/Dockerfile.jdk8-test @@ -5,28 +5,34 @@ # - tomcat:8-jre8 # - ANT 1.10.5 # - maven:3-jdk-8 -# - note: expose /solr to any host; provide /rest over http +# - note: # - default tag for branch: dspace/dspace: dspace/dspace:dspace-7_x-jdk8-test # Step 1 - Run Maven Build -FROM maven:3-jdk-8 as build +FROM dspace/dspace-dependencies:dspace-7_x as build +ARG TARGET_DIR=dspace-installer WORKDIR /app +# The dspace-install directory will be written to /install +RUN mkdir /install \ + && chown -Rv dspace: /install + +USER dspace + # Copy the DSpace source code into the workdir (excluding .dockerignore contents) -ADD . /app/ +ADD --chown=dspace . /app/ COPY dspace/src/main/docker/local.cfg /app/local.cfg -# Provide web.xml overrides to make webapps easier to test -COPY dspace/src/main/docker/test/solr_web.xml /app/dspace-solr/src/main/webapp/WEB-INF/web.xml -COPY dspace/src/main/docker/test/rest_web.xml /app/dspace-rest/src/main/webapp/WEB-INF/web.xml - -RUN mvn package +# Build DSpace. Copy the dspace-install directory to /install. Clean up the build to keep the docker image small +RUN mvn package && \ + mv /app/dspace/target/${TARGET_DIR}/* /install && \ + mvn clean # Step 2 - Run Ant Deploy FROM tomcat:8-jre8 as ant_build ARG TARGET_DIR=dspace-installer -COPY --from=build /app /dspace-src -WORKDIR /dspace-src/dspace/target/${TARGET_DIR} +COPY --from=build /install /dspace-src +WORKDIR /dspace-src # Create the initial install deployment using ANT ENV ANT_VERSION 1.10.5 @@ -36,23 +42,15 @@ ENV PATH $ANT_HOME/bin:$PATH RUN mkdir $ANT_HOME && \ wget -qO- "https://www.apache.org/dist/ant/binaries/apache-ant-$ANT_VERSION-bin.tar.gz" | tar -zx --strip-components=1 -C $ANT_HOME -RUN ant update_configs update_code update_webapps update_solr_indexes +RUN ant init_installation update_configs update_code update_webapps update_solr_indexes # Step 3 - Run tomcat # Create a new tomcat image that does not retain the the build directory contents FROM tomcat:8-jre8 -COPY --from=ant_build /dspace /dspace +ENV DSPACE_INSTALL=/dspace +COPY --from=ant_build /dspace $DSPACE_INSTALL EXPOSE 8080 8009 -# Ant will be embedded in the final container to allow additional deployments -ENV ANT_VERSION 1.10.5 -ENV ANT_HOME /tmp/ant-$ANT_VERSION -ENV PATH $ANT_HOME/bin:$PATH - -RUN mkdir $ANT_HOME && \ - wget -qO- "https://www.apache.org/dist/ant/binaries/apache-ant-$ANT_VERSION-bin.tar.gz" | tar -zx --strip-components=1 -C $ANT_HOME - -ENV DSPACE_INSTALL=/dspace ENV JAVA_OPTS=-Xmx2000m RUN ln -s $DSPACE_INSTALL/webapps/solr /usr/local/tomcat/webapps/solr && \ @@ -62,3 +60,9 @@ RUN ln -s $DSPACE_INSTALL/webapps/solr /usr/local/tomcat/webapps/solr ln -s $DSPACE_INSTALL/webapps/rdf /usr/local/tomcat/webapps/rdf && \ ln -s $DSPACE_INSTALL/webapps/sword /usr/local/tomcat/webapps/sword && \ ln -s $DSPACE_INSTALL/webapps/swordv2 /usr/local/tomcat/webapps/swordv2 + +COPY dspace/src/main/docker/test/solr_web.xml $DSPACE_INSTALL/webapps/solr/WEB-INF/web.xml +COPY dspace/src/main/docker/test/rest_web.xml $DSPACE_INSTALL/webapps/rest/WEB-INF/web.xml + +RUN sed -i -e "s|\${dspace.dir}|$DSPACE_INSTALL|" $DSPACE_INSTALL/webapps/solr/WEB-INF/web.xml && \ + sed -i -e "s|\${dspace.dir}|$DSPACE_INSTALL|" $DSPACE_INSTALL/webapps/rest/WEB-INF/web.xml From d90e60a92b6109b0596e13e260cdce4879ee42bb Mon Sep 17 00:00:00 2001 From: Terry Brady Date: Wed, 6 Feb 2019 15:28:53 -0800 Subject: [PATCH 38/68] Sync docker solr web.xml with web.xml --- dspace/src/main/docker/test/solr_web.xml | 58 ++++++++++++++---------- 1 file changed, 34 insertions(+), 24 deletions(-) diff --git a/dspace/src/main/docker/test/solr_web.xml b/dspace/src/main/docker/test/solr_web.xml index 4329317c53..50a8bd5b9a 100644 --- a/dspace/src/main/docker/test/solr_web.xml +++ b/dspace/src/main/docker/test/solr_web.xml @@ -1,6 +1,4 @@ - - + - - - - solr/home ${dspace.dir}/solr java.lang.String - + - log4j.configuration - ${dspace.dir}/config/log4j-solr.properties - URL locating a Log4J configuration file (properties or XML). + + URL locating a Log4J configuration file (properties or XML). + + log4jConfiguration + ${dspace.dir}/config/log4j-solr.xml + + org.apache.logging.log4j.web.Log4jServletContextListener + + + + Activate logging + log4jServletFilter + org.apache.logging.log4j.web.Log4jServletFilter + + LocalHostRestrictionFilter @@ -87,7 +92,16 @@ --> - - - org.dspace.solr.filters.ConfigureLog4jListener - - Zookeeper org.apache.solr.servlet.ZookeeperInfoServlet From aa4125c9e9bb31b946ea11d6f676f3439447e983 Mon Sep 17 00:00:00 2001 From: Raf Ponsaerts Date: Thu, 7 Feb 2019 15:39:15 +0100 Subject: [PATCH 39/68] Removed the name check from the DspaceObjectRestEqualityUtils --- .../dspace/app/rest/utils/DSpaceObjectRestEqualityUtils.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/dspace-spring-rest/src/main/java/org/dspace/app/rest/utils/DSpaceObjectRestEqualityUtils.java b/dspace-spring-rest/src/main/java/org/dspace/app/rest/utils/DSpaceObjectRestEqualityUtils.java index b7d6e37b1b..c71eb5bf85 100644 --- a/dspace-spring-rest/src/main/java/org/dspace/app/rest/utils/DSpaceObjectRestEqualityUtils.java +++ b/dspace-spring-rest/src/main/java/org/dspace/app/rest/utils/DSpaceObjectRestEqualityUtils.java @@ -27,8 +27,7 @@ public class DSpaceObjectRestEqualityUtils { public boolean isDSpaceObjectEqualsWithoutMetadata(DSpaceObjectRest original, DSpaceObjectRest updated) { return StringUtils.equals(original.getId(), updated.getId()) && StringUtils.equals(original.getCategory(), updated.getCategory()) && - StringUtils.equals(original.getHandle(), updated.getHandle()) && - StringUtils.equals(original.getName(), updated.getName()); + StringUtils.equals(original.getHandle(), updated.getHandle()); } } From 42942978a801a0425c52461d364944a3ddce2cf5 Mon Sep 17 00:00:00 2001 From: Samuel Date: Wed, 12 Dec 2018 14:31:54 +0100 Subject: [PATCH 40/68] Added support for the CRUD operations on the MetadataField and MetadataSchema REST endpoints --- .../content/MetadataSchemaServiceImpl.java | 12 +- .../app/rest/RestResourceController.java | 8 + .../app/rest/model/MetadataFieldRest.java | 2 + .../app/rest/model/MetadataSchemaRest.java | 2 + .../MetadataFieldRestRepository.java | 137 +++++++++- .../MetadataSchemaRestRepository.java | 118 ++++++++- .../rest/MetadataSchemaRestRepositoryIT.java | 235 ++++++++++++++++-- .../rest/MetadatafieldRestRepositoryIT.java | 229 ++++++++++++++++- .../rest/matcher/MetadataschemaMatcher.java | 12 +- 9 files changed, 724 insertions(+), 31 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/content/MetadataSchemaServiceImpl.java b/dspace-api/src/main/java/org/dspace/content/MetadataSchemaServiceImpl.java index 185addfb01..d5c2c22f88 100644 --- a/dspace-api/src/main/java/org/dspace/content/MetadataSchemaServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/content/MetadataSchemaServiceImpl.java @@ -14,6 +14,7 @@ import org.apache.logging.log4j.Logger; import org.dspace.authorize.AuthorizeException; import org.dspace.authorize.service.AuthorizeService; import org.dspace.content.dao.MetadataSchemaDAO; +import org.dspace.content.service.MetadataFieldService; import org.dspace.content.service.MetadataSchemaService; import org.dspace.core.Context; import org.dspace.core.LogManager; @@ -33,6 +34,9 @@ public class MetadataSchemaServiceImpl implements MetadataSchemaService { */ private static Logger log = org.apache.logging.log4j.LogManager.getLogger(MetadataSchemaServiceImpl.class); + @Autowired + protected MetadataFieldService metadataFieldService; + @Autowired(required = true) protected AuthorizeService authorizeService; @@ -115,10 +119,14 @@ public class MetadataSchemaServiceImpl implements MetadataSchemaService { "Only administrators may modify the metadata registry"); } - log.info(LogManager.getHeader(context, "delete_metadata_schema", - "metadata_schema_id=" + metadataSchema.getID())); + for (MetadataField metadataField : metadataFieldService.findAllInSchema(context, metadataSchema)) { + metadataFieldService.delete(context, metadataField); + } metadataSchemaDAO.delete(context, metadataSchema); + + log.info(LogManager.getHeader(context, "delete_metadata_schema", + "metadata_schema_id=" + metadataSchema.getID())); } @Override diff --git a/dspace-spring-rest/src/main/java/org/dspace/app/rest/RestResourceController.java b/dspace-spring-rest/src/main/java/org/dspace/app/rest/RestResourceController.java index dea2395731..1e4631cec3 100644 --- a/dspace-spring-rest/src/main/java/org/dspace/app/rest/RestResourceController.java +++ b/dspace-spring-rest/src/main/java/org/dspace/app/rest/RestResourceController.java @@ -987,6 +987,14 @@ public class RestResourceController implements InitializingBean { } + @RequestMapping(method = RequestMethod.PUT, value = REGEX_REQUESTMAPPING_IDENTIFIER_AS_DIGIT) + public DSpaceResource put(HttpServletRequest request, + @PathVariable String apiCategory, @PathVariable String model, + @PathVariable Integer id, + @RequestBody(required = true) JsonNode jsonNode) { + return putOneInternal(request, apiCategory, model, id, jsonNode); + } + /** * Execute a PUT request for an entity with id of type UUID; * diff --git a/dspace-spring-rest/src/main/java/org/dspace/app/rest/model/MetadataFieldRest.java b/dspace-spring-rest/src/main/java/org/dspace/app/rest/model/MetadataFieldRest.java index 51fafd52d5..3fc017db01 100644 --- a/dspace-spring-rest/src/main/java/org/dspace/app/rest/model/MetadataFieldRest.java +++ b/dspace-spring-rest/src/main/java/org/dspace/app/rest/model/MetadataFieldRest.java @@ -8,6 +8,7 @@ package org.dspace.app.rest.model; import com.fasterxml.jackson.annotation.JsonIgnore; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import org.dspace.app.rest.RestResourceController; /** @@ -15,6 +16,7 @@ import org.dspace.app.rest.RestResourceController; * * @author Andrea Bollini (andrea.bollini at 4science.it) */ +@JsonIgnoreProperties(ignoreUnknown = true) public class MetadataFieldRest extends BaseObjectRest { public static final String NAME = "metadatafield"; public static final String CATEGORY = RestAddressableModel.CORE; diff --git a/dspace-spring-rest/src/main/java/org/dspace/app/rest/model/MetadataSchemaRest.java b/dspace-spring-rest/src/main/java/org/dspace/app/rest/model/MetadataSchemaRest.java index c6244af87e..649e2e6e2d 100644 --- a/dspace-spring-rest/src/main/java/org/dspace/app/rest/model/MetadataSchemaRest.java +++ b/dspace-spring-rest/src/main/java/org/dspace/app/rest/model/MetadataSchemaRest.java @@ -7,6 +7,7 @@ */ package org.dspace.app.rest.model; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import org.dspace.app.rest.RestResourceController; /** @@ -14,6 +15,7 @@ import org.dspace.app.rest.RestResourceController; * * @author Andrea Bollini (andrea.bollini at 4science.it) */ +@JsonIgnoreProperties(ignoreUnknown = true) public class MetadataSchemaRest extends BaseObjectRest { public static final String NAME = "metadataschema"; public static final String CATEGORY = RestAddressableModel.CORE; diff --git a/dspace-spring-rest/src/main/java/org/dspace/app/rest/repository/MetadataFieldRestRepository.java b/dspace-spring-rest/src/main/java/org/dspace/app/rest/repository/MetadataFieldRestRepository.java index dddb62b5da..44c5ef0723 100644 --- a/dspace-spring-rest/src/main/java/org/dspace/app/rest/repository/MetadataFieldRestRepository.java +++ b/dspace-spring-rest/src/main/java/org/dspace/app/rest/repository/MetadataFieldRestRepository.java @@ -7,22 +7,37 @@ */ package org.dspace.app.rest.repository; +import static java.lang.Integer.parseInt; +import static org.apache.commons.lang.StringUtils.isBlank; + +import java.io.IOException; import java.sql.SQLException; import java.util.List; +import java.util.Objects; +import javax.servlet.http.HttpServletRequest; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.google.gson.Gson; import org.dspace.app.rest.Parameter; import org.dspace.app.rest.SearchRestMethod; import org.dspace.app.rest.converter.MetadataFieldConverter; +import org.dspace.app.rest.exception.PatchBadRequestException; +import org.dspace.app.rest.exception.UnprocessableEntityException; import org.dspace.app.rest.model.MetadataFieldRest; import org.dspace.app.rest.model.hateoas.MetadataFieldResource; +import org.dspace.authorize.AuthorizeException; import org.dspace.content.MetadataField; import org.dspace.content.MetadataSchema; +import org.dspace.content.NonUniqueMetadataException; import org.dspace.content.service.MetadataFieldService; import org.dspace.content.service.MetadataSchemaService; import org.dspace.core.Context; 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; /** @@ -34,7 +49,7 @@ import org.springframework.stereotype.Component; public class MetadataFieldRestRepository extends DSpaceRestRepository { @Autowired - MetadataFieldService metaFieldService; + MetadataFieldService metadataFieldService; @Autowired MetadataSchemaService metadataSchemaService; @@ -49,7 +64,7 @@ public class MetadataFieldRestRepository extends DSpaceRestRepository findAll(Context context, Pageable pageable) { List metadataField = null; try { - metadataField = metaFieldService.findAll(context); + metadataField = metadataFieldService.findAll(context); } catch (SQLException e) { throw new RuntimeException(e.getMessage(), e); } @@ -73,7 +88,7 @@ public class MetadataFieldRestRepository extends DSpaceRestRepository findBySchema(@Parameter(value = "schema", required = true) String schemaName, - Pageable pageable) { + Pageable pageable) { Context context = obtainContext(); List metadataFields = null; try { @@ -81,7 +96,7 @@ public class MetadataFieldRestRepository extends DSpaceRestRepository { @Autowired - MetadataSchemaService metaScemaService; + MetadataSchemaService metadataSchemaService; @Autowired MetadataSchemaConverter converter; @@ -42,7 +56,7 @@ public class MetadataSchemaRestRepository extends DSpaceRestRepository findAll(Context context, Pageable pageable) { List metadataSchema = null; try { - metadataSchema = metaScemaService.findAll(context); + metadataSchema = metadataSchemaService.findAll(context); } catch (SQLException e) { throw new RuntimeException(e.getMessage(), e); } @@ -73,4 +87,102 @@ public class MetadataSchemaRestRepository extends DSpaceRestRepository idRef = new AtomicReference<>(); + + try { + + assertThat(metadataSchemaService.find(context, TEST_NAME), nullValue()); + + getClient(authToken) + .perform(post("/api/core/metadataschemas") + .content(new ObjectMapper().writeValueAsBytes(metadataSchemaRest)) + .contentType(contentType)) + .andExpect(status().isCreated()) + .andDo(result -> idRef.set(read(result.getResponse().getContentAsString(), "$.id"))); + + MetadataSchema metadataSchema = metadataSchemaService.find(context, idRef.get()); + assertThat(metadataSchema, notNullValue()); + + assertEquals(metadataSchema.getID(), idRef.get()); + assertEquals(metadataSchema.getName(), TEST_NAME); + assertEquals(metadataSchema.getNamespace(), TEST_NAMESPACE); + + } finally { + deleteMetadataSchemaIfExists(TEST_NAME); + } + } + + @Test + public void createUnauthauthorizedTest() + throws Exception { + + MetadataSchemaRest metadataSchemaRest = new MetadataSchemaRest(); + metadataSchemaRest.setPrefix(TEST_NAME); + metadataSchemaRest.setNamespace(TEST_NAMESPACE); + + try { + getClient() + .perform(post("/api/core/metadataschemas") + .content(new ObjectMapper().writeValueAsBytes(metadataSchemaRest)) + .contentType(contentType)) + .andExpect(status().isUnauthorized()); + + } finally { + deleteMetadataSchemaIfExists(TEST_NAME); + } + } + + @Test + public void deleteSuccess() throws Exception { + + MetadataSchema metadataSchema = createMetadataSchema(); + + try { + + assertThat(metadataSchemaService.find(context, metadataSchema.getID()), notNullValue()); + + getClient(getAuthToken(admin.getEmail(), password)) + .perform(delete("/api/core/metadataschemas/" + metadataSchema.getID())) + .andExpect(status().isNoContent()); + + assertThat(metadataSchemaService.find(context, metadataSchema.getID()), nullValue()); + + } finally { + deleteMetadataSchemaIfExists(TEST_NAME); + } + } + + @Test + public void deleteUnauthorized() throws Exception { + + MetadataSchema metadataSchema = createMetadataSchema(); + + try { + + assertThat(metadataSchemaService.find(context, metadataSchema.getID()), notNullValue()); + + getClient() + .perform(delete("/api/core/metadataschemas/" + metadataSchema.getID())) + .andExpect(status().isUnauthorized()); + + assertThat(metadataSchemaService.find(context, metadataSchema.getID()), notNullValue()); + + } finally { + deleteMetadataSchemaIfExists(TEST_NAME); + } + } + + @Test + public void deleteNonExisting() throws Exception { + + MetadataSchema metadataSchema = createMetadataSchema(); + deleteMetadataSchemaIfExists(TEST_NAME); + + Integer id = metadataSchema.getID(); + assertThat(metadataSchemaService.find(context, id), nullValue()); + + getClient(getAuthToken(admin.getEmail(), password)) + .perform(delete("/api/core/metadataschemas/" + id)) + .andExpect(status().isNotFound()); + } + + @Test + public void update() throws Exception { + + MetadataSchema metadataSchema = createMetadataSchema(); + + MetadataSchemaRest metadataSchemaRest = new MetadataSchemaRest(); + metadataSchemaRest.setId(metadataSchema.getID()); + metadataSchemaRest.setPrefix(TEST_NAME_UPDATED); + metadataSchemaRest.setNamespace(TEST_NAMESPACE_UPDATED); + + try { + getClient(getAuthToken(admin.getEmail(), password)) + .perform(put("/api/core/metadataschemas/" + metadataSchema.getID()) + .content(new ObjectMapper().writeValueAsBytes(metadataSchemaRest)) + .contentType(contentType)) + .andExpect(status().isOk()); + + metadataSchema = metadataSchemaService.find(context, metadataSchema.getID()); + + assertEquals(TEST_NAME_UPDATED, metadataSchema.getName()); + assertEquals(TEST_NAMESPACE_UPDATED, metadataSchema.getNamespace()); + } finally { + deleteMetadataSchemaIfExists(metadataSchema); + } + } + + @Test + public void updateUnauthorized() throws Exception { + + MetadataSchema metadataSchema = createMetadataSchema(); + + MetadataSchemaRest metadataSchemaRest = new MetadataSchemaRest(); + metadataSchemaRest.setId(metadataSchema.getID()); + metadataSchemaRest.setPrefix(TEST_NAME_UPDATED); + metadataSchemaRest.setNamespace(TEST_NAMESPACE_UPDATED); + + try { + getClient() + .perform(put("/api/core/metadataschemas/" + metadataSchema.getID()) + .content(new ObjectMapper().writeValueAsBytes(metadataSchemaRest)) + .contentType(contentType)) + .andExpect(status().isUnauthorized()); + + metadataSchema = metadataSchemaService.find(context, metadataSchema.getID()); + + assertEquals(TEST_NAME, metadataSchema.getName()); + assertEquals(TEST_NAMESPACE, metadataSchema.getNamespace()); + } finally { + deleteMetadataSchemaIfExists(metadataSchema); + } + } + + private MetadataSchema createMetadataSchema() throws SQLException, AuthorizeException, NonUniqueMetadataException { + context.turnOffAuthorisationSystem(); + MetadataSchema metadataSchema = metadataSchemaService.create(context, TEST_NAME, TEST_NAMESPACE); + context.commit(); + return metadataSchema; + } + + private void deleteMetadataSchemaIfExists(String name) throws SQLException, AuthorizeException { + + deleteMetadataSchemaIfExists(metadataSchemaService.find(context, name)); + } + + private void deleteMetadataSchemaIfExists(MetadataSchema metadataSchema) throws SQLException, AuthorizeException { + + if (metadataSchema != null) { + context.turnOffAuthorisationSystem(); + metadataSchemaService.delete(context, metadataSchema); + context.commit(); + } } } diff --git a/dspace-spring-rest/src/test/java/org/dspace/app/rest/MetadatafieldRestRepositoryIT.java b/dspace-spring-rest/src/test/java/org/dspace/app/rest/MetadatafieldRestRepositoryIT.java index 7217030ab3..b207dcd655 100644 --- a/dspace-spring-rest/src/test/java/org/dspace/app/rest/MetadatafieldRestRepositoryIT.java +++ b/dspace-spring-rest/src/test/java/org/dspace/app/rest/MetadatafieldRestRepositoryIT.java @@ -7,23 +7,62 @@ */ package org.dspace.app.rest; +import static com.jayway.jsonpath.JsonPath.read; import static org.hamcrest.Matchers.is; +import static org.hamcrest.Matchers.notNullValue; +import static org.hamcrest.Matchers.nullValue; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertThat; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.delete; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.put; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; +import java.sql.SQLException; +import java.util.concurrent.atomic.AtomicReference; + +import com.fasterxml.jackson.databind.ObjectMapper; import org.dspace.app.rest.builder.MetadataFieldBuilder; import org.dspace.app.rest.builder.MetadataSchemaBuilder; import org.dspace.app.rest.matcher.MetadataFieldMatcher; +import org.dspace.app.rest.model.MetadataFieldRest; import org.dspace.app.rest.test.AbstractControllerIntegrationTest; +import org.dspace.authorize.AuthorizeException; import org.dspace.content.MetadataField; +import org.dspace.content.MetadataFieldServiceImpl; import org.dspace.content.MetadataSchema; +import org.dspace.content.NonUniqueMetadataException; +import org.dspace.content.service.MetadataSchemaService; import org.hamcrest.Matchers; +import org.junit.Before; import org.junit.Test; +import org.springframework.beans.factory.annotation.Autowired; public class MetadatafieldRestRepositoryIT extends AbstractControllerIntegrationTest { + private static final String ELEMENT = "test element"; + private static final String QUALIFIER = "test qualifier"; + private static final String SCOPE_NOTE = "test scope_note"; + + private static final String ELEMENT_UPDATED = "test element updated"; + private static final String QUALIFIER_UPDATED = "test qualifier updated"; + private static final String SCOPE_NOTE_UPDATED = "test scope_note updated"; + + private MetadataSchema metadataSchema; + + @Autowired + private MetadataSchemaService metadataSchemaService; + + @Autowired + private MetadataFieldServiceImpl metadataFieldService; + + @Before + public void setup() throws Exception { + metadataSchema = metadataSchemaService.findAll(context).get(0); + } @Test public void findAll() throws Exception { @@ -66,12 +105,12 @@ public class MetadatafieldRestRepositoryIT extends AbstractControllerIntegration @Test public void searchMethodsExist() throws Exception { getClient().perform(get("/api/core/metadatafields")) - .andExpect(jsonPath("$._links.search.href", Matchers.notNullValue())); + .andExpect(jsonPath("$._links.search.href", notNullValue())); getClient().perform(get("/api/core/metadatafields/search")) .andExpect(status().isOk()) .andExpect(content().contentType(contentType)) - .andExpect(jsonPath("$._links.bySchema", Matchers.notNullValue())); + .andExpect(jsonPath("$._links.bySchema", notNullValue())); } @Test @@ -124,4 +163,190 @@ public class MetadatafieldRestRepositoryIT extends AbstractControllerIntegration .andExpect(status().isUnprocessableEntity()); } + @Test + public void createSuccess() throws Exception { + + MetadataFieldRest metadataFieldRest = new MetadataFieldRest(); + metadataFieldRest.setElement(ELEMENT); + metadataFieldRest.setQualifier(QUALIFIER); + metadataFieldRest.setScopeNote(SCOPE_NOTE); + + String authToken = getAuthToken(admin.getEmail(), password); + AtomicReference idRef = new AtomicReference<>(); + + try { + assertThat(metadataFieldService.findByElement(context, metadataSchema, ELEMENT, QUALIFIER), nullValue()); + + getClient(authToken) + .perform(post("/api/core/metadatafields") + .param("schemaId", metadataSchema.getID() + "") + .content(new ObjectMapper().writeValueAsBytes(metadataFieldRest)) + .contentType(contentType)) + .andExpect(status().isCreated()) + .andDo(result -> idRef.set(read(result.getResponse().getContentAsString(), "$.id"))); + + MetadataField metadataField = metadataFieldService.find(context, idRef.get()); + assertThat(metadataField, notNullValue()); + + assertEquals(metadataField.getMetadataSchema(), metadataSchema); + assertEquals(metadataField.getElement(), ELEMENT); + assertEquals(metadataField.getQualifier(), QUALIFIER); + assertEquals(metadataField.getScopeNote(), SCOPE_NOTE); + + } finally { + deleteMetadataFieldIfExists(); + } + } + + @Test + public void createUnauthauthorized() throws Exception { + + MetadataFieldRest metadataFieldRest = new MetadataFieldRest(); + metadataFieldRest.setElement(ELEMENT); + metadataFieldRest.setQualifier(QUALIFIER); + metadataFieldRest.setScopeNote(SCOPE_NOTE); + + try { + getClient() + .perform(post("/api/core/metadatafields") + .param("schemaId", metadataSchema.getID() + "") + .content(new ObjectMapper().writeValueAsBytes(metadataFieldRest)) + .contentType(contentType)) + .andExpect(status().isUnauthorized()); + } finally { + deleteMetadataFieldIfExists(); + } + } + + @Test + public void deleteSuccess() throws Exception { + + MetadataField metadataField = createMetadataField(); + + try { + + assertThat(metadataFieldService.find(context, metadataField.getID()), notNullValue()); + + getClient(getAuthToken(admin.getEmail(), password)) + .perform(delete("/api/core/metadatafields/" + metadataField.getID())) + .andExpect(status().isNoContent()); + + assertThat(metadataFieldService.find(context, metadataField.getID()), nullValue()); + + } finally { + deleteMetadataFieldIfExists(); + } + } + + @Test + public void deleteUnauthorized() throws Exception { + + MetadataField metadataField = createMetadataField(); + + try { + + assertThat(metadataFieldService.find(context, metadataField.getID()), notNullValue()); + + getClient() + .perform(delete("/api/core/metadatafields/" + metadataField.getID())) + .andExpect(status().isUnauthorized()); + + assertThat(metadataFieldService.find(context, metadataField.getID()), notNullValue()); + + } finally { + deleteMetadataFieldIfExists(); + } + } + + @Test + public void deleteNonExisting() throws Exception { + + MetadataField metadataField = createMetadataField(); + deleteMetadataFieldIfExists(); + + Integer id = metadataField.getID(); + assertThat(metadataFieldService.find(context, id), nullValue()); + + getClient(getAuthToken(admin.getEmail(), password)) + .perform(delete("/api/core/metadatafields/" + id)) + .andExpect(status().isNotFound()); + } + + @Test + public void update() throws Exception { + + MetadataField metadataField = createMetadataField(); + + MetadataFieldRest metadataFieldRest = new MetadataFieldRest(); + metadataFieldRest.setId(metadataField.getID()); + metadataFieldRest.setElement(ELEMENT_UPDATED); + metadataFieldRest.setQualifier(QUALIFIER_UPDATED); + metadataFieldRest.setScopeNote(SCOPE_NOTE_UPDATED); + + try { + getClient(getAuthToken(admin.getEmail(), password)) + .perform(put("/api/core/metadatafields/" + metadataField.getID()) + .content(new ObjectMapper().writeValueAsBytes(metadataFieldRest)) + .contentType(contentType)) + .andExpect(status().isOk()); + + metadataField = metadataFieldService.find(context, metadataField.getID()); + + assertEquals(ELEMENT_UPDATED, metadataField.getElement()); + assertEquals(QUALIFIER_UPDATED, metadataField.getQualifier()); + assertEquals(SCOPE_NOTE_UPDATED, metadataField.getScopeNote()); + } finally { + deleteMetadataFieldIfExists(metadataField); + } + } + + @Test + public void updateUnauthorized() throws Exception { + + MetadataField metadataField = createMetadataField(); + + MetadataFieldRest metadataFieldRest = new MetadataFieldRest(); + metadataFieldRest.setId(metadataField.getID()); + metadataFieldRest.setElement(ELEMENT_UPDATED); + metadataFieldRest.setQualifier(QUALIFIER_UPDATED); + metadataFieldRest.setScopeNote(SCOPE_NOTE_UPDATED); + + try { + getClient() + .perform(put("/api/core/metadatafields/" + metadataField.getID()) + .content(new ObjectMapper().writeValueAsBytes(metadataFieldRest)) + .contentType(contentType)) + .andExpect(status().isUnauthorized()); + + metadataField = metadataFieldService.find(context, metadataField.getID()); + + assertEquals(ELEMENT, metadataField.getElement()); + assertEquals(QUALIFIER, metadataField.getQualifier()); + assertEquals(SCOPE_NOTE, metadataField.getScopeNote()); + } finally { + deleteMetadataFieldIfExists(metadataField); + } + } + + private MetadataField createMetadataField() throws AuthorizeException, SQLException, NonUniqueMetadataException { + context.turnOffAuthorisationSystem(); + MetadataField metadataField = metadataFieldService.create( + context, metadataSchema, ELEMENT, QUALIFIER, SCOPE_NOTE + ); + context.commit(); + return metadataField; + } + + private void deleteMetadataFieldIfExists() throws SQLException, AuthorizeException { + + deleteMetadataFieldIfExists(metadataFieldService.findByElement(context, metadataSchema, ELEMENT, QUALIFIER)); + } + + private void deleteMetadataFieldIfExists(MetadataField metadataField) throws SQLException, AuthorizeException { + if (metadataField != null) { + context.turnOffAuthorisationSystem(); + metadataFieldService.delete(context, metadataField); + context.commit(); + } + } } diff --git a/dspace-spring-rest/src/test/java/org/dspace/app/rest/matcher/MetadataschemaMatcher.java b/dspace-spring-rest/src/test/java/org/dspace/app/rest/matcher/MetadataschemaMatcher.java index 18e8a5ad8d..42b70810a5 100644 --- a/dspace-spring-rest/src/test/java/org/dspace/app/rest/matcher/MetadataschemaMatcher.java +++ b/dspace-spring-rest/src/test/java/org/dspace/app/rest/matcher/MetadataschemaMatcher.java @@ -29,11 +29,15 @@ public class MetadataschemaMatcher { } public static Matcher matchEntry(MetadataSchema metadataSchema) { + return matchEntry(metadataSchema.getName(), metadataSchema.getNamespace()); + } + + public static Matcher matchEntry(String name, String nameSpace) { return allOf( - hasJsonPath("$.prefix", is(metadataSchema.getName())), - hasJsonPath("$.namespace", is(metadataSchema.getNamespace())), - hasJsonPath("$.type", is("metadataschema")), - hasJsonPath("$._links.self.href", Matchers.containsString("/api/core/metadataschemas")) + hasJsonPath("$.prefix", is(name)), + hasJsonPath("$.namespace", is(nameSpace)), + hasJsonPath("$.type", is("metadataschema")), + hasJsonPath("$._links.self.href", Matchers.containsString("/api/core/metadataschemas")) ); } } From 0a7a8aab8ec774a2efdf74baaf6086f90be1bcae Mon Sep 17 00:00:00 2001 From: Samuel Date: Thu, 13 Dec 2018 17:18:26 +0100 Subject: [PATCH 41/68] Added support for the CRUD operations on the MetadataField and MetadataSchema REST endpoints - feedback --- .../app/rest/RestResourceController.java | 24 +++++++++++++-- .../rest/repository/DSpaceRestRepository.java | 29 ++++++++++--------- .../MetadataFieldRestRepository.java | 6 +--- .../MetadataSchemaRestRepository.java | 6 +--- 4 files changed, 39 insertions(+), 26 deletions(-) diff --git a/dspace-spring-rest/src/main/java/org/dspace/app/rest/RestResourceController.java b/dspace-spring-rest/src/main/java/org/dspace/app/rest/RestResourceController.java index 1e4631cec3..e0c80ee66f 100644 --- a/dspace-spring-rest/src/main/java/org/dspace/app/rest/RestResourceController.java +++ b/dspace-spring-rest/src/main/java/org/dspace/app/rest/RestResourceController.java @@ -987,11 +987,31 @@ public class RestResourceController implements InitializingBean { } + + /** + * Execute a PUT request for an entity with id of type Integer; + * + * curl -X PUT http:///dspace-spring-rest/api/{apiCategory}/{model} + * + * Example: + *
+     * {@code
+     *      curl -X PUT http:///dspace-spring-rest/api/metadatafield
+     * }
+     * 
+ * + * @param request the http request + * @param apiCategory the API category e.g. "api" + * @param model the DSpace model e.g. "metadatafield" + * @param id the ID of the target REST object + * @param jsonNode the part of the request body representing the updated rest object + * @return the relevant REST resource + */ @RequestMapping(method = RequestMethod.PUT, value = REGEX_REQUESTMAPPING_IDENTIFIER_AS_DIGIT) public DSpaceResource put(HttpServletRequest request, @PathVariable String apiCategory, @PathVariable String model, @PathVariable Integer id, - @RequestBody(required = true) JsonNode jsonNode) { + @RequestBody JsonNode jsonNode) { return putOneInternal(request, apiCategory, model, id, jsonNode); } @@ -1018,7 +1038,7 @@ public class RestResourceController implements InitializingBean { public DSpaceResource put(HttpServletRequest request, @PathVariable String apiCategory, @PathVariable String model, @PathVariable UUID uuid, - @RequestBody(required = true) JsonNode jsonNode) { + @RequestBody JsonNode jsonNode) { return putOneInternal(request, apiCategory, model, uuid, jsonNode); } diff --git a/dspace-spring-rest/src/main/java/org/dspace/app/rest/repository/DSpaceRestRepository.java b/dspace-spring-rest/src/main/java/org/dspace/app/rest/repository/DSpaceRestRepository.java index 0be6f7475a..29e0085bb3 100644 --- a/dspace-spring-rest/src/main/java/org/dspace/app/rest/repository/DSpaceRestRepository.java +++ b/dspace-spring-rest/src/main/java/org/dspace/app/rest/repository/DSpaceRestRepository.java @@ -71,7 +71,7 @@ public abstract class DSpaceRestRepository Date: Thu, 10 Jan 2019 11:42:31 +0100 Subject: [PATCH 42/68] Added support for the CRUD operations on the MetadataField and MetadataSchema REST endpoints - feedback --- .../dspace/app/rest/exception/PatchBadRequestException.java | 5 ++++- .../app/rest/repository/MetadataFieldRestRepository.java | 4 ++-- .../app/rest/repository/MetadataSchemaRestRepository.java | 6 ++++-- 3 files changed, 10 insertions(+), 5 deletions(-) diff --git a/dspace-spring-rest/src/main/java/org/dspace/app/rest/exception/PatchBadRequestException.java b/dspace-spring-rest/src/main/java/org/dspace/app/rest/exception/PatchBadRequestException.java index 5c3328a1dd..2000684fb2 100644 --- a/dspace-spring-rest/src/main/java/org/dspace/app/rest/exception/PatchBadRequestException.java +++ b/dspace-spring-rest/src/main/java/org/dspace/app/rest/exception/PatchBadRequestException.java @@ -22,7 +22,10 @@ import org.springframework.web.bind.annotation.ResponseStatus; public class PatchBadRequestException extends RuntimeException { public PatchBadRequestException(String message) { - super(message); + this(message, null); } + public PatchBadRequestException(String message, Exception e) { + super(message, e); + } } diff --git a/dspace-spring-rest/src/main/java/org/dspace/app/rest/repository/MetadataFieldRestRepository.java b/dspace-spring-rest/src/main/java/org/dspace/app/rest/repository/MetadataFieldRestRepository.java index 502e626520..46f1644540 100644 --- a/dspace-spring-rest/src/main/java/org/dspace/app/rest/repository/MetadataFieldRestRepository.java +++ b/dspace-spring-rest/src/main/java/org/dspace/app/rest/repository/MetadataFieldRestRepository.java @@ -127,7 +127,7 @@ public class MetadataFieldRestRepository extends DSpaceRestRepository Date: Wed, 23 Jan 2019 15:34:55 -0600 Subject: [PATCH 43/68] Minor improvements to English in error messages. --- .../app/rest/repository/MetadataFieldRestRepository.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/dspace-spring-rest/src/main/java/org/dspace/app/rest/repository/MetadataFieldRestRepository.java b/dspace-spring-rest/src/main/java/org/dspace/app/rest/repository/MetadataFieldRestRepository.java index 46f1644540..c548c8604b 100644 --- a/dspace-spring-rest/src/main/java/org/dspace/app/rest/repository/MetadataFieldRestRepository.java +++ b/dspace-spring-rest/src/main/java/org/dspace/app/rest/repository/MetadataFieldRestRepository.java @@ -133,16 +133,16 @@ public class MetadataFieldRestRepository extends DSpaceRestRepository Date: Wed, 23 Jan 2019 15:37:46 -0600 Subject: [PATCH 44/68] More minor corrections to English --- .../app/rest/repository/MetadataFieldRestRepository.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/dspace-spring-rest/src/main/java/org/dspace/app/rest/repository/MetadataFieldRestRepository.java b/dspace-spring-rest/src/main/java/org/dspace/app/rest/repository/MetadataFieldRestRepository.java index c548c8604b..3b67acaf7c 100644 --- a/dspace-spring-rest/src/main/java/org/dspace/app/rest/repository/MetadataFieldRestRepository.java +++ b/dspace-spring-rest/src/main/java/org/dspace/app/rest/repository/MetadataFieldRestRepository.java @@ -191,16 +191,16 @@ public class MetadataFieldRestRepository extends DSpaceRestRepository Date: Wed, 23 Jan 2019 15:39:38 -0600 Subject: [PATCH 45/68] English grammar corrections --- .../repository/MetadataSchemaRestRepository.java | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/dspace-spring-rest/src/main/java/org/dspace/app/rest/repository/MetadataSchemaRestRepository.java b/dspace-spring-rest/src/main/java/org/dspace/app/rest/repository/MetadataSchemaRestRepository.java index 915a785e26..36d290e247 100644 --- a/dspace-spring-rest/src/main/java/org/dspace/app/rest/repository/MetadataSchemaRestRepository.java +++ b/dspace-spring-rest/src/main/java/org/dspace/app/rest/repository/MetadataSchemaRestRepository.java @@ -106,10 +106,10 @@ public class MetadataSchemaRestRepository extends DSpaceRestRepository Date: Thu, 7 Feb 2019 15:11:14 +0100 Subject: [PATCH 46/68] Applied feedback and fixed test cases --- .../app/rest/RestResourceController.java | 12 +- .../app/rest/model/MetadataFieldRest.java | 4 +- .../app/rest/model/MetadataSchemaRest.java | 4 +- .../rest/repository/DSpaceRestRepository.java | 7 +- .../rest/MetadataSchemaRestRepositoryIT.java | 191 ++++++-------- .../rest/MetadatafieldRestRepositoryIT.java | 237 +++++++++--------- 6 files changed, 213 insertions(+), 242 deletions(-) diff --git a/dspace-spring-rest/src/main/java/org/dspace/app/rest/RestResourceController.java b/dspace-spring-rest/src/main/java/org/dspace/app/rest/RestResourceController.java index e0c80ee66f..536379c696 100644 --- a/dspace-spring-rest/src/main/java/org/dspace/app/rest/RestResourceController.java +++ b/dspace-spring-rest/src/main/java/org/dspace/app/rest/RestResourceController.java @@ -991,17 +991,17 @@ public class RestResourceController implements InitializingBean { /** * Execute a PUT request for an entity with id of type Integer; * - * curl -X PUT http:///dspace-spring-rest/api/{apiCategory}/{model} + * curl -X PUT http:///api/{apiCategory}/{model}/{id} * * Example: *
      * {@code
-     *      curl -X PUT http:///dspace-spring-rest/api/metadatafield
+     *      curl -X PUT http:///api/core/metadatafield/1
      * }
      * 
* * @param request the http request - * @param apiCategory the API category e.g. "api" + * @param apiCategory the API category e.g. "core" * @param model the DSpace model e.g. "metadatafield" * @param id the ID of the target REST object * @param jsonNode the part of the request body representing the updated rest object @@ -1018,17 +1018,17 @@ public class RestResourceController implements InitializingBean { /** * Execute a PUT request for an entity with id of type UUID; * - * curl -X PUT http:///dspace-spring-rest/api/{apiCategory}/{model}/{uuid} + * curl -X PUT http:///api/{apiCategory}/{model}/{uuid} * * Example: *
      * {@code
-     *      curl -X PUT http:///dspace-spring-rest/api/collection/320c0492-de1d-4646-9e69-193d36b366e9
+     *      curl -X PUT http:///api/core/collection/8b632938-77c2-487c-81f0-e804f63e68e6
      * }
      * 
* * @param request the http request - * @param apiCategory the API category e.g. "api" + * @param apiCategory the API category e.g. "core" * @param model the DSpace model e.g. "collection" * @param uuid the ID of the target REST object * @param jsonNode the part of the request body representing the updated rest object diff --git a/dspace-spring-rest/src/main/java/org/dspace/app/rest/model/MetadataFieldRest.java b/dspace-spring-rest/src/main/java/org/dspace/app/rest/model/MetadataFieldRest.java index 3fc017db01..7e6eabc4d0 100644 --- a/dspace-spring-rest/src/main/java/org/dspace/app/rest/model/MetadataFieldRest.java +++ b/dspace-spring-rest/src/main/java/org/dspace/app/rest/model/MetadataFieldRest.java @@ -8,7 +8,7 @@ package org.dspace.app.rest.model; import com.fasterxml.jackson.annotation.JsonIgnore; -import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; import org.dspace.app.rest.RestResourceController; /** @@ -16,7 +16,6 @@ import org.dspace.app.rest.RestResourceController; * * @author Andrea Bollini (andrea.bollini at 4science.it) */ -@JsonIgnoreProperties(ignoreUnknown = true) public class MetadataFieldRest extends BaseObjectRest { public static final String NAME = "metadatafield"; public static final String CATEGORY = RestAddressableModel.CORE; @@ -63,6 +62,7 @@ public class MetadataFieldRest extends BaseObjectRest { } @Override + @JsonProperty(access = JsonProperty.Access.READ_ONLY) public String getType() { return NAME; } diff --git a/dspace-spring-rest/src/main/java/org/dspace/app/rest/model/MetadataSchemaRest.java b/dspace-spring-rest/src/main/java/org/dspace/app/rest/model/MetadataSchemaRest.java index 649e2e6e2d..27229ea429 100644 --- a/dspace-spring-rest/src/main/java/org/dspace/app/rest/model/MetadataSchemaRest.java +++ b/dspace-spring-rest/src/main/java/org/dspace/app/rest/model/MetadataSchemaRest.java @@ -7,7 +7,7 @@ */ package org.dspace.app.rest.model; -import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; import org.dspace.app.rest.RestResourceController; /** @@ -15,7 +15,6 @@ import org.dspace.app.rest.RestResourceController; * * @author Andrea Bollini (andrea.bollini at 4science.it) */ -@JsonIgnoreProperties(ignoreUnknown = true) public class MetadataSchemaRest extends BaseObjectRest { public static final String NAME = "metadataschema"; public static final String CATEGORY = RestAddressableModel.CORE; @@ -41,6 +40,7 @@ public class MetadataSchemaRest extends BaseObjectRest { } @Override + @JsonProperty(access = JsonProperty.Access.READ_ONLY) public String getType() { return NAME; } diff --git a/dspace-spring-rest/src/main/java/org/dspace/app/rest/repository/DSpaceRestRepository.java b/dspace-spring-rest/src/main/java/org/dspace/app/rest/repository/DSpaceRestRepository.java index 29e0085bb3..5cf16f6031 100644 --- a/dspace-spring-rest/src/main/java/org/dspace/app/rest/repository/DSpaceRestRepository.java +++ b/dspace-spring-rest/src/main/java/org/dspace/app/rest/repository/DSpaceRestRepository.java @@ -409,7 +409,8 @@ public abstract class DSpaceRestRepository idRef = new AtomicReference<>(); - try { - assertThat(metadataSchemaService.find(context, TEST_NAME), nullValue()); + getClient(authToken) + .perform(post("/api/core/metadataschemas") + .content(new ObjectMapper().writeValueAsBytes(metadataSchemaRest)) + .contentType(contentType)) + .andExpect(status().isCreated()) + .andDo(result -> idRef.set(read(result.getResponse().getContentAsString(), "$.id"))); - getClient(authToken) - .perform(post("/api/core/metadataschemas") - .content(new ObjectMapper().writeValueAsBytes(metadataSchemaRest)) - .contentType(contentType)) - .andExpect(status().isCreated()) - .andDo(result -> idRef.set(read(result.getResponse().getContentAsString(), "$.id"))); - - MetadataSchema metadataSchema = metadataSchemaService.find(context, idRef.get()); - assertThat(metadataSchema, notNullValue()); - - assertEquals(metadataSchema.getID(), idRef.get()); - assertEquals(metadataSchema.getName(), TEST_NAME); - assertEquals(metadataSchema.getNamespace(), TEST_NAMESPACE); - - } finally { - deleteMetadataSchemaIfExists(TEST_NAME); - } + getClient().perform(get("/api/core/metadataschemas/" + idRef.get())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$", MetadataschemaMatcher.matchEntry(TEST_NAME, TEST_NAMESPACE))); } @Test public void createUnauthauthorizedTest() throws Exception { + context.turnOffAuthorisationSystem(); MetadataSchemaRest metadataSchemaRest = new MetadataSchemaRest(); metadataSchemaRest.setPrefix(TEST_NAME); metadataSchemaRest.setNamespace(TEST_NAMESPACE); - try { - getClient() - .perform(post("/api/core/metadataschemas") - .content(new ObjectMapper().writeValueAsBytes(metadataSchemaRest)) - .contentType(contentType)) - .andExpect(status().isUnauthorized()); - - } finally { - deleteMetadataSchemaIfExists(TEST_NAME); - } + getClient() + .perform(post("/api/core/metadataschemas") + .content(new ObjectMapper().writeValueAsBytes(metadataSchemaRest)) + .contentType(contentType)) + .andExpect(status().isUnauthorized()); } @Test public void deleteSuccess() throws Exception { + context.turnOffAuthorisationSystem(); - MetadataSchema metadataSchema = createMetadataSchema(); + MetadataSchema metadataSchema = MetadataSchemaBuilder.createMetadataSchema(context, "ATest", "A namespace") + .build(); - try { + getClient().perform(get("/api/core/metadataschemas/" + metadataSchema.getID())) + .andExpect(status().isOk()); - assertThat(metadataSchemaService.find(context, metadataSchema.getID()), notNullValue()); - getClient(getAuthToken(admin.getEmail(), password)) - .perform(delete("/api/core/metadataschemas/" + metadataSchema.getID())) - .andExpect(status().isNoContent()); + getClient(getAuthToken(admin.getEmail(), password)) + .perform(delete("/api/core/metadataschemas/" + metadataSchema.getID())) + .andExpect(status().isNoContent()); - assertThat(metadataSchemaService.find(context, metadataSchema.getID()), nullValue()); + getClient().perform(get("/api/core/metadataschemas/" + metadataSchema.getID())) + .andExpect(status().isNotFound()); - } finally { - deleteMetadataSchemaIfExists(TEST_NAME); - } } @Test public void deleteUnauthorized() throws Exception { + context.turnOffAuthorisationSystem(); - MetadataSchema metadataSchema = createMetadataSchema(); + MetadataSchema metadataSchema = MetadataSchemaBuilder.createMetadataSchema(context, TEST_NAME, TEST_NAMESPACE) + .build(); - try { + getClient().perform(get("/api/core/metadataschemas/" + metadataSchema.getID())).andExpect(status().isOk()); - assertThat(metadataSchemaService.find(context, metadataSchema.getID()), notNullValue()); + getClient() + .perform(delete("/api/core/metadataschemas/" + metadataSchema.getID())) + .andExpect(status().isUnauthorized()); - getClient() - .perform(delete("/api/core/metadataschemas/" + metadataSchema.getID())) - .andExpect(status().isUnauthorized()); + getClient().perform(get("/api/core/metadataschemas/" + metadataSchema.getID())).andExpect(status().isOk()); - assertThat(metadataSchemaService.find(context, metadataSchema.getID()), notNullValue()); - - } finally { - deleteMetadataSchemaIfExists(TEST_NAME); - } } @Test public void deleteNonExisting() throws Exception { + context.turnOffAuthorisationSystem(); - MetadataSchema metadataSchema = createMetadataSchema(); - deleteMetadataSchemaIfExists(TEST_NAME); + MetadataSchema metadataSchema = MetadataSchemaBuilder.createMetadataSchema(context, "A name", "A namespace") + .build(); Integer id = metadataSchema.getID(); - assertThat(metadataSchemaService.find(context, id), nullValue()); + + getClient(getAuthToken(admin.getEmail(), password)) + .perform(delete("/api/core/metadataschemas/" + id)) + .andExpect(status().isNoContent()); getClient(getAuthToken(admin.getEmail(), password)) .perform(delete("/api/core/metadataschemas/" + id)) @@ -188,74 +175,50 @@ public class MetadataSchemaRestRepositoryIT extends AbstractControllerIntegratio @Test public void update() throws Exception { + context.turnOffAuthorisationSystem(); - MetadataSchema metadataSchema = createMetadataSchema(); + MetadataSchema metadataSchema = MetadataSchemaBuilder.createMetadataSchema(context, TEST_NAME, TEST_NAMESPACE) + .build(); MetadataSchemaRest metadataSchemaRest = new MetadataSchemaRest(); metadataSchemaRest.setId(metadataSchema.getID()); metadataSchemaRest.setPrefix(TEST_NAME_UPDATED); metadataSchemaRest.setNamespace(TEST_NAMESPACE_UPDATED); - try { - getClient(getAuthToken(admin.getEmail(), password)) - .perform(put("/api/core/metadataschemas/" + metadataSchema.getID()) - .content(new ObjectMapper().writeValueAsBytes(metadataSchemaRest)) - .contentType(contentType)) - .andExpect(status().isOk()); + getClient(getAuthToken(admin.getEmail(), password)) + .perform(put("/api/core/metadataschemas/" + metadataSchema.getID()) + .content(new ObjectMapper().writeValueAsBytes(metadataSchemaRest)) + .contentType(contentType)) + .andExpect(status().isOk()); - metadataSchema = metadataSchemaService.find(context, metadataSchema.getID()); - - assertEquals(TEST_NAME_UPDATED, metadataSchema.getName()); - assertEquals(TEST_NAMESPACE_UPDATED, metadataSchema.getNamespace()); - } finally { - deleteMetadataSchemaIfExists(metadataSchema); - } + getClient().perform(get("/api/core/metadataschemas/" + metadataSchema.getID())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$", MetadataschemaMatcher + .matchEntry(TEST_NAME_UPDATED, TEST_NAMESPACE_UPDATED))); } @Test public void updateUnauthorized() throws Exception { + context.turnOffAuthorisationSystem(); - MetadataSchema metadataSchema = createMetadataSchema(); + MetadataSchema metadataSchema = MetadataSchemaBuilder.createMetadataSchema(context, TEST_NAME, TEST_NAMESPACE) + .build(); MetadataSchemaRest metadataSchemaRest = new MetadataSchemaRest(); metadataSchemaRest.setId(metadataSchema.getID()); metadataSchemaRest.setPrefix(TEST_NAME_UPDATED); metadataSchemaRest.setNamespace(TEST_NAMESPACE_UPDATED); - try { - getClient() - .perform(put("/api/core/metadataschemas/" + metadataSchema.getID()) - .content(new ObjectMapper().writeValueAsBytes(metadataSchemaRest)) - .contentType(contentType)) - .andExpect(status().isUnauthorized()); + getClient() + .perform(put("/api/core/metadataschemas/" + metadataSchema.getID()) + .content(new ObjectMapper().writeValueAsBytes(metadataSchemaRest)) + .contentType(contentType)) + .andExpect(status().isUnauthorized()); + + getClient().perform(get("/api/core/metadataschemas/" + metadataSchema.getID())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$", MetadataschemaMatcher + .matchEntry(TEST_NAME, TEST_NAMESPACE))); +} - metadataSchema = metadataSchemaService.find(context, metadataSchema.getID()); - - assertEquals(TEST_NAME, metadataSchema.getName()); - assertEquals(TEST_NAMESPACE, metadataSchema.getNamespace()); - } finally { - deleteMetadataSchemaIfExists(metadataSchema); - } - } - - private MetadataSchema createMetadataSchema() throws SQLException, AuthorizeException, NonUniqueMetadataException { - context.turnOffAuthorisationSystem(); - MetadataSchema metadataSchema = metadataSchemaService.create(context, TEST_NAME, TEST_NAMESPACE); - context.commit(); - return metadataSchema; - } - - private void deleteMetadataSchemaIfExists(String name) throws SQLException, AuthorizeException { - - deleteMetadataSchemaIfExists(metadataSchemaService.find(context, name)); - } - - private void deleteMetadataSchemaIfExists(MetadataSchema metadataSchema) throws SQLException, AuthorizeException { - - if (metadataSchema != null) { - context.turnOffAuthorisationSystem(); - metadataSchemaService.delete(context, metadataSchema); - context.commit(); - } - } } diff --git a/dspace-spring-rest/src/test/java/org/dspace/app/rest/MetadatafieldRestRepositoryIT.java b/dspace-spring-rest/src/test/java/org/dspace/app/rest/MetadatafieldRestRepositoryIT.java index b207dcd655..a90305abbb 100644 --- a/dspace-spring-rest/src/test/java/org/dspace/app/rest/MetadatafieldRestRepositoryIT.java +++ b/dspace-spring-rest/src/test/java/org/dspace/app/rest/MetadatafieldRestRepositoryIT.java @@ -11,7 +11,6 @@ import static com.jayway.jsonpath.JsonPath.read; import static org.hamcrest.Matchers.is; import static org.hamcrest.Matchers.notNullValue; import static org.hamcrest.Matchers.nullValue; -import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertThat; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.delete; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; @@ -21,7 +20,6 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers. import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; -import java.sql.SQLException; import java.util.concurrent.atomic.AtomicReference; import com.fasterxml.jackson.databind.ObjectMapper; @@ -30,11 +28,9 @@ import org.dspace.app.rest.builder.MetadataSchemaBuilder; import org.dspace.app.rest.matcher.MetadataFieldMatcher; import org.dspace.app.rest.model.MetadataFieldRest; import org.dspace.app.rest.test.AbstractControllerIntegrationTest; -import org.dspace.authorize.AuthorizeException; import org.dspace.content.MetadataField; import org.dspace.content.MetadataFieldServiceImpl; import org.dspace.content.MetadataSchema; -import org.dspace.content.NonUniqueMetadataException; import org.dspace.content.service.MetadataSchemaService; import org.hamcrest.Matchers; import org.junit.Before; @@ -104,6 +100,8 @@ public class MetadatafieldRestRepositoryIT extends AbstractControllerIntegration @Test public void searchMethodsExist() throws Exception { + context.turnOffAuthorisationSystem(); + getClient().perform(get("/api/core/metadatafields")) .andExpect(jsonPath("$._links.search.href", notNullValue())); @@ -147,6 +145,7 @@ public class MetadatafieldRestRepositoryIT extends AbstractControllerIntegration @Test public void findByUndefinedSchema() throws Exception { + context.turnOffAuthorisationSystem(); getClient().perform(get("/api/core/metadatafields/search/bySchema") .param("schema", "undefined")) @@ -158,6 +157,7 @@ public class MetadatafieldRestRepositoryIT extends AbstractControllerIntegration @Test public void findByNullSchema() throws Exception { + context.turnOffAuthorisationSystem(); getClient().perform(get("/api/core/metadatafields/search/bySchema")) .andExpect(status().isUnprocessableEntity()); @@ -165,106 +165,135 @@ public class MetadatafieldRestRepositoryIT extends AbstractControllerIntegration @Test public void createSuccess() throws Exception { + context.turnOffAuthorisationSystem(); MetadataFieldRest metadataFieldRest = new MetadataFieldRest(); - metadataFieldRest.setElement(ELEMENT); - metadataFieldRest.setQualifier(QUALIFIER); + metadataFieldRest.setElement("testElementForCreate"); + metadataFieldRest.setQualifier("testQualifierForCreate"); metadataFieldRest.setScopeNote(SCOPE_NOTE); String authToken = getAuthToken(admin.getEmail(), password); AtomicReference idRef = new AtomicReference<>(); - try { - assertThat(metadataFieldService.findByElement(context, metadataSchema, ELEMENT, QUALIFIER), nullValue()); + assertThat(metadataFieldService.findByElement(context, metadataSchema, ELEMENT, QUALIFIER), nullValue()); - getClient(authToken) - .perform(post("/api/core/metadatafields") - .param("schemaId", metadataSchema.getID() + "") - .content(new ObjectMapper().writeValueAsBytes(metadataFieldRest)) - .contentType(contentType)) - .andExpect(status().isCreated()) - .andDo(result -> idRef.set(read(result.getResponse().getContentAsString(), "$.id"))); + getClient(authToken) + .perform(post("/api/core/metadatafields") + .param("schemaId", metadataSchema.getID() + "") + .content(new ObjectMapper().writeValueAsBytes(metadataFieldRest)) + .contentType(contentType)) + .andExpect(status().isCreated()) + .andDo(result -> idRef.set(read(result.getResponse().getContentAsString(), "$.id"))); - MetadataField metadataField = metadataFieldService.find(context, idRef.get()); - assertThat(metadataField, notNullValue()); - - assertEquals(metadataField.getMetadataSchema(), metadataSchema); - assertEquals(metadataField.getElement(), ELEMENT); - assertEquals(metadataField.getQualifier(), QUALIFIER); - assertEquals(metadataField.getScopeNote(), SCOPE_NOTE); - - } finally { - deleteMetadataFieldIfExists(); - } + getClient(authToken).perform(get("/api/core/metadatafields/" + idRef.get())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$", MetadataFieldMatcher.matchMetadataFieldByKeys( + metadataSchema.getName(), "testElementForCreate", "testQualifierForCreate"))); } @Test - public void createUnauthauthorized() throws Exception { + public void createUnauthorized() throws Exception { + context.turnOffAuthorisationSystem(); MetadataFieldRest metadataFieldRest = new MetadataFieldRest(); metadataFieldRest.setElement(ELEMENT); metadataFieldRest.setQualifier(QUALIFIER); metadataFieldRest.setScopeNote(SCOPE_NOTE); - try { - getClient() - .perform(post("/api/core/metadatafields") - .param("schemaId", metadataSchema.getID() + "") - .content(new ObjectMapper().writeValueAsBytes(metadataFieldRest)) - .contentType(contentType)) - .andExpect(status().isUnauthorized()); - } finally { - deleteMetadataFieldIfExists(); - } + getClient() + .perform(post("/api/core/metadatafields") + .param("schemaId", metadataSchema.getID() + "") + .content(new ObjectMapper().writeValueAsBytes(metadataFieldRest)) + .contentType(contentType)) + .andExpect(status().isUnauthorized()); + } + + @Test + public void createUnauthorizedEPersonNoAdminRights() throws Exception { + context.turnOffAuthorisationSystem(); + + MetadataFieldRest metadataFieldRest = new MetadataFieldRest(); + metadataFieldRest.setElement(ELEMENT); + metadataFieldRest.setQualifier(QUALIFIER); + metadataFieldRest.setScopeNote(SCOPE_NOTE); + + String token = getAuthToken(eperson.getEmail(), password); + + getClient(token) + .perform(post("/api/core/metadatafields") + .param("schemaId", metadataSchema.getID() + "") + .content(new ObjectMapper().writeValueAsBytes(metadataFieldRest)) + .contentType(contentType)) + .andExpect(status().isForbidden()); } @Test public void deleteSuccess() throws Exception { + context.turnOffAuthorisationSystem(); - MetadataField metadataField = createMetadataField(); + MetadataField metadataField = MetadataFieldBuilder.createMetadataField(context, ELEMENT, QUALIFIER, SCOPE_NOTE) + .build(); - try { - assertThat(metadataFieldService.find(context, metadataField.getID()), notNullValue()); + getClient().perform(get("/api/core/metadatafields/" + metadataField.getID())) + .andExpect(status().isOk()); + getClient(getAuthToken(admin.getEmail(), password)) + .perform(delete("/api/core/metadatafields/" + metadataField.getID())) + .andExpect(status().isNoContent()); - getClient(getAuthToken(admin.getEmail(), password)) - .perform(delete("/api/core/metadatafields/" + metadataField.getID())) - .andExpect(status().isNoContent()); - - assertThat(metadataFieldService.find(context, metadataField.getID()), nullValue()); - - } finally { - deleteMetadataFieldIfExists(); - } + getClient().perform(get("/api/core/metadatafields/" + metadataField.getID())) + .andExpect(status().isNotFound()); } @Test public void deleteUnauthorized() throws Exception { + context.turnOffAuthorisationSystem(); - MetadataField metadataField = createMetadataField(); + MetadataField metadataField = MetadataFieldBuilder.createMetadataField(context, ELEMENT, QUALIFIER, SCOPE_NOTE) + .build(); - try { + getClient().perform(get("/api/core/metadatafields/" + metadataField.getID())) + .andExpect(status().isOk()); + getClient() + .perform(delete("/api/core/metadatafields/" + metadataField.getID())) + .andExpect(status().isUnauthorized()); - assertThat(metadataFieldService.find(context, metadataField.getID()), notNullValue()); - - getClient() - .perform(delete("/api/core/metadatafields/" + metadataField.getID())) - .andExpect(status().isUnauthorized()); - - assertThat(metadataFieldService.find(context, metadataField.getID()), notNullValue()); - - } finally { - deleteMetadataFieldIfExists(); - } + getClient().perform(get("/api/core/metadatafields/" + metadataField.getID())) + .andExpect(status().isOk()); } @Test - public void deleteNonExisting() throws Exception { + public void deleteUnauthorizedEPersonNoAdminRights() throws Exception { + context.turnOffAuthorisationSystem(); - MetadataField metadataField = createMetadataField(); - deleteMetadataFieldIfExists(); + MetadataField metadataField = MetadataFieldBuilder.createMetadataField(context, ELEMENT, QUALIFIER, SCOPE_NOTE) + .build(); + String token = getAuthToken(eperson.getEmail(), password); + + + getClient().perform(get("/api/core/metadatafields/" + metadataField.getID())) + .andExpect(status().isOk()); + getClient(token) + .perform(delete("/api/core/metadatafields/" + metadataField.getID())) + .andExpect(status().isForbidden()); + + getClient().perform(get("/api/core/metadatafields/" + metadataField.getID())) + .andExpect(status().isOk()); + } + + + @Test + public void deleteNonExisting() throws Exception { + context.turnOffAuthorisationSystem(); + + MetadataField metadataField = MetadataFieldBuilder.createMetadataField(context, ELEMENT, QUALIFIER, SCOPE_NOTE) + .build(); Integer id = metadataField.getID(); + getClient(getAuthToken(admin.getEmail(), password)) + .perform(delete("/api/core/metadatafields/" + id)) + .andExpect(status().isNoContent()); + assertThat(metadataFieldService.find(context, id), nullValue()); getClient(getAuthToken(admin.getEmail(), password)) @@ -274,79 +303,55 @@ public class MetadatafieldRestRepositoryIT extends AbstractControllerIntegration @Test public void update() throws Exception { + context.turnOffAuthorisationSystem(); - MetadataField metadataField = createMetadataField(); - + MetadataField metadataField = MetadataFieldBuilder.createMetadataField(context, ELEMENT, QUALIFIER, SCOPE_NOTE) + .build(); MetadataFieldRest metadataFieldRest = new MetadataFieldRest(); metadataFieldRest.setId(metadataField.getID()); metadataFieldRest.setElement(ELEMENT_UPDATED); metadataFieldRest.setQualifier(QUALIFIER_UPDATED); metadataFieldRest.setScopeNote(SCOPE_NOTE_UPDATED); - try { - getClient(getAuthToken(admin.getEmail(), password)) - .perform(put("/api/core/metadatafields/" + metadataField.getID()) - .content(new ObjectMapper().writeValueAsBytes(metadataFieldRest)) - .contentType(contentType)) - .andExpect(status().isOk()); + getClient(getAuthToken(admin.getEmail(), password)) + .perform(put("/api/core/metadatafields/" + metadataField.getID()) + .content(new ObjectMapper().writeValueAsBytes(metadataFieldRest)) + .contentType(contentType)) + .andExpect(status().isOk()); - metadataField = metadataFieldService.find(context, metadataField.getID()); - - assertEquals(ELEMENT_UPDATED, metadataField.getElement()); - assertEquals(QUALIFIER_UPDATED, metadataField.getQualifier()); - assertEquals(SCOPE_NOTE_UPDATED, metadataField.getScopeNote()); - } finally { - deleteMetadataFieldIfExists(metadataField); - } + getClient().perform(get("/api/core/metadatafields/" + metadataField.getID())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$", MetadataFieldMatcher.matchMetadataFieldByKeys( + metadataSchema.getName(), ELEMENT_UPDATED, QUALIFIER_UPDATED) + )); } @Test public void updateUnauthorized() throws Exception { + context.turnOffAuthorisationSystem(); - MetadataField metadataField = createMetadataField(); - + MetadataField metadataField = MetadataFieldBuilder.createMetadataField(context, ELEMENT, QUALIFIER, SCOPE_NOTE) + .build(); MetadataFieldRest metadataFieldRest = new MetadataFieldRest(); metadataFieldRest.setId(metadataField.getID()); metadataFieldRest.setElement(ELEMENT_UPDATED); metadataFieldRest.setQualifier(QUALIFIER_UPDATED); metadataFieldRest.setScopeNote(SCOPE_NOTE_UPDATED); - try { - getClient() - .perform(put("/api/core/metadatafields/" + metadataField.getID()) - .content(new ObjectMapper().writeValueAsBytes(metadataFieldRest)) - .contentType(contentType)) - .andExpect(status().isUnauthorized()); + getClient() + .perform(put("/api/core/metadatafields/" + metadataField.getID()) + .content(new ObjectMapper().writeValueAsBytes(metadataFieldRest)) + .contentType(contentType)) + .andExpect(status().isUnauthorized()); + + getClient().perform(get("/api/core/metadatafields/" + metadataField.getID())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$", MetadataFieldMatcher.matchMetadataFieldByKeys( + metadataSchema.getName(), ELEMENT, QUALIFIER) + )); - metadataField = metadataFieldService.find(context, metadataField.getID()); - assertEquals(ELEMENT, metadataField.getElement()); - assertEquals(QUALIFIER, metadataField.getQualifier()); - assertEquals(SCOPE_NOTE, metadataField.getScopeNote()); - } finally { - deleteMetadataFieldIfExists(metadataField); - } } - private MetadataField createMetadataField() throws AuthorizeException, SQLException, NonUniqueMetadataException { - context.turnOffAuthorisationSystem(); - MetadataField metadataField = metadataFieldService.create( - context, metadataSchema, ELEMENT, QUALIFIER, SCOPE_NOTE - ); - context.commit(); - return metadataField; - } - private void deleteMetadataFieldIfExists() throws SQLException, AuthorizeException { - - deleteMetadataFieldIfExists(metadataFieldService.findByElement(context, metadataSchema, ELEMENT, QUALIFIER)); - } - - private void deleteMetadataFieldIfExists(MetadataField metadataField) throws SQLException, AuthorizeException { - if (metadataField != null) { - context.turnOffAuthorisationSystem(); - metadataFieldService.delete(context, metadataField); - context.commit(); - } - } } From 7cde38d2298721ab3b9a78a3788b7612d0b77bff Mon Sep 17 00:00:00 2001 From: Kim Shepherd Date: Wed, 13 Feb 2019 13:03:37 +1300 Subject: [PATCH 47/68] [DS-4136] Tidy up comments, only display per-1k msg if batch size !1000 --- dspace-oai/src/main/java/org/dspace/xoai/app/XOAI.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/dspace-oai/src/main/java/org/dspace/xoai/app/XOAI.java b/dspace-oai/src/main/java/org/dspace/xoai/app/XOAI.java index 8c2ac8a1f9..1a6083ca24 100644 --- a/dspace-oai/src/main/java/org/dspace/xoai/app/XOAI.java +++ b/dspace-oai/src/main/java/org/dspace/xoai/app/XOAI.java @@ -56,7 +56,7 @@ import org.dspace.content.factory.ContentServiceFactory; import org.dspace.content.service.ItemService; import org.dspace.core.Constants; import org.dspace.core.Context; -import org.dspace.services.ConfigurationService; //added +import org.dspace.services.ConfigurationService; import org.dspace.services.factory.DSpaceServicesFactory; import org.dspace.xoai.exceptions.CompilingException; import org.dspace.xoai.services.api.CollectionsService; @@ -304,7 +304,7 @@ public class XOAI { log.error(ex.getMessage(), ex); } i++; - if (i % 1000 == 0) { + if (i % 1000 == 0 && batchSize != 1000) { System.out.println(i + " items imported so far..."); } if (i % batchSize == 0) { From d450b59899119cd70e32c3c2f788c22c19d0d02a Mon Sep 17 00:00:00 2001 From: Raf Ponsaerts Date: Wed, 13 Feb 2019 13:08:30 +0100 Subject: [PATCH 48/68] Applied feedback and added tests --- .../rest/MetadataSchemaRestRepositoryIT.java | 52 ++++++- .../rest/MetadatafieldRestRepositoryIT.java | 142 ++++++++++++------ 2 files changed, 140 insertions(+), 54 deletions(-) diff --git a/dspace-spring-rest/src/test/java/org/dspace/app/rest/MetadataSchemaRestRepositoryIT.java b/dspace-spring-rest/src/test/java/org/dspace/app/rest/MetadataSchemaRestRepositoryIT.java index 922ff65867..8ffcd4df67 100644 --- a/dspace-spring-rest/src/test/java/org/dspace/app/rest/MetadataSchemaRestRepositoryIT.java +++ b/dspace-spring-rest/src/test/java/org/dspace/app/rest/MetadataSchemaRestRepositoryIT.java @@ -31,6 +31,11 @@ import org.hamcrest.Matchers; import org.junit.Test; import org.springframework.beans.factory.annotation.Autowired; +/** + * Integration tests for the {@link org.dspace.app.rest.repository.MetadataSchemaRestRepository} + * This class will include all the tests for the logic with regards to the + * {@link org.dspace.app.rest.repository.MetadataSchemaRestRepository} + */ public class MetadataSchemaRestRepositoryIT extends AbstractControllerIntegrationTest { private static final String TEST_NAME = "testSchemaName"; @@ -48,6 +53,7 @@ public class MetadataSchemaRestRepositoryIT extends AbstractControllerIntegratio context.turnOffAuthorisationSystem(); MetadataSchema metadataSchema = MetadataSchemaBuilder.createMetadataSchema(context, "ATest", "ANamespace") .build(); + context.restoreAuthSystemState(); getClient().perform(get("/api/core/metadataschemas")) .andExpect(status().isOk()) @@ -65,6 +71,7 @@ public class MetadataSchemaRestRepositoryIT extends AbstractControllerIntegratio context.turnOffAuthorisationSystem(); MetadataSchema metadataSchema = MetadataSchemaBuilder.createMetadataSchema(context, "ATest", "ANamespace") .build(); + context.restoreAuthSystemState(); getClient().perform(get("/api/core/metadataschemas/" + metadataSchema.getID())) .andExpect(status().isOk()) @@ -79,8 +86,7 @@ public class MetadataSchemaRestRepositoryIT extends AbstractControllerIntegratio MetadataSchema metadataSchema = MetadataSchemaBuilder.createMetadataSchema(context, "ATest", "ANamespace") .build(); - - + context.restoreAuthSystemState(); MetadataSchemaRest metadataSchemaRest = metadataSchemaConverter.fromModel(metadataSchema); metadataSchemaRest.setPrefix(TEST_NAME); @@ -103,10 +109,8 @@ public class MetadataSchemaRestRepositoryIT extends AbstractControllerIntegratio } @Test - public void createUnauthauthorizedTest() + public void createUnauthorizedTest() throws Exception { - context.turnOffAuthorisationSystem(); - MetadataSchemaRest metadataSchemaRest = new MetadataSchemaRest(); metadataSchemaRest.setPrefix(TEST_NAME); metadataSchemaRest.setNamespace(TEST_NAMESPACE); @@ -125,6 +129,8 @@ public class MetadataSchemaRestRepositoryIT extends AbstractControllerIntegratio MetadataSchema metadataSchema = MetadataSchemaBuilder.createMetadataSchema(context, "ATest", "A namespace") .build(); + context.restoreAuthSystemState(); + getClient().perform(get("/api/core/metadataschemas/" + metadataSchema.getID())) .andExpect(status().isOk()); @@ -145,6 +151,8 @@ public class MetadataSchemaRestRepositoryIT extends AbstractControllerIntegratio MetadataSchema metadataSchema = MetadataSchemaBuilder.createMetadataSchema(context, TEST_NAME, TEST_NAMESPACE) .build(); + context.restoreAuthSystemState(); + getClient().perform(get("/api/core/metadataschemas/" + metadataSchema.getID())).andExpect(status().isOk()); getClient() @@ -162,6 +170,8 @@ public class MetadataSchemaRestRepositoryIT extends AbstractControllerIntegratio MetadataSchema metadataSchema = MetadataSchemaBuilder.createMetadataSchema(context, "A name", "A namespace") .build(); + context.restoreAuthSystemState(); + Integer id = metadataSchema.getID(); getClient(getAuthToken(admin.getEmail(), password)) @@ -180,6 +190,8 @@ public class MetadataSchemaRestRepositoryIT extends AbstractControllerIntegratio MetadataSchema metadataSchema = MetadataSchemaBuilder.createMetadataSchema(context, TEST_NAME, TEST_NAMESPACE) .build(); + context.restoreAuthSystemState(); + MetadataSchemaRest metadataSchemaRest = new MetadataSchemaRest(); metadataSchemaRest.setId(metadataSchema.getID()); metadataSchemaRest.setPrefix(TEST_NAME_UPDATED); @@ -204,6 +216,8 @@ public class MetadataSchemaRestRepositoryIT extends AbstractControllerIntegratio MetadataSchema metadataSchema = MetadataSchemaBuilder.createMetadataSchema(context, TEST_NAME, TEST_NAMESPACE) .build(); + context.restoreAuthSystemState(); + MetadataSchemaRest metadataSchemaRest = new MetadataSchemaRest(); metadataSchemaRest.setId(metadataSchema.getID()); metadataSchemaRest.setPrefix(TEST_NAME_UPDATED); @@ -219,6 +233,32 @@ public class MetadataSchemaRestRepositoryIT extends AbstractControllerIntegratio .andExpect(status().isOk()) .andExpect(jsonPath("$", MetadataschemaMatcher .matchEntry(TEST_NAME, TEST_NAMESPACE))); -} + } + + @Test + public void updateWrongRights() throws Exception { + context.turnOffAuthorisationSystem(); + + MetadataSchema metadataSchema = MetadataSchemaBuilder.createMetadataSchema(context, TEST_NAME, TEST_NAMESPACE) + .build(); + + context.restoreAuthSystemState(); + + MetadataSchemaRest metadataSchemaRest = new MetadataSchemaRest(); + metadataSchemaRest.setId(metadataSchema.getID()); + metadataSchemaRest.setPrefix(TEST_NAME_UPDATED); + metadataSchemaRest.setNamespace(TEST_NAMESPACE_UPDATED); + + getClient(getAuthToken(eperson.getEmail(), password)) + .perform(put("/api/core/metadataschemas/" + metadataSchema.getID()) + .content(new ObjectMapper().writeValueAsBytes(metadataSchemaRest)) + .contentType(contentType)) + .andExpect(status().isForbidden()); + + getClient().perform(get("/api/core/metadataschemas/" + metadataSchema.getID())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$", MetadataschemaMatcher + .matchEntry(TEST_NAME, TEST_NAMESPACE))); + } } diff --git a/dspace-spring-rest/src/test/java/org/dspace/app/rest/MetadatafieldRestRepositoryIT.java b/dspace-spring-rest/src/test/java/org/dspace/app/rest/MetadatafieldRestRepositoryIT.java index a90305abbb..49a5995be7 100644 --- a/dspace-spring-rest/src/test/java/org/dspace/app/rest/MetadatafieldRestRepositoryIT.java +++ b/dspace-spring-rest/src/test/java/org/dspace/app/rest/MetadatafieldRestRepositoryIT.java @@ -37,6 +37,11 @@ import org.junit.Before; import org.junit.Test; import org.springframework.beans.factory.annotation.Autowired; +/** + * Integration tests for the {@link org.dspace.app.rest.repository.MetadataFieldRestRepository} + * This class will include all the tests for the logic with regards to the + * {@link org.dspace.app.rest.repository.MetadataFieldRestRepository} + */ public class MetadatafieldRestRepositoryIT extends AbstractControllerIntegrationTest { private static final String ELEMENT = "test element"; @@ -66,14 +71,15 @@ public class MetadatafieldRestRepositoryIT extends AbstractControllerIntegration context.turnOffAuthorisationSystem(); MetadataField metadataField = MetadataFieldBuilder .createMetadataField(context, "AnElement", "AQualifier", "AScopeNote").build(); + context.restoreAuthSystemState(); getClient().perform(get("/api/core/metadatafields") - .param("size", String.valueOf(100))) + .param("size", String.valueOf(100))) .andExpect(status().isOk()) .andExpect(content().contentType(contentType)) .andExpect(jsonPath("$._embedded.metadatafields", Matchers.hasItems( - MetadataFieldMatcher.matchMetadataFieldByKeys("dc","title", null), - MetadataFieldMatcher.matchMetadataFieldByKeys("dc","date", "issued")) + MetadataFieldMatcher.matchMetadataFieldByKeys("dc", "title", null), + MetadataFieldMatcher.matchMetadataFieldByKeys("dc", "date", "issued")) )) .andExpect(jsonPath("$._links.first.href", Matchers.containsString("/api/core/metadatafields"))) .andExpect(jsonPath("$._links.self.href", Matchers.containsString("/api/core/metadatafields"))) @@ -89,6 +95,7 @@ public class MetadatafieldRestRepositoryIT extends AbstractControllerIntegration context.turnOffAuthorisationSystem(); MetadataField metadataField = MetadataFieldBuilder .createMetadataField(context, "AnElement", "AQualifier", "AScopeNote").build(); + context.restoreAuthSystemState(); getClient().perform(get("/api/core/metadatafields/" + metadataField.getID())) .andExpect(status().isOk()) @@ -100,7 +107,6 @@ public class MetadatafieldRestRepositoryIT extends AbstractControllerIntegration @Test public void searchMethodsExist() throws Exception { - context.turnOffAuthorisationSystem(); getClient().perform(get("/api/core/metadatafields")) .andExpect(jsonPath("$._links.search.href", notNullValue())); @@ -116,39 +122,39 @@ public class MetadatafieldRestRepositoryIT extends AbstractControllerIntegration context.turnOffAuthorisationSystem(); MetadataSchema schema = MetadataSchemaBuilder.createMetadataSchema(context, "ASchema", - "http://www.dspace.org/ns/aschema").build(); + "http://www.dspace.org/ns/aschema").build(); MetadataField metadataField = MetadataFieldBuilder .createMetadataField(context, schema, "AnElement", "AQualifier", "AScopeNote").build(); + context.restoreAuthSystemState(); getClient().perform(get("/api/core/metadatafields/search/bySchema") - .param("schema", "dc") - .param("size", String.valueOf(100))) + .param("schema", "dc") + .param("size", String.valueOf(100))) .andExpect(status().isOk()) .andExpect(content().contentType(contentType)) .andExpect(jsonPath("$._embedded.metadatafields", Matchers.hasItems( - MetadataFieldMatcher.matchMetadataFieldByKeys("dc","title", null), - MetadataFieldMatcher.matchMetadataFieldByKeys("dc","date", "issued")) + MetadataFieldMatcher.matchMetadataFieldByKeys("dc", "title", null), + MetadataFieldMatcher.matchMetadataFieldByKeys("dc", "date", "issued")) )) .andExpect(jsonPath("$.page.size", is(100))); getClient().perform(get("/api/core/metadatafields/search/bySchema") - .param("schema", schema.getName())) - .andExpect(status().isOk()) - .andExpect(content().contentType(contentType)) - .andExpect(jsonPath("$._embedded.metadatafields", Matchers.hasItem( - MetadataFieldMatcher.matchMetadataField(metadataField)) - )) - .andExpect(jsonPath("$.page.size", is(20))) - .andExpect(jsonPath("$.page.totalElements", is(1))); + .param("schema", schema.getName())) + .andExpect(status().isOk()) + .andExpect(content().contentType(contentType)) + .andExpect(jsonPath("$._embedded.metadatafields", Matchers.hasItem( + MetadataFieldMatcher.matchMetadataField(metadataField)) + )) + .andExpect(jsonPath("$.page.size", is(20))) + .andExpect(jsonPath("$.page.totalElements", is(1))); } @Test public void findByUndefinedSchema() throws Exception { - context.turnOffAuthorisationSystem(); getClient().perform(get("/api/core/metadatafields/search/bySchema") - .param("schema", "undefined")) + .param("schema", "undefined")) .andExpect(status().isOk()) .andExpect(content().contentType(contentType)) .andExpect(jsonPath("$.page.size", is(20))) @@ -157,7 +163,6 @@ public class MetadatafieldRestRepositoryIT extends AbstractControllerIntegration @Test public void findByNullSchema() throws Exception { - context.turnOffAuthorisationSystem(); getClient().perform(get("/api/core/metadatafields/search/bySchema")) .andExpect(status().isUnprocessableEntity()); @@ -165,7 +170,6 @@ public class MetadatafieldRestRepositoryIT extends AbstractControllerIntegration @Test public void createSuccess() throws Exception { - context.turnOffAuthorisationSystem(); MetadataFieldRest metadataFieldRest = new MetadataFieldRest(); metadataFieldRest.setElement("testElementForCreate"); @@ -178,12 +182,12 @@ public class MetadatafieldRestRepositoryIT extends AbstractControllerIntegration assertThat(metadataFieldService.findByElement(context, metadataSchema, ELEMENT, QUALIFIER), nullValue()); getClient(authToken) - .perform(post("/api/core/metadatafields") - .param("schemaId", metadataSchema.getID() + "") - .content(new ObjectMapper().writeValueAsBytes(metadataFieldRest)) - .contentType(contentType)) - .andExpect(status().isCreated()) - .andDo(result -> idRef.set(read(result.getResponse().getContentAsString(), "$.id"))); + .perform(post("/api/core/metadatafields") + .param("schemaId", metadataSchema.getID() + "") + .content(new ObjectMapper().writeValueAsBytes(metadataFieldRest)) + .contentType(contentType)) + .andExpect(status().isCreated()) + .andDo(result -> idRef.set(read(result.getResponse().getContentAsString(), "$.id"))); getClient(authToken).perform(get("/api/core/metadatafields/" + idRef.get())) .andExpect(status().isOk()) @@ -193,7 +197,6 @@ public class MetadatafieldRestRepositoryIT extends AbstractControllerIntegration @Test public void createUnauthorized() throws Exception { - context.turnOffAuthorisationSystem(); MetadataFieldRest metadataFieldRest = new MetadataFieldRest(); metadataFieldRest.setElement(ELEMENT); @@ -201,16 +204,15 @@ public class MetadatafieldRestRepositoryIT extends AbstractControllerIntegration metadataFieldRest.setScopeNote(SCOPE_NOTE); getClient() - .perform(post("/api/core/metadatafields") - .param("schemaId", metadataSchema.getID() + "") - .content(new ObjectMapper().writeValueAsBytes(metadataFieldRest)) - .contentType(contentType)) - .andExpect(status().isUnauthorized()); + .perform(post("/api/core/metadatafields") + .param("schemaId", metadataSchema.getID() + "") + .content(new ObjectMapper().writeValueAsBytes(metadataFieldRest)) + .contentType(contentType)) + .andExpect(status().isUnauthorized()); } @Test public void createUnauthorizedEPersonNoAdminRights() throws Exception { - context.turnOffAuthorisationSystem(); MetadataFieldRest metadataFieldRest = new MetadataFieldRest(); metadataFieldRest.setElement(ELEMENT); @@ -233,13 +235,14 @@ public class MetadatafieldRestRepositoryIT extends AbstractControllerIntegration MetadataField metadataField = MetadataFieldBuilder.createMetadataField(context, ELEMENT, QUALIFIER, SCOPE_NOTE) .build(); + context.restoreAuthSystemState(); getClient().perform(get("/api/core/metadatafields/" + metadataField.getID())) .andExpect(status().isOk()); getClient(getAuthToken(admin.getEmail(), password)) - .perform(delete("/api/core/metadatafields/" + metadataField.getID())) - .andExpect(status().isNoContent()); + .perform(delete("/api/core/metadatafields/" + metadataField.getID())) + .andExpect(status().isNoContent()); getClient().perform(get("/api/core/metadatafields/" + metadataField.getID())) .andExpect(status().isNotFound()); @@ -252,11 +255,13 @@ public class MetadatafieldRestRepositoryIT extends AbstractControllerIntegration MetadataField metadataField = MetadataFieldBuilder.createMetadataField(context, ELEMENT, QUALIFIER, SCOPE_NOTE) .build(); + context.restoreAuthSystemState(); + getClient().perform(get("/api/core/metadatafields/" + metadataField.getID())) .andExpect(status().isOk()); getClient() - .perform(delete("/api/core/metadatafields/" + metadataField.getID())) - .andExpect(status().isUnauthorized()); + .perform(delete("/api/core/metadatafields/" + metadataField.getID())) + .andExpect(status().isUnauthorized()); getClient().perform(get("/api/core/metadatafields/" + metadataField.getID())) .andExpect(status().isOk()); @@ -268,6 +273,9 @@ public class MetadatafieldRestRepositoryIT extends AbstractControllerIntegration MetadataField metadataField = MetadataFieldBuilder.createMetadataField(context, ELEMENT, QUALIFIER, SCOPE_NOTE) .build(); + + context.restoreAuthSystemState(); + String token = getAuthToken(eperson.getEmail(), password); @@ -289,6 +297,8 @@ public class MetadatafieldRestRepositoryIT extends AbstractControllerIntegration MetadataField metadataField = MetadataFieldBuilder.createMetadataField(context, ELEMENT, QUALIFIER, SCOPE_NOTE) .build(); + context.restoreAuthSystemState(); + Integer id = metadataField.getID(); getClient(getAuthToken(admin.getEmail(), password)) .perform(delete("/api/core/metadatafields/" + id)) @@ -297,8 +307,8 @@ public class MetadatafieldRestRepositoryIT extends AbstractControllerIntegration assertThat(metadataFieldService.find(context, id), nullValue()); getClient(getAuthToken(admin.getEmail(), password)) - .perform(delete("/api/core/metadatafields/" + id)) - .andExpect(status().isNotFound()); + .perform(delete("/api/core/metadatafields/" + id)) + .andExpect(status().isNotFound()); } @Test @@ -307,6 +317,9 @@ public class MetadatafieldRestRepositoryIT extends AbstractControllerIntegration MetadataField metadataField = MetadataFieldBuilder.createMetadataField(context, ELEMENT, QUALIFIER, SCOPE_NOTE) .build(); + + context.restoreAuthSystemState(); + MetadataFieldRest metadataFieldRest = new MetadataFieldRest(); metadataFieldRest.setId(metadataField.getID()); metadataFieldRest.setElement(ELEMENT_UPDATED); @@ -314,10 +327,10 @@ public class MetadatafieldRestRepositoryIT extends AbstractControllerIntegration metadataFieldRest.setScopeNote(SCOPE_NOTE_UPDATED); getClient(getAuthToken(admin.getEmail(), password)) - .perform(put("/api/core/metadatafields/" + metadataField.getID()) - .content(new ObjectMapper().writeValueAsBytes(metadataFieldRest)) - .contentType(contentType)) - .andExpect(status().isOk()); + .perform(put("/api/core/metadatafields/" + metadataField.getID()) + .content(new ObjectMapper().writeValueAsBytes(metadataFieldRest)) + .contentType(contentType)) + .andExpect(status().isOk()); getClient().perform(get("/api/core/metadatafields/" + metadataField.getID())) .andExpect(status().isOk()) @@ -332,6 +345,9 @@ public class MetadatafieldRestRepositoryIT extends AbstractControllerIntegration MetadataField metadataField = MetadataFieldBuilder.createMetadataField(context, ELEMENT, QUALIFIER, SCOPE_NOTE) .build(); + + context.restoreAuthSystemState(); + MetadataFieldRest metadataFieldRest = new MetadataFieldRest(); metadataFieldRest.setId(metadataField.getID()); metadataFieldRest.setElement(ELEMENT_UPDATED); @@ -339,10 +355,40 @@ public class MetadatafieldRestRepositoryIT extends AbstractControllerIntegration metadataFieldRest.setScopeNote(SCOPE_NOTE_UPDATED); getClient() - .perform(put("/api/core/metadatafields/" + metadataField.getID()) - .content(new ObjectMapper().writeValueAsBytes(metadataFieldRest)) - .contentType(contentType)) - .andExpect(status().isUnauthorized()); + .perform(put("/api/core/metadatafields/" + metadataField.getID()) + .content(new ObjectMapper().writeValueAsBytes(metadataFieldRest)) + .contentType(contentType)) + .andExpect(status().isUnauthorized()); + + getClient().perform(get("/api/core/metadatafields/" + metadataField.getID())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$", MetadataFieldMatcher.matchMetadataFieldByKeys( + metadataSchema.getName(), ELEMENT, QUALIFIER) + )); + + + } + + @Test + public void updateWrongRights() throws Exception { + context.turnOffAuthorisationSystem(); + + MetadataField metadataField = MetadataFieldBuilder.createMetadataField(context, ELEMENT, QUALIFIER, SCOPE_NOTE) + .build(); + + context.restoreAuthSystemState(); + + MetadataFieldRest metadataFieldRest = new MetadataFieldRest(); + metadataFieldRest.setId(metadataField.getID()); + metadataFieldRest.setElement(ELEMENT_UPDATED); + metadataFieldRest.setQualifier(QUALIFIER_UPDATED); + metadataFieldRest.setScopeNote(SCOPE_NOTE_UPDATED); + + getClient(getAuthToken(eperson.getEmail(), password)) + .perform(put("/api/core/metadatafields/" + metadataField.getID()) + .content(new ObjectMapper().writeValueAsBytes(metadataFieldRest)) + .contentType(contentType)) + .andExpect(status().isForbidden()); getClient().perform(get("/api/core/metadatafields/" + metadataField.getID())) .andExpect(status().isOk()) From 0b1216821847a4f78369e1b13622a2ad716a30d0 Mon Sep 17 00:00:00 2001 From: ssolim <30314886+ssolim@users.noreply.github.com> Date: Wed, 6 Feb 2019 09:42:23 +0100 Subject: [PATCH 49/68] XOAI.java skip empty list and dont add it to solr --- dspace-oai/src/main/java/org/dspace/xoai/app/XOAI.java | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/dspace-oai/src/main/java/org/dspace/xoai/app/XOAI.java b/dspace-oai/src/main/java/org/dspace/xoai/app/XOAI.java index 1a6083ca24..32fb2dbca5 100644 --- a/dspace-oai/src/main/java/org/dspace/xoai/app/XOAI.java +++ b/dspace-oai/src/main/java/org/dspace/xoai/app/XOAI.java @@ -315,9 +315,11 @@ public class XOAI { } } System.out.println("Total: " + i + " items"); - server.add(list); - server.commit(true, true); - list.clear(); + if (i > 0) { + server.add(list); + server.commit(true, true); + list.clear(); + } return i; } catch (SolrServerException | IOException ex) { throw new DSpaceSolrIndexerException(ex.getMessage(), ex); From b6f73682a398e58d0ffd1983262d26ff29a5a2eb Mon Sep 17 00:00:00 2001 From: Terry Brady Date: Fri, 15 Feb 2019 15:33:39 -0800 Subject: [PATCH 50/68] Migrate postgres update-sequences.sql --- .../dspace/storage/rdbms/DatabaseUtils.java | 56 +++++++++++++++---- .../rdbms/sqlmigration/postgres/README.md | 12 ++++ .../postgres/update-sequences.sql | 0 dspace/etc/postgres/README.md | 12 ---- 4 files changed, 56 insertions(+), 24 deletions(-) rename {dspace/etc => dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration}/postgres/update-sequences.sql (100%) diff --git a/dspace-api/src/main/java/org/dspace/storage/rdbms/DatabaseUtils.java b/dspace-api/src/main/java/org/dspace/storage/rdbms/DatabaseUtils.java index a8ca129e85..cf9157d21e 100644 --- a/dspace-api/src/main/java/org/dspace/storage/rdbms/DatabaseUtils.java +++ b/dspace-api/src/main/java/org/dspace/storage/rdbms/DatabaseUtils.java @@ -10,6 +10,7 @@ package org.dspace.storage.rdbms; import java.io.BufferedReader; import java.io.File; import java.io.IOException; +import java.io.InputStream; import java.io.InputStreamReader; import java.sql.Connection; import java.sql.DatabaseMetaData; @@ -88,7 +89,8 @@ public class DatabaseUtils { // Usage checks if (argv.length < 1) { System.out.println("\nDatabase action argument is missing."); - System.out.println("Valid actions: 'test', 'info', 'migrate', 'repair', 'validate' or 'clean'"); + System.out.println("Valid actions: 'test', 'info', 'migrate', 'repair', 'validate', " + + "'update-sequences' or 'clean'"); System.out.println("\nOr, type 'database help' for more information.\n"); System.exit(1); } @@ -328,24 +330,54 @@ public class DatabaseUtils { e.printStackTrace(); System.exit(1); } + } else if (argv[0].equalsIgnoreCase("update-sequences")) { + try (Connection connection = dataSource.getConnection()) { + String dbType = getDbType(connection); + String sqlfile = "org/dspace/storage/rdbms/sqlmigration/" + dbType + + "/update-sequences.sql"; + InputStream sqlstream = DatabaseUtils.class.getClassLoader().getResourceAsStream(sqlfile); + if (sqlstream != null) { + StringBuilder sb = new StringBuilder(); + try (BufferedReader br = new BufferedReader(new InputStreamReader(sqlstream))) { + for (String line = br.readLine(); line != null; line = br.readLine()) { + sb.append(line); + } + } + if (sb.length() > 0) { + System.out.println("Running " + sqlfile); + connection.createStatement().execute(sb.toString()); + System.out.println("update-sequences complete"); + } else { + System.err.println(sqlfile + " contains no SQL to execute"); + } + } else { + System.err.println(sqlfile + " not found"); + } + } } else { System.out.println("\nUsage: database [action]"); - System.out.println("Valid actions: 'test', 'info', 'migrate', 'repair' or 'clean'"); + System.out.println("Valid actions: 'test', 'info', 'migrate', 'repair', " + + "'update-sequences' or 'clean'"); System.out.println( - " - test = Performs a test connection to database to validate connection settings"); + " - test = Performs a test connection to database to " + + "validate connection settings"); System.out.println( - " - info / status = Describe basic info/status about database, including validating the " + - "compatibility of this database"); - System.out.println(" - migrate = Migrate the database to the latest version"); + " - info / status = Describe basic info/status about database, including validating the " + + "compatibility of this database"); System.out.println( - " - repair = Attempt to repair any previously failed database migrations or checksum " + - "mismatches (via Flyway repair)"); + " - migrate = Migrate the database to the latest version"); System.out.println( - " - validate = Validate current database's migration status (via Flyway validate), " + - "validating all migration checksums."); + " - repair = Attempt to repair any previously failed database " + + "migrations or checksum mismatches (via Flyway repair)"); System.out.println( - " - clean = DESTROY all data and tables in database (WARNING there is no going back!). " + - "Requires 'db.cleanDisabled=false' setting in config."); + " - validate = Validate current database's migration status (via Flyway validate), " + + "validating all migration checksums."); + System.out.println( + " - update-sequences = Update database sequences after running AIP ingest."); + System.out.println( + " - clean = DESTROY all data and tables in database " + + "(WARNING there is no going back!). " + + "Requires 'db.cleanDisabled=false' setting in config."); System.out.println(""); System.exit(0); } diff --git a/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/postgres/README.md b/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/postgres/README.md index 8f549b9c45..72eb279912 100644 --- a/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/postgres/README.md +++ b/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/postgres/README.md @@ -17,4 +17,16 @@ not realize you manually ran one or more scripts. Please see the Flyway Documentation for more information: http://flywaydb.org/ +## Using the update-sequences.sql script + +The `update-sequences.sql` script in this directory may still be used to update +your internal database counts if you feel they have gotten out of "sync". This +may sometimes occur after large restores of content (e.g. when using the DSpace +[AIP Backup and Restore](https://wiki.duraspace.org/display/DSDOC5x/AIP+Backup+and+Restore) +feature). + +This `update-sequences.sql` script can be executed by running +"dspace database update-sequences". It will not harm your +database (or its contents) in any way. It just ensures all database counts (i.e. +sequences) are properly set to the next available value. diff --git a/dspace/etc/postgres/update-sequences.sql b/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/postgres/update-sequences.sql similarity index 100% rename from dspace/etc/postgres/update-sequences.sql rename to dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/postgres/update-sequences.sql diff --git a/dspace/etc/postgres/README.md b/dspace/etc/postgres/README.md index 7d4a9ad2da..976f245931 100644 --- a/dspace/etc/postgres/README.md +++ b/dspace/etc/postgres/README.md @@ -19,15 +19,3 @@ database up-to-date. These scripts are now located in the source code at: As Flyway automates the upgrade process, you should NEVER run these SQL scripts manually. For more information, please see the `README.md` in the scripts directory. - -## Using the update-sequences.sql script - -The `update-sequences.sql` script in this directory may still be used to update -your internal database counts if you feel they have gotten out of "sync". This -may sometimes occur after large restores of content (e.g. when using the DSpace -[AIP Backup and Restore](https://wiki.duraspace.org/display/DSDOC5x/AIP+Backup+and+Restore) -feature). - -This `update-sequences.sql` script can be run manually. It will not harm your -database (or its contents) in any way. It just ensures all database counts (i.e. -sequences) are properly set to the next available value. From e421a7c7c7580d9ef2f071d95f6790c39237152e Mon Sep 17 00:00:00 2001 From: Terry Brady Date: Tue, 19 Feb 2019 06:26:12 -0800 Subject: [PATCH 51/68] apply review feedback --- .../dspace/storage/rdbms/DatabaseUtils.java | 2 +- .../sqlmigration/oracle/update-sequences.sql | 104 ++++++++++++++++++ .../postgres/update-sequences.sql | 7 ++ 3 files changed, 112 insertions(+), 1 deletion(-) create mode 100644 dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/oracle/update-sequences.sql diff --git a/dspace-api/src/main/java/org/dspace/storage/rdbms/DatabaseUtils.java b/dspace-api/src/main/java/org/dspace/storage/rdbms/DatabaseUtils.java index cf9157d21e..a32662f83c 100644 --- a/dspace-api/src/main/java/org/dspace/storage/rdbms/DatabaseUtils.java +++ b/dspace-api/src/main/java/org/dspace/storage/rdbms/DatabaseUtils.java @@ -333,7 +333,7 @@ public class DatabaseUtils { } else if (argv[0].equalsIgnoreCase("update-sequences")) { try (Connection connection = dataSource.getConnection()) { String dbType = getDbType(connection); - String sqlfile = "org/dspace/storage/rdbms/sqlmigration/" + dbType + + String sqlfile = "/org/dspace/storage/rdbms/sqlmigration/" + dbType + "/update-sequences.sql"; InputStream sqlstream = DatabaseUtils.class.getClassLoader().getResourceAsStream(sqlfile); if (sqlstream != null) { diff --git a/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/oracle/update-sequences.sql b/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/oracle/update-sequences.sql new file mode 100644 index 0000000000..4a0402488f --- /dev/null +++ b/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/oracle/update-sequences.sql @@ -0,0 +1,104 @@ +-- +-- 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/ +-- +-- update-sequences.sql +-- +-- Copyright (c) 2002-2016, The DSpace Foundation. All rights reserved. +-- +-- Redistribution and use in source and binary forms, with or without +-- modification, are permitted provided that the following conditions are +-- met: +-- +-- - Redistributions of source code must retain the above copyright +-- notice, this list of conditions and the following disclaimer. +-- +-- - Redistributions in binary form must reproduce the above copyright +-- notice, this list of conditions and the following disclaimer in the +-- documentation and/or other materials provided with the distribution. +-- +-- - Neither the name of the DSpace Foundation nor the names of its +-- contributors may be used to endorse or promote products derived from +-- this software without specific prior written permission. +-- +-- THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +-- ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +-- LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +-- A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +-- HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, +-- INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, +-- BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS +-- OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +-- ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR +-- TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE +-- USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH +-- DAMAGE. + +-- SQL code to update the ID (primary key) generating sequences, if some +-- import operation has set explicit IDs. +-- +-- Sequences are used to generate IDs for new rows in the database. If a +-- bulk import operation, such as an SQL dump, specifies primary keys for +-- imported data explicitly, the sequences are out of sync and need updating. +-- This SQL code does just that. +-- +-- This should rarely be needed; any bulk import should be performed using the +-- org.dspace.content API which is safe to use concurrently and in multiple +-- JVMs. The SQL code below will typically only be required after a direct +-- SQL data dump from a backup or somesuch. + +-- Depends on being run from sqlplus with incseq.sql in the current path +-- you can find incseq.sql at: http://www.akadia.com/services/scripts/incseq.sql +-- Here that script was renamed to updateseq.sql. + +DECLARE + PROCEDURE updateseq ( seq IN VARCHAR, + tbl IN VARCHAR, + attr IN VARCHAR, + cond IN VARCHAR ) IS + curr NUMBER := 0; + BEGIN + EXECUTE IMMEDIATE 'SELECT max(' + || attr + || ') INTO curr FROM ' + || tbl || ' ' || cond; + curr := curr + 1; + EXECUTE IMMEDIATE 'DROP SEQUENCE ' || seq; + EXECUTE IMMEDIATE 'CREATE SEQUENCE ' + || seq + || ' START WITH ' + || NVL(curr, 1); + END; +BEGIN + updateseq('seq', 'tbl', 'attr', ''); +END; +/ + +execute updateseq(bitstreamformatregistry_seq, bitstreamformatregistry, bitstream_format_id, ""); +execute updateseq(fileextension_seq, fileextension, file_extension_id, ""); +execute updateseq(resourcepolicy_seq, resourcepolicy, policy_id, ""); +execute updateseq(workspaceitem_seq, workspaceitem, workspace_item_id, ""); +execute updateseq(workflowitem_seq, workflowitem, workflow_id, ""); +execute updateseq(tasklistitem_seq, tasklistitem, tasklist_id, ""); +execute updateseq(registrationdata_seq, registrationdata, registrationdata_id, ""); +execute updateseq(subscription_seq, subscription, subscription_id, ""); +execute updateseq(metadatafieldregistry_seq, metadatafieldregistry, metadata_field_id, ""); +execute updateseq(metadatavalue_seq, metadatavalue, metadata_value_id, ""); +execute updateseq(metadataschemaregistry_seq, metadataschemaregistry, metadata_schema_id, ""); +execute updateseq(harvested_collection_seq, harvested_collection, id, ""); +execute updateseq(harvested_item_seq, harvested_item, id, ""); +execute updateseq(webapp_seq, webapp, webapp_id, ""); +execute updateseq(requestitem_seq, requestitem, requestitem_id, ""); +execute updateseq(handle_id_seq, handle, handle_id, ""); + +-- Handle Sequence is a special case. Since Handles minted by DSpace use the 'handle_seq', +-- we need to ensure the next assigned handle will *always* be unique. So, 'handle_seq' +-- always needs to be set to the value of the *largest* handle suffix. That way when the +-- next handle is assigned, it will use the next largest number. This query does the following: +-- For all 'handle' values which have a number in their suffix (after '/'), find the maximum +-- suffix value, convert it to a number, and set the 'handle_seq' to start at the next value +-- (see updateseq.sql script for more) +execute updateseq(handle_seq, handle, "to_number(regexp_replace(handle, '.*/', ''), '999999999999')", "WHERE REGEXP_LIKE(handle, '^.*/[0123456789]*$')"); diff --git a/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/postgres/update-sequences.sql b/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/postgres/update-sequences.sql index 9928dbf319..e479993201 100644 --- a/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/postgres/update-sequences.sql +++ b/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/postgres/update-sequences.sql @@ -1,4 +1,11 @@ -- +-- 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/ +-- +-- -- update-sequences.sql -- -- Copyright (c) 2002-2016, The DSpace Foundation. All rights reserved. From 5f47771e4e95597e3286db73815f7a329036fa04 Mon Sep 17 00:00:00 2001 From: Terry Brady Date: Tue, 19 Feb 2019 06:26:28 -0800 Subject: [PATCH 52/68] remove /etc directory --- dspace/etc/oracle/README.md | 33 ------------ dspace/etc/oracle/update-sequences.sql | 75 -------------------------- dspace/etc/oracle/updateseq.sql | 30 ----------- dspace/etc/postgres/README.md | 21 -------- 4 files changed, 159 deletions(-) delete mode 100644 dspace/etc/oracle/README.md delete mode 100644 dspace/etc/oracle/update-sequences.sql delete mode 100644 dspace/etc/oracle/updateseq.sql delete mode 100644 dspace/etc/postgres/README.md diff --git a/dspace/etc/oracle/README.md b/dspace/etc/oracle/README.md deleted file mode 100644 index 0ed2a66aa9..0000000000 --- a/dspace/etc/oracle/README.md +++ /dev/null @@ -1,33 +0,0 @@ -# DSpace Database Now Upgrades Automatically - -AS OF DSPACE 5, the DSpace database now upgrades itself AUTOMATICALLY. - -Therefore, all `database_schema*.sql` files have been removed. Starting -with DSpace 4.x -> 5.0 upgrade, you will no longer need to manually run any -SQL scripts to upgrade your database. - -Please see the [5.0 Upgrade Instructions](https://wiki.duraspace.org/display/DSDOC5x/Upgrading+DSpace) -for more information on upgrading to DSpace 5. - - -## More info on automatic database upgrades - -As of DSpace 5.0, we now use [Flyway DB](http://flywaydb.org/) along with the -SQL scripts embedded in the `dspace-api.jar` to automatically keep your DSpace -database up-to-date. These scripts are now located in the source code at: -`[dspace-src]/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/oracle` - -As Flyway automates the upgrade process, you should NEVER run these SQL scripts -manually. For more information, please see the `README.md` in the scripts directory. - -## Using the update-sequences.sql script - -The `update-sequences.sql` script in this directory may still be used to update -your internal database counts if you feel they have gotten out of "sync". This -may sometimes occur after large restores of content (e.g. when using the DSpace -[AIP Backup and Restore](https://wiki.duraspace.org/display/DSDOC5x/AIP+Backup+and+Restore) -feature). - -This `update-sequences.sql` script can be run manually. It will not harm your -database (or its contents) in any way. It just ensures all database counts (i.e. -sequences) are properly set to the next available value. diff --git a/dspace/etc/oracle/update-sequences.sql b/dspace/etc/oracle/update-sequences.sql deleted file mode 100644 index d40f7665a7..0000000000 --- a/dspace/etc/oracle/update-sequences.sql +++ /dev/null @@ -1,75 +0,0 @@ --- --- update-sequences.sql --- --- Copyright (c) 2002-2016, The DSpace Foundation. All rights reserved. --- --- Redistribution and use in source and binary forms, with or without --- modification, are permitted provided that the following conditions are --- met: --- --- - Redistributions of source code must retain the above copyright --- notice, this list of conditions and the following disclaimer. --- --- - Redistributions in binary form must reproduce the above copyright --- notice, this list of conditions and the following disclaimer in the --- documentation and/or other materials provided with the distribution. --- --- - Neither the name of the DSpace Foundation nor the names of its --- contributors may be used to endorse or promote products derived from --- this software without specific prior written permission. --- --- THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS --- ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT --- LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR --- A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT --- HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, --- INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, --- BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS --- OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND --- ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR --- TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE --- USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH --- DAMAGE. - --- SQL code to update the ID (primary key) generating sequences, if some --- import operation has set explicit IDs. --- --- Sequences are used to generate IDs for new rows in the database. If a --- bulk import operation, such as an SQL dump, specifies primary keys for --- imported data explicitly, the sequences are out of sync and need updating. --- This SQL code does just that. --- --- This should rarely be needed; any bulk import should be performed using the --- org.dspace.content API which is safe to use concurrently and in multiple --- JVMs. The SQL code below will typically only be required after a direct --- SQL data dump from a backup or somesuch. - --- Depends on being run from sqlplus with incseq.sql in the current path --- you can find incseq.sql at: http://www.akadia.com/services/scripts/incseq.sql --- Here that script was renamed to updateseq.sql. - -@updateseq.sql bitstreamformatregistry_seq bitstreamformatregistry bitstream_format_id "" -@updateseq.sql fileextension_seq fileextension file_extension_id "" -@updateseq.sql resourcepolicy_seq resourcepolicy policy_id "" -@updateseq.sql workspaceitem_seq workspaceitem workspace_item_id "" -@updateseq.sql workflowitem_seq workflowitem workflow_id "" -@updateseq.sql tasklistitem_seq tasklistitem tasklist_id "" -@updateseq.sql registrationdata_seq registrationdata registrationdata_id "" -@updateseq.sql subscription_seq subscription subscription_id "" -@updateseq.sql metadatafieldregistry_seq metadatafieldregistry metadata_field_id "" -@updateseq.sql metadatavalue_seq metadatavalue metadata_value_id "" -@updateseq.sql metadataschemaregistry_seq metadataschemaregistry metadata_schema_id "" -@updateseq.sql harvested_collection_seq harvested_collection id "" -@updateseq.sql harvested_item_seq harvested_item id "" -@updateseq.sql webapp_seq webapp webapp_id "" -@updateseq.sql requestitem_seq requestitem requestitem_id "" -@updateseq.sql handle_id_seq handle handle_id "" - --- Handle Sequence is a special case. Since Handles minted by DSpace use the 'handle_seq', --- we need to ensure the next assigned handle will *always* be unique. So, 'handle_seq' --- always needs to be set to the value of the *largest* handle suffix. That way when the --- next handle is assigned, it will use the next largest number. This query does the following: --- For all 'handle' values which have a number in their suffix (after '/'), find the maximum --- suffix value, convert it to a number, and set the 'handle_seq' to start at the next value --- (see updateseq.sql script for more) -@updateseq.sql handle_seq handle "to_number(regexp_replace(handle, '.*/', ''), '999999999999')" "WHERE REGEXP_LIKE(handle, '^.*/[0123456789]*$')" diff --git a/dspace/etc/oracle/updateseq.sql b/dspace/etc/oracle/updateseq.sql deleted file mode 100644 index 49d3701cd2..0000000000 --- a/dspace/etc/oracle/updateseq.sql +++ /dev/null @@ -1,30 +0,0 @@ --- ############################################################################################# --- --- %Purpose: Set a sequence to the max value of a given attribute --- --- ############################################################################################# --- --- Paramters: --- 1: sequence name --- 2: table name --- 3: attribute name --- --- Sample usage: --- @updateseq.sql my_sequence my_table my_attribute where-clause --- --------------------------------------------------------------------------------- --- -SET SERVEROUTPUT ON SIZE 1000000; --- -DECLARE - curr NUMBER := 0; -BEGIN - SELECT max(&3) INTO curr FROM &2 &4; - - curr := curr + 1; - - EXECUTE IMMEDIATE 'DROP SEQUENCE &1'; - - EXECUTE IMMEDIATE 'CREATE SEQUENCE &1 START WITH ' || NVL(curr,1); -END; -/ diff --git a/dspace/etc/postgres/README.md b/dspace/etc/postgres/README.md deleted file mode 100644 index 976f245931..0000000000 --- a/dspace/etc/postgres/README.md +++ /dev/null @@ -1,21 +0,0 @@ -# DSpace Database Now Upgrades Automatically - -AS OF DSPACE 5, the DSpace database now upgrades itself AUTOMATICALLY. - -Therefore, all `database_schema*.sql` files have been removed. Starting -with DSpace 4.x -> 5.0 upgrade, you will no longer need to manually run any -SQL scripts to upgrade your database. - -Please see the [5.0 Upgrade Instructions](https://wiki.duraspace.org/display/DSDOC5x/Upgrading+DSpace) -for more information on upgrading to DSpace 5. - - -## More info on automatic database upgrades - -As of DSpace 5.0, we now use [Flyway DB](http://flywaydb.org/) along with the -SQL scripts embedded in the `dspace-api.jar` to automatically keep your DSpace -database up-to-date. These scripts are now located in the source code at: -`[dspace-src]/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/postgres` - -As Flyway automates the upgrade process, you should NEVER run these SQL scripts -manually. For more information, please see the `README.md` in the scripts directory. From 22cc5469a23f6f86d4cb330030f8a542c8149efd Mon Sep 17 00:00:00 2001 From: Terry Brady Date: Tue, 19 Feb 2019 06:30:25 -0800 Subject: [PATCH 53/68] simplify string extract from stream --- .../java/org/dspace/storage/rdbms/DatabaseUtils.java | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/dspace-api/src/main/java/org/dspace/storage/rdbms/DatabaseUtils.java b/dspace-api/src/main/java/org/dspace/storage/rdbms/DatabaseUtils.java index a32662f83c..2dfac7fa93 100644 --- a/dspace-api/src/main/java/org/dspace/storage/rdbms/DatabaseUtils.java +++ b/dspace-api/src/main/java/org/dspace/storage/rdbms/DatabaseUtils.java @@ -24,6 +24,7 @@ import java.util.regex.Matcher; import java.util.regex.Pattern; import javax.sql.DataSource; +import org.apache.commons.io.IOUtils; import org.apache.commons.lang3.StringUtils; import org.apache.logging.log4j.Logger; import org.dspace.core.Context; @@ -337,15 +338,10 @@ public class DatabaseUtils { "/update-sequences.sql"; InputStream sqlstream = DatabaseUtils.class.getClassLoader().getResourceAsStream(sqlfile); if (sqlstream != null) { - StringBuilder sb = new StringBuilder(); - try (BufferedReader br = new BufferedReader(new InputStreamReader(sqlstream))) { - for (String line = br.readLine(); line != null; line = br.readLine()) { - sb.append(line); - } - } - if (sb.length() > 0) { + String s = IOUtils.toString(sqlstream, "UTF-8"); + if (!s.isEmpty()) { System.out.println("Running " + sqlfile); - connection.createStatement().execute(sb.toString()); + connection.createStatement().execute(s); System.out.println("update-sequences complete"); } else { System.err.println(sqlfile + " contains no SQL to execute"); From 4ac06271c874a36e0c7f51e3b274ad9483b4dfa5 Mon Sep 17 00:00:00 2001 From: Terry Brady Date: Tue, 19 Feb 2019 06:53:30 -0800 Subject: [PATCH 54/68] attempt to fix headers --- .../sqlmigration/oracle/update-sequences.sql | 31 ------------------ .../postgres/update-sequences.sql | 32 ------------------- 2 files changed, 63 deletions(-) diff --git a/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/oracle/update-sequences.sql b/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/oracle/update-sequences.sql index 4a0402488f..1f70d409f4 100644 --- a/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/oracle/update-sequences.sql +++ b/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/oracle/update-sequences.sql @@ -5,37 +5,6 @@ -- -- http://www.dspace.org/license/ -- --- update-sequences.sql --- --- Copyright (c) 2002-2016, The DSpace Foundation. All rights reserved. --- --- Redistribution and use in source and binary forms, with or without --- modification, are permitted provided that the following conditions are --- met: --- --- - Redistributions of source code must retain the above copyright --- notice, this list of conditions and the following disclaimer. --- --- - Redistributions in binary form must reproduce the above copyright --- notice, this list of conditions and the following disclaimer in the --- documentation and/or other materials provided with the distribution. --- --- - Neither the name of the DSpace Foundation nor the names of its --- contributors may be used to endorse or promote products derived from --- this software without specific prior written permission. --- --- THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS --- ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT --- LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR --- A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT --- HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, --- INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, --- BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS --- OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND --- ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR --- TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE --- USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH --- DAMAGE. -- SQL code to update the ID (primary key) generating sequences, if some -- import operation has set explicit IDs. diff --git a/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/postgres/update-sequences.sql b/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/postgres/update-sequences.sql index e479993201..c1aaadce86 100644 --- a/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/postgres/update-sequences.sql +++ b/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/postgres/update-sequences.sql @@ -5,38 +5,6 @@ -- -- http://www.dspace.org/license/ -- --- --- update-sequences.sql --- --- Copyright (c) 2002-2016, The DSpace Foundation. All rights reserved. --- --- Redistribution and use in source and binary forms, with or without --- modification, are permitted provided that the following conditions are --- met: --- --- - Redistributions of source code must retain the above copyright --- notice, this list of conditions and the following disclaimer. --- --- - Redistributions in binary form must reproduce the above copyright --- notice, this list of conditions and the following disclaimer in the --- documentation and/or other materials provided with the distribution. --- --- Neither the name of the DSpace Foundation nor the names of its --- contributors may be used to endorse or promote products derived from --- this software without specific prior written permission. --- --- THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS --- ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT --- LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR --- A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT --- HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, --- INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, --- BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS --- OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND --- ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR --- TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE --- USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH --- DAMAGE. -- SQL code to update the ID (primary key) generating sequences, if some -- import operation has set explicit IDs. From f829b7df5a1ee75077003e65706ecf0890f1ab61 Mon Sep 17 00:00:00 2001 From: Terry Brady Date: Tue, 19 Feb 2019 07:11:23 -0800 Subject: [PATCH 55/68] remove dspace/etc from build steps --- dspace/src/main/config/build.xml | 27 --------------------------- 1 file changed, 27 deletions(-) diff --git a/dspace/src/main/config/build.xml b/dspace/src/main/config/build.xml index ab13191561..8b4f588dd0 100644 --- a/dspace/src/main/config/build.xml +++ b/dspace/src/main/config/build.xml @@ -569,16 +569,6 @@ Common usage: - - - - - - - - - - @@ -593,10 +583,6 @@ Common usage: ${dspace.dir}/lib.bak-${build.date} - ${dspace.dir}/etc was backed up to - - ${dspace.dir}/etc.bak-${build.date} - Please review these directories and delete if no longer needed. ==================================================================== @@ -728,8 +714,6 @@ Common usage: - - @@ -821,17 +805,6 @@ Common usage: - - - - - - - - - From 89c1cc77291977329ba7f71eb3817ddf6604096f Mon Sep 17 00:00:00 2001 From: Terry Brady Date: Tue, 19 Feb 2019 07:34:56 -0800 Subject: [PATCH 56/68] remove /org --- .../src/main/java/org/dspace/storage/rdbms/DatabaseUtils.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dspace-api/src/main/java/org/dspace/storage/rdbms/DatabaseUtils.java b/dspace-api/src/main/java/org/dspace/storage/rdbms/DatabaseUtils.java index 2dfac7fa93..f4b0936c6f 100644 --- a/dspace-api/src/main/java/org/dspace/storage/rdbms/DatabaseUtils.java +++ b/dspace-api/src/main/java/org/dspace/storage/rdbms/DatabaseUtils.java @@ -334,7 +334,7 @@ public class DatabaseUtils { } else if (argv[0].equalsIgnoreCase("update-sequences")) { try (Connection connection = dataSource.getConnection()) { String dbType = getDbType(connection); - String sqlfile = "/org/dspace/storage/rdbms/sqlmigration/" + dbType + + String sqlfile = "org/dspace/storage/rdbms/sqlmigration/" + dbType + "/update-sequences.sql"; InputStream sqlstream = DatabaseUtils.class.getClassLoader().getResourceAsStream(sqlfile); if (sqlstream != null) { From 7773b46aca60ed129e6fe6a93f18a77a1538e05e Mon Sep 17 00:00:00 2001 From: Terry Brady Date: Tue, 19 Feb 2019 10:16:21 -0800 Subject: [PATCH 57/68] update sql --- .../sqlmigration/oracle/update-sequences.sql | 68 +++++++++++++------ 1 file changed, 46 insertions(+), 22 deletions(-) diff --git a/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/oracle/update-sequences.sql b/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/oracle/update-sequences.sql index 1f70d409f4..5b7b9324b2 100644 --- a/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/oracle/update-sequences.sql +++ b/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/oracle/update-sequences.sql @@ -23,7 +23,9 @@ -- you can find incseq.sql at: http://www.akadia.com/services/scripts/incseq.sql -- Here that script was renamed to updateseq.sql. -DECLARE +-- + +create or replace PROCEDURE updateseq ( seq IN VARCHAR, tbl IN VARCHAR, attr IN VARCHAR, @@ -41,27 +43,49 @@ DECLARE || ' START WITH ' || NVL(curr, 1); END; -BEGIN - updateseq('seq', 'tbl', 'attr', ''); -END; -/ -execute updateseq(bitstreamformatregistry_seq, bitstreamformatregistry, bitstream_format_id, ""); -execute updateseq(fileextension_seq, fileextension, file_extension_id, ""); -execute updateseq(resourcepolicy_seq, resourcepolicy, policy_id, ""); -execute updateseq(workspaceitem_seq, workspaceitem, workspace_item_id, ""); -execute updateseq(workflowitem_seq, workflowitem, workflow_id, ""); -execute updateseq(tasklistitem_seq, tasklistitem, tasklist_id, ""); -execute updateseq(registrationdata_seq, registrationdata, registrationdata_id, ""); -execute updateseq(subscription_seq, subscription, subscription_id, ""); -execute updateseq(metadatafieldregistry_seq, metadatafieldregistry, metadata_field_id, ""); -execute updateseq(metadatavalue_seq, metadatavalue, metadata_value_id, ""); -execute updateseq(metadataschemaregistry_seq, metadataschemaregistry, metadata_schema_id, ""); -execute updateseq(harvested_collection_seq, harvested_collection, id, ""); -execute updateseq(harvested_item_seq, harvested_item, id, ""); -execute updateseq(webapp_seq, webapp, webapp_id, ""); -execute updateseq(requestitem_seq, requestitem, requestitem_id, ""); -execute updateseq(handle_id_seq, handle, handle_id, ""); +create or replace procedure updateseq( + seq IN VARCHAR, + tbl IN VARCHAR, + attr IN VARCHAR, + cond IN VARCHAR ) +IS + l_val number := 0; + new_val number:= 0; + offset number:=0; +BEGIN + execute immediate + 'select ' || seq || '.nextval from dual' INTO l_val; + + execute immediate + 'select max(' || attr || ') from ' || tbl || ' ' || cond || '' INTO new_val; + + execute immediate + 'alter sequence ' || seq || ' increment by ' || (new_val - l_val + 1) || ' minvalue 0'; + + execute immediate + 'select ' || seq || '.nextval from dual' INTO l_val; + + execute immediate + 'alter sequence ' || seq || ' increment by 1 minvalue 0'; +END; + +execute updateseq('bitstreamformatregistry_seq', 'bitstreamformatregistry', 'bitstream_format_id', ''); +execute updateseq('fileextension_seq', 'fileextension', 'file_extension_id', ''); +execute updateseq('resourcepolicy_seq', 'resourcepolicy', 'policy_id', ''); +execute updateseq('workspaceitem_seq', 'workspaceitem', 'workspace_item_id', ''); +execute updateseq('workflowitem_seq', 'workflowitem', 'workflow_id', ''); +execute updateseq('tasklistitem_seq', 'tasklistitem', 'tasklist_id', ''); +execute updateseq('registrationdata_seq', 'registrationdata', 'registrationdata_id', ''); +execute updateseq('subscription_seq', 'subscription', 'subscription_id', ''); +execute updateseq('metadatafieldregistry_seq', 'metadatafieldregistry', 'metadata_field_id', ''); +execute updateseq('metadatavalue_seq', 'metadatavalue', 'metadata_value_id', ''); +execute updateseq('metadataschemaregistry_seq', 'metadataschemaregistry', 'metadata_schema_id', ''); +execute updateseq('harvested_collection_seq', 'harvested_collection', 'id', ''); +execute updateseq('harvested_item_seq', 'harvested_item', 'id', ''); +execute updateseq('webapp_seq', 'webapp', 'webapp_id', ''); +execute updateseq('requestitem_seq', 'requestitem', 'requestitem_id', ''); +execute updateseq('handle_id_seq', 'handle', 'handle_id', ''); -- Handle Sequence is a special case. Since Handles minted by DSpace use the 'handle_seq', -- we need to ensure the next assigned handle will *always* be unique. So, 'handle_seq' @@ -70,4 +94,4 @@ execute updateseq(handle_id_seq, handle, handle_id, ""); -- For all 'handle' values which have a number in their suffix (after '/'), find the maximum -- suffix value, convert it to a number, and set the 'handle_seq' to start at the next value -- (see updateseq.sql script for more) -execute updateseq(handle_seq, handle, "to_number(regexp_replace(handle, '.*/', ''), '999999999999')", "WHERE REGEXP_LIKE(handle, '^.*/[0123456789]*$')"); +execute updateseq('handle_seq', 'handle', "to_number(regexp_replace(handle, '.*/', ''), '999999999999')", "WHERE REGEXP_LIKE(handle, '^.*/[0123456789]*$')"); From 330283565098296a6dbe05699b567bcfdeb64b9b Mon Sep 17 00:00:00 2001 From: "Mark H. Wood" Date: Wed, 20 Feb 2019 11:23:21 -0500 Subject: [PATCH 58/68] [DS-4167] Fix various problems with Oracle script. --- .../sqlmigration/oracle/update-sequences.sql | 80 ++++++++++--------- 1 file changed, 43 insertions(+), 37 deletions(-) diff --git a/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/oracle/update-sequences.sql b/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/oracle/update-sequences.sql index 1f70d409f4..cc706416ab 100644 --- a/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/oracle/update-sequences.sql +++ b/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/oracle/update-sequences.sql @@ -19,55 +19,61 @@ -- JVMs. The SQL code below will typically only be required after a direct -- SQL data dump from a backup or somesuch. --- Depends on being run from sqlplus with incseq.sql in the current path --- you can find incseq.sql at: http://www.akadia.com/services/scripts/incseq.sql --- Here that script was renamed to updateseq.sql. +-- The 'updateseq' procedure was derived from incseq.sql found at: +-- http://www.akadia.com/services/scripts/incseq.sql DECLARE PROCEDURE updateseq ( seq IN VARCHAR, tbl IN VARCHAR, attr IN VARCHAR, - cond IN VARCHAR ) IS + cond IN VARCHAR DEFAULT '' ) IS curr NUMBER := 0; BEGIN - EXECUTE IMMEDIATE 'SELECT max(' - || attr - || ') INTO curr FROM ' - || tbl || ' ' || cond; + EXECUTE IMMEDIATE 'SELECT max(' || attr + || ') FROM ' || tbl + || ' ' || cond + INTO curr; curr := curr + 1; EXECUTE IMMEDIATE 'DROP SEQUENCE ' || seq; EXECUTE IMMEDIATE 'CREATE SEQUENCE ' || seq || ' START WITH ' || NVL(curr, 1); - END; + END updateseq; + BEGIN - updateseq('seq', 'tbl', 'attr', ''); + updateseq('bitstreamformatregistry_seq', 'bitstreamformatregistry', + 'bitstream_format_id'); + updateseq('fileextension_seq', 'fileextension', 'file_extension_id'); + updateseq('resourcepolicy_seq', 'resourcepolicy', 'policy_id'); + updateseq('workspaceitem_seq', 'workspaceitem', 'workspace_item_id'); + updateseq('workflowitem_seq', 'workflowitem', 'workflow_id'); + updateseq('tasklistitem_seq', 'tasklistitem', 'tasklist_id'); + updateseq('registrationdata_seq', 'registrationdata', + 'registrationdata_id'); + updateseq('subscription_seq', 'subscription', 'subscription_id'); + updateseq('metadatafieldregistry_seq', 'metadatafieldregistry', + 'metadata_field_id'); + updateseq('metadatavalue_seq', 'metadatavalue', 'metadata_value_id'); + updateseq('metadataschemaregistry_seq', 'metadataschemaregistry', + 'metadata_schema_id'); + updateseq('harvested_collection_seq', 'harvested_collection', 'id'); + updateseq('harvested_item_seq', 'harvested_item', 'id'); + updateseq('webapp_seq', 'webapp', 'webapp_id'); + updateseq('requestitem_seq', 'requestitem', 'requestitem_id'); + updateseq('handle_id_seq', 'handle', 'handle_id'); + + -- Handle Sequence is a special case. Since Handles minted by DSpace + -- use the 'handle_seq', we need to ensure the next assigned handle + -- will *always* be unique. So, 'handle_seq' always needs to be set + -- to the value of the *largest* handle suffix. That way when the + -- next handle is assigned, it will use the next largest number. This + -- query does the following: + -- For all 'handle' values which have a number in their suffix + -- (after '/'), find the maximum suffix value, convert it to a + -- number, and set the 'handle_seq' to start at the next value (see + -- updateseq above for more). + updateseq('handle_seq', 'handle', + q'{to_number(regexp_replace(handle, '.*/', ''), '999999999999')}', + q'{WHERE REGEXP_LIKE(handle, '^.*/[0123456789]*$')}'); END; -/ - -execute updateseq(bitstreamformatregistry_seq, bitstreamformatregistry, bitstream_format_id, ""); -execute updateseq(fileextension_seq, fileextension, file_extension_id, ""); -execute updateseq(resourcepolicy_seq, resourcepolicy, policy_id, ""); -execute updateseq(workspaceitem_seq, workspaceitem, workspace_item_id, ""); -execute updateseq(workflowitem_seq, workflowitem, workflow_id, ""); -execute updateseq(tasklistitem_seq, tasklistitem, tasklist_id, ""); -execute updateseq(registrationdata_seq, registrationdata, registrationdata_id, ""); -execute updateseq(subscription_seq, subscription, subscription_id, ""); -execute updateseq(metadatafieldregistry_seq, metadatafieldregistry, metadata_field_id, ""); -execute updateseq(metadatavalue_seq, metadatavalue, metadata_value_id, ""); -execute updateseq(metadataschemaregistry_seq, metadataschemaregistry, metadata_schema_id, ""); -execute updateseq(harvested_collection_seq, harvested_collection, id, ""); -execute updateseq(harvested_item_seq, harvested_item, id, ""); -execute updateseq(webapp_seq, webapp, webapp_id, ""); -execute updateseq(requestitem_seq, requestitem, requestitem_id, ""); -execute updateseq(handle_id_seq, handle, handle_id, ""); - --- Handle Sequence is a special case. Since Handles minted by DSpace use the 'handle_seq', --- we need to ensure the next assigned handle will *always* be unique. So, 'handle_seq' --- always needs to be set to the value of the *largest* handle suffix. That way when the --- next handle is assigned, it will use the next largest number. This query does the following: --- For all 'handle' values which have a number in their suffix (after '/'), find the maximum --- suffix value, convert it to a number, and set the 'handle_seq' to start at the next value --- (see updateseq.sql script for more) -execute updateseq(handle_seq, handle, "to_number(regexp_replace(handle, '.*/', ''), '999999999999')", "WHERE REGEXP_LIKE(handle, '^.*/[0123456789]*$')"); From 35e98f0d4b71f0806576f8f4c955b1cce70fe8bb Mon Sep 17 00:00:00 2001 From: Terry Brady Date: Wed, 20 Feb 2019 08:40:34 -0800 Subject: [PATCH 59/68] v1 oracle sql --- .../sqlmigration/oracle/update-sequences.sql | 61 +++++++++++-------- 1 file changed, 37 insertions(+), 24 deletions(-) diff --git a/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/oracle/update-sequences.sql b/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/oracle/update-sequences.sql index 5b7b9324b2..780a78074a 100644 --- a/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/oracle/update-sequences.sql +++ b/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/oracle/update-sequences.sql @@ -25,43 +25,27 @@ -- -create or replace - PROCEDURE updateseq ( seq IN VARCHAR, - tbl IN VARCHAR, - attr IN VARCHAR, - cond IN VARCHAR ) IS - curr NUMBER := 0; - BEGIN - EXECUTE IMMEDIATE 'SELECT max(' - || attr - || ') INTO curr FROM ' - || tbl || ' ' || cond; - curr := curr + 1; - EXECUTE IMMEDIATE 'DROP SEQUENCE ' || seq; - EXECUTE IMMEDIATE 'CREATE SEQUENCE ' - || seq - || ' START WITH ' - || NVL(curr, 1); - END; - create or replace procedure updateseq( seq IN VARCHAR, tbl IN VARCHAR, - attr IN VARCHAR, - cond IN VARCHAR ) + attr IN VARCHAR2, + cond IN VARCHAR2 ) IS l_val number := 0; new_val number:= 0; - offset number:=0; BEGIN execute immediate 'select ' || seq || '.nextval from dual' INTO l_val; execute immediate 'select max(' || attr || ') from ' || tbl || ' ' || cond || '' INTO new_val; + + IF new_val is null THEN + new_val := 0; + END IF; execute immediate - 'alter sequence ' || seq || ' increment by ' || (new_val - l_val + 1) || ' minvalue 0'; + 'alter sequence ' || seq || ' increment by ' || (new_val - l_val) || ' minvalue 0'; execute immediate 'select ' || seq || '.nextval from dual' INTO l_val; @@ -87,6 +71,7 @@ execute updateseq('webapp_seq', 'webapp', 'webapp_id', ''); execute updateseq('requestitem_seq', 'requestitem', 'requestitem_id', ''); execute updateseq('handle_id_seq', 'handle', 'handle_id', ''); + -- Handle Sequence is a special case. Since Handles minted by DSpace use the 'handle_seq', -- we need to ensure the next assigned handle will *always* be unique. So, 'handle_seq' -- always needs to be set to the value of the *largest* handle suffix. That way when the @@ -94,4 +79,32 @@ execute updateseq('handle_id_seq', 'handle', 'handle_id', ''); -- For all 'handle' values which have a number in their suffix (after '/'), find the maximum -- suffix value, convert it to a number, and set the 'handle_seq' to start at the next value -- (see updateseq.sql script for more) -execute updateseq('handle_seq', 'handle', "to_number(regexp_replace(handle, '.*/', ''), '999999999999')", "WHERE REGEXP_LIKE(handle, '^.*/[0123456789]*$')"); + +create or replace procedure updatehandleseq( + seq IN VARCHAR ) +IS + l_val number := 0; + new_val number := 0; +BEGIN + execute immediate + 'select ' || seq || '.nextval from dual' INTO l_val; + + execute immediate + 'select max(to_number(regexp_replace(handle, ''.*/'', ''''), ''999999999999'')) from handle WHERE REGEXP_LIKE(handle, ''^.*/[0123456789]*$'')' INTO new_val; + + IF new_val is null THEN + new_val := 0; + END IF; + + execute immediate + 'alter sequence ' || seq || ' increment by ' || (new_val - l_val) || ' minvalue 0'; + + execute immediate + 'select ' || seq || '.nextval from dual' INTO l_val; + + execute immediate + 'alter sequence ' || seq || ' increment by 1 minvalue 0'; +END; + + +execute updatehandleseq('bitstreamformatregistry_seq'); From 60ea589296836b20d154e711cd1a5c3f771ad4de Mon Sep 17 00:00:00 2001 From: Chris Wilper Date: Tue, 11 Dec 2018 15:27:25 -0500 Subject: [PATCH 60/68] DS-4107 Represent DSO metadata as a map in REST --- .../rest/converter/DSpaceObjectConverter.java | 19 +- .../app/rest/converter/MetadataConverter.java | 91 +++++++ .../converter/MetadataValueConverter.java | 28 +++ .../app/rest/model/DSpaceObjectRest.java | 8 +- .../app/rest/model/MetadataEntryRest.java | 47 ---- .../dspace/app/rest/model/MetadataRest.java | 56 +++++ .../app/rest/model/MetadataValueRest.java | 15 +- .../repository/CollectionRestRepository.java | 22 +- .../repository/CommunityRestRepository.java | 24 +- .../rest/repository/DSpaceRestRepository.java | 4 + .../repository/EPersonRestRepository.java | 13 +- .../rest/repository/GroupRestRepository.java | 14 +- .../rest/repository/ItemRestRepository.java | 16 +- .../app/rest/utils/DSpaceObjectUtils.java | 62 ----- .../app/rest/BrowsesResourceControllerIT.java | 17 +- .../app/rest/CollectionRestRepositoryIT.java | 79 +++--- .../app/rest/CommunityRestRepositoryIT.java | 137 +++++----- .../app/rest/EPersonRestRepositoryIT.java | 23 +- .../dspace/app/rest/ItemRestRepositoryIT.java | 235 ++++++------------ .../app/rest/matcher/BitstreamMatcher.java | 8 +- .../matcher/BitstreamMetadataMatcher.java | 33 --- .../app/rest/matcher/CollectionMatcher.java | 4 +- .../matcher/CollectionMetadataMatcher.java | 26 -- .../app/rest/matcher/CommunityMatcher.java | 4 +- .../app/rest/matcher/EPersonMatcher.java | 6 +- .../rest/matcher/EPersonMetadataMatcher.java | 40 --- .../dspace/app/rest/matcher/ItemMatcher.java | 7 +- ...adataMatcher.java => MetadataMatcher.java} | 15 +- 28 files changed, 431 insertions(+), 622 deletions(-) create mode 100644 dspace-spring-rest/src/main/java/org/dspace/app/rest/converter/MetadataConverter.java create mode 100644 dspace-spring-rest/src/main/java/org/dspace/app/rest/converter/MetadataValueConverter.java delete mode 100644 dspace-spring-rest/src/main/java/org/dspace/app/rest/model/MetadataEntryRest.java create mode 100644 dspace-spring-rest/src/main/java/org/dspace/app/rest/model/MetadataRest.java delete mode 100644 dspace-spring-rest/src/main/java/org/dspace/app/rest/utils/DSpaceObjectUtils.java delete mode 100644 dspace-spring-rest/src/test/java/org/dspace/app/rest/matcher/BitstreamMetadataMatcher.java delete mode 100644 dspace-spring-rest/src/test/java/org/dspace/app/rest/matcher/CollectionMetadataMatcher.java delete mode 100644 dspace-spring-rest/src/test/java/org/dspace/app/rest/matcher/EPersonMetadataMatcher.java rename dspace-spring-rest/src/test/java/org/dspace/app/rest/matcher/{CommunityMetadataMatcher.java => MetadataMatcher.java} (56%) diff --git a/dspace-spring-rest/src/main/java/org/dspace/app/rest/converter/DSpaceObjectConverter.java b/dspace-spring-rest/src/main/java/org/dspace/app/rest/converter/DSpaceObjectConverter.java index 5f02ae8de0..462768de4b 100644 --- a/dspace-spring-rest/src/main/java/org/dspace/app/rest/converter/DSpaceObjectConverter.java +++ b/dspace-spring-rest/src/main/java/org/dspace/app/rest/converter/DSpaceObjectConverter.java @@ -7,12 +7,8 @@ */ package org.dspace.app.rest.converter; -import java.util.ArrayList; -import java.util.List; - -import org.dspace.app.rest.model.MetadataEntryRest; import org.dspace.content.DSpaceObject; -import org.dspace.content.MetadataValue; +import org.springframework.beans.factory.annotation.Autowired; /** * This is the base converter from/to objects in the DSpace API data model and @@ -26,6 +22,9 @@ public abstract class DSpaceObjectConverter extends DSpaceConverter { + @Autowired(required = true) + private MetadataConverter metadataConverter; + @Override public R fromModel(M obj) { R resource = newInstance(); @@ -34,15 +33,7 @@ public abstract class DSpaceObjectConverter metadata = new ArrayList(); - for (MetadataValue mv : obj.getMetadata()) { - MetadataEntryRest me = new MetadataEntryRest(); - me.setKey(mv.getMetadataField().toString('.')); - me.setValue(mv.getValue()); - me.setLanguage(mv.getLanguage()); - metadata.add(me); - } - resource.setMetadata(metadata); + resource.setMetadata(metadataConverter.convert(obj.getMetadata())); return resource; } diff --git a/dspace-spring-rest/src/main/java/org/dspace/app/rest/converter/MetadataConverter.java b/dspace-spring-rest/src/main/java/org/dspace/app/rest/converter/MetadataConverter.java new file mode 100644 index 0000000000..ec1835bf07 --- /dev/null +++ b/dspace-spring-rest/src/main/java/org/dspace/app/rest/converter/MetadataConverter.java @@ -0,0 +1,91 @@ +/** + * 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.converter; + +import java.sql.SQLException; +import java.util.Comparator; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.SortedSet; +import java.util.TreeSet; +import java.util.stream.Collectors; + +import org.dspace.app.rest.model.MetadataRest; +import org.dspace.app.rest.model.MetadataValueRest; +import org.dspace.authorize.AuthorizeException; +import org.dspace.content.DSpaceObject; +import org.dspace.content.Item; +import org.dspace.content.MetadataValue; +import org.dspace.content.factory.ContentServiceFactory; +import org.dspace.content.service.DSpaceObjectService; +import org.dspace.core.Context; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.core.convert.converter.Converter; +import org.springframework.stereotype.Component; + +@Component +public class MetadataConverter implements Converter, MetadataRest> { + + @Autowired + private ContentServiceFactory contentServiceFactory; + + @Autowired + private MetadataValueConverter valueConverter; + + @Override + public MetadataRest convert(List metadataValueList) { + // Convert each value to a DTO while retaining place order in a map of key -> SortedSet + Map> mapOfSortedSets = new HashMap<>(); + for (MetadataValue metadataValue : metadataValueList) { + String key = metadataValue.getMetadataField().toString('.'); + SortedSet set = mapOfSortedSets.get(key); + if (set == null) { + set = new TreeSet<>(Comparator.comparingInt(MetadataValueRest::getPlace)); + mapOfSortedSets.put(key, set); + } + set.add(valueConverter.convert(metadataValue)); + } + + MetadataRest metadataRest = new MetadataRest(); + + // Populate MetadataRest's map of key -> List while respecting SortedSet's order + Map> mapOfLists = metadataRest.getMap(); + for (Map.Entry> entry : mapOfSortedSets.entrySet()) { + mapOfLists.put(entry.getKey(), entry.getValue().stream().collect(Collectors.toList())); + } + + return metadataRest; + } + + /** + * Completely replaces the metadata in the given dso. + * + * @param context the context to use. + * @param dso the dso whose metadata should be updated. + * @param metadataRest the new metadata. + * @throws SQLException if a database error occurs. + * @throws AuthorizeException if an authorization error occurs. + */ + public void setMetadata(Context context, DSpaceObject dso, MetadataRest metadataRest) + throws SQLException, AuthorizeException { + DSpaceObjectService dsoService = contentServiceFactory.getDSpaceObjectService(dso); + dsoService.clearMetadata(context, dso, Item.ANY, Item.ANY, Item.ANY, Item.ANY); + for (Map.Entry> entry: metadataRest.getMap().entrySet()) { + String[] seq = entry.getKey().split("\\."); + String schema = seq[0]; + String element = seq[1]; + String qualifier = seq.length == 3 ? seq[2] : null; + for (MetadataValueRest mvr: entry.getValue()) { + dsoService.addMetadata(context, dso, schema, element, qualifier, mvr.getLanguage(), + mvr.getValue(), mvr.getAuthority(), mvr.getConfidence()); + } + } + dsoService.update(context, dso); + } +} diff --git a/dspace-spring-rest/src/main/java/org/dspace/app/rest/converter/MetadataValueConverter.java b/dspace-spring-rest/src/main/java/org/dspace/app/rest/converter/MetadataValueConverter.java new file mode 100644 index 0000000000..1dfe4ab12f --- /dev/null +++ b/dspace-spring-rest/src/main/java/org/dspace/app/rest/converter/MetadataValueConverter.java @@ -0,0 +1,28 @@ +/** + * 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.converter; + +import org.dspace.app.rest.model.MetadataValueRest; +import org.dspace.content.MetadataValue; +import org.springframework.core.convert.converter.Converter; +import org.springframework.stereotype.Component; + +@Component +public class MetadataValueConverter implements Converter { + + @Override + public MetadataValueRest convert(MetadataValue model) { + MetadataValueRest metadataValueRest = new MetadataValueRest(); + metadataValueRest.setValue(model.getValue()); + metadataValueRest.setLanguage(model.getLanguage()); + metadataValueRest.setAuthority(model.getAuthority()); + metadataValueRest.setConfidence(model.getConfidence()); + metadataValueRest.setPlace(model.getPlace()); + return metadataValueRest; + } +} diff --git a/dspace-spring-rest/src/main/java/org/dspace/app/rest/model/DSpaceObjectRest.java b/dspace-spring-rest/src/main/java/org/dspace/app/rest/model/DSpaceObjectRest.java index bf89cd7a61..c0ef206aa1 100644 --- a/dspace-spring-rest/src/main/java/org/dspace/app/rest/model/DSpaceObjectRest.java +++ b/dspace-spring-rest/src/main/java/org/dspace/app/rest/model/DSpaceObjectRest.java @@ -7,8 +7,6 @@ */ package org.dspace.app.rest.model; -import java.util.List; - import org.dspace.app.rest.RestResourceController; /** @@ -22,7 +20,7 @@ public abstract class DSpaceObjectRest extends BaseObjectRest { private String name; private String handle; - List metadata; + MetadataRest metadata = new MetadataRest(); @Override public String getId() { @@ -53,11 +51,11 @@ public abstract class DSpaceObjectRest extends BaseObjectRest { this.handle = handle; } - public List getMetadata() { + public MetadataRest getMetadata() { return metadata; } - public void setMetadata(List metadata) { + public void setMetadata(MetadataRest metadata) { this.metadata = metadata; } diff --git a/dspace-spring-rest/src/main/java/org/dspace/app/rest/model/MetadataEntryRest.java b/dspace-spring-rest/src/main/java/org/dspace/app/rest/model/MetadataEntryRest.java deleted file mode 100644 index da2b841f2e..0000000000 --- a/dspace-spring-rest/src/main/java/org/dspace/app/rest/model/MetadataEntryRest.java +++ /dev/null @@ -1,47 +0,0 @@ -/** - * 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.model; - -/** - * An embeddable representation of the Metadata to use in with DSpace REST - * Resource - * - * @author Andrea Bollini (andrea.bollini at 4science.it) - */ -public class MetadataEntryRest { - String key; - - String value; - - String language; - - public String getKey() { - return key; - } - - public void setKey(String key) { - this.key = key; - } - - public String getValue() { - return value; - } - - public void setValue(String value) { - this.value = value; - } - - public String getLanguage() { - return language; - } - - public void setLanguage(String language) { - this.language = language; - } - -} diff --git a/dspace-spring-rest/src/main/java/org/dspace/app/rest/model/MetadataRest.java b/dspace-spring-rest/src/main/java/org/dspace/app/rest/model/MetadataRest.java new file mode 100644 index 0000000000..6225843d62 --- /dev/null +++ b/dspace-spring-rest/src/main/java/org/dspace/app/rest/model/MetadataRest.java @@ -0,0 +1,56 @@ +/** + * 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.model; + +import java.util.Arrays; +import java.util.List; +import java.util.SortedMap; +import java.util.TreeMap; + +import com.fasterxml.jackson.annotation.JsonAnyGetter; +import com.fasterxml.jackson.annotation.JsonAnySetter; + +public class MetadataRest { + + @JsonAnySetter + private SortedMap> map = new TreeMap(); + + @JsonAnyGetter + public SortedMap> getMap() { + return map; + } + + /** + * Sets the metadata values for a given key. + * + * @param key the key. + * @param values the values. The values will be ordered according to their {@code place} value, if + * nonnegative. Values that are negative (the default is -1) are assume to be non-explicitly + * set and will will be ordered at the end of the list, after the last value with an order, + * in the order they are passed to this method. + * @return this instance, to support chaining calls for easy initialization. + */ + public MetadataRest put(String key, MetadataValueRest... values) { + // determine highest explicitly ordered value + int highest = -1; + for (MetadataValueRest value : values) { + if (value.getPlace() > highest) { + highest = value.getPlace(); + } + } + // add any non-explicitly ordered values after highest + for (MetadataValueRest value : values) { + if (value.getPlace() < 0) { + highest++; + value.setPlace(highest); + } + } + map.put(key, Arrays.asList(values)); + return this; + } +} \ No newline at end of file diff --git a/dspace-spring-rest/src/main/java/org/dspace/app/rest/model/MetadataValueRest.java b/dspace-spring-rest/src/main/java/org/dspace/app/rest/model/MetadataValueRest.java index 4dc7be11e9..768a81a4ff 100644 --- a/dspace-spring-rest/src/main/java/org/dspace/app/rest/model/MetadataValueRest.java +++ b/dspace-spring-rest/src/main/java/org/dspace/app/rest/model/MetadataValueRest.java @@ -7,8 +7,7 @@ */ package org.dspace.app.rest.model; -import com.fasterxml.jackson.annotation.JsonProperty; -import com.fasterxml.jackson.annotation.JsonProperty.Access; +import com.fasterxml.jackson.annotation.JsonIgnore; /** * An embeddable representation of the Metadata to use in with DSpace REST @@ -26,8 +25,15 @@ public class MetadataValueRest { int confidence; - @JsonProperty(access = Access.READ_ONLY) - int place; + @JsonIgnore + int place = -1; + + public MetadataValueRest() { + } + + public MetadataValueRest(String value) { + this.value = value; + } public String getValue() { return value; @@ -68,5 +74,4 @@ public class MetadataValueRest { public void setPlace(int place) { this.place = place; } - } diff --git a/dspace-spring-rest/src/main/java/org/dspace/app/rest/repository/CollectionRestRepository.java b/dspace-spring-rest/src/main/java/org/dspace/app/rest/repository/CollectionRestRepository.java index 9f25e12d19..c00b7d3b07 100644 --- a/dspace-spring-rest/src/main/java/org/dspace/app/rest/repository/CollectionRestRepository.java +++ b/dspace-spring-rest/src/main/java/org/dspace/app/rest/repository/CollectionRestRepository.java @@ -23,14 +23,13 @@ import org.apache.commons.lang3.StringUtils; import org.dspace.app.rest.Parameter; import org.dspace.app.rest.SearchRestMethod; import org.dspace.app.rest.converter.CollectionConverter; +import org.dspace.app.rest.converter.MetadataConverter; import org.dspace.app.rest.exception.RepositoryMethodNotImplementedException; import org.dspace.app.rest.exception.UnprocessableEntityException; import org.dspace.app.rest.model.CollectionRest; import org.dspace.app.rest.model.CommunityRest; -import org.dspace.app.rest.model.MetadataEntryRest; import org.dspace.app.rest.model.hateoas.CollectionResource; import org.dspace.app.rest.utils.CollectionRestEqualityUtils; -import org.dspace.app.rest.utils.DSpaceObjectUtils; import org.dspace.authorize.AuthorizeException; import org.dspace.content.Collection; import org.dspace.content.Community; @@ -66,7 +65,7 @@ public class CollectionRestRepository extends DSpaceRestRepository metadataEntryRestList = collectionRest.getMetadata(); - collection = (Collection) dspaceObjectUtils.replaceMetadataValues(context, - collection, - metadataEntryRestList); + metadataConverter.setMetadata(context, collection, collectionRest.getMetadata()); } else { throw new IllegalArgumentException("The UUID in the Json and the UUID in the url do not match: " + id + ", " diff --git a/dspace-spring-rest/src/main/java/org/dspace/app/rest/repository/CommunityRestRepository.java b/dspace-spring-rest/src/main/java/org/dspace/app/rest/repository/CommunityRestRepository.java index 2e6834b3cf..67c1262e3b 100644 --- a/dspace-spring-rest/src/main/java/org/dspace/app/rest/repository/CommunityRestRepository.java +++ b/dspace-spring-rest/src/main/java/org/dspace/app/rest/repository/CommunityRestRepository.java @@ -19,18 +19,16 @@ import javax.ws.rs.BadRequestException; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; -import com.google.gson.Gson; import org.apache.commons.lang3.StringUtils; import org.dspace.app.rest.Parameter; import org.dspace.app.rest.SearchRestMethod; import org.dspace.app.rest.converter.CommunityConverter; +import org.dspace.app.rest.converter.MetadataConverter; import org.dspace.app.rest.exception.RepositoryMethodNotImplementedException; import org.dspace.app.rest.exception.UnprocessableEntityException; import org.dspace.app.rest.model.CommunityRest; -import org.dspace.app.rest.model.MetadataEntryRest; import org.dspace.app.rest.model.hateoas.CommunityResource; import org.dspace.app.rest.utils.CommunityRestEqualityUtils; -import org.dspace.app.rest.utils.DSpaceObjectUtils; import org.dspace.authorize.AuthorizeException; import org.dspace.content.Community; import org.dspace.content.service.CommunityService; @@ -60,7 +58,7 @@ public class CommunityRestRepository extends DSpaceRestRepository metadataEntryRestList = communityRest.getMetadata(); - community = (Community) dspaceObjectUtils.replaceMetadataValues(context, community, metadataEntryRestList); + metadataConverter.setMetadata(context, community, communityRest.getMetadata()); } else { throw new UnprocessableEntityException("The given JSON and the original Community differ more " + "than just the metadata"); diff --git a/dspace-spring-rest/src/main/java/org/dspace/app/rest/repository/DSpaceRestRepository.java b/dspace-spring-rest/src/main/java/org/dspace/app/rest/repository/DSpaceRestRepository.java index 5cf16f6031..6cda70eee3 100644 --- a/dspace-spring-rest/src/main/java/org/dspace/app/rest/repository/DSpaceRestRepository.java +++ b/dspace-spring-rest/src/main/java/org/dspace/app/rest/repository/DSpaceRestRepository.java @@ -25,6 +25,7 @@ import org.dspace.app.rest.model.hateoas.DSpaceResource; import org.dspace.app.rest.model.patch.Patch; import org.dspace.app.util.DCInputsReaderException; import org.dspace.authorize.AuthorizeException; +import org.dspace.content.service.MetadataFieldService; import org.dspace.core.Context; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.domain.Page; @@ -54,6 +55,9 @@ public abstract class DSpaceRestRepository thisRepository; + @Autowired + private MetadataFieldService metadataFieldService; + @Override public S save(S entity) { Context context = null; diff --git a/dspace-spring-rest/src/main/java/org/dspace/app/rest/repository/EPersonRestRepository.java b/dspace-spring-rest/src/main/java/org/dspace/app/rest/repository/EPersonRestRepository.java index 721e4c6313..37853dfb88 100644 --- a/dspace-spring-rest/src/main/java/org/dspace/app/rest/repository/EPersonRestRepository.java +++ b/dspace-spring-rest/src/main/java/org/dspace/app/rest/repository/EPersonRestRepository.java @@ -20,11 +20,11 @@ import org.apache.commons.lang3.StringUtils; import org.dspace.app.rest.Parameter; import org.dspace.app.rest.SearchRestMethod; import org.dspace.app.rest.converter.EPersonConverter; +import org.dspace.app.rest.converter.MetadataConverter; import org.dspace.app.rest.exception.PatchBadRequestException; import org.dspace.app.rest.exception.RESTAuthorizationException; import org.dspace.app.rest.exception.UnprocessableEntityException; import org.dspace.app.rest.model.EPersonRest; -import org.dspace.app.rest.model.MetadataEntryRest; import org.dspace.app.rest.model.hateoas.EPersonResource; import org.dspace.app.rest.model.patch.Operation; import org.dspace.app.rest.model.patch.Patch; @@ -60,6 +60,9 @@ public class EPersonRestRepository extends DSpaceRestRepository { @Autowired GroupConverter converter; + @Autowired + MetadataConverter metadataConverter; + @Override @PreAuthorize("hasAuthority('ADMIN')") protected GroupRest createAndReturn(Context context) @@ -65,14 +68,7 @@ public class GroupRestRepository extends DSpaceRestRepository { group = gs.create(context); gs.setName(group, groupRest.getName()); gs.update(context, group); - - if (groupRest.getMetadata() != null) { - for (MetadataEntryRest mer: groupRest.getMetadata()) { - String[] metadatakey = mer.getKey().split("\\."); - gs.addMetadata(context, group, metadatakey[0], metadatakey[1], - metadatakey.length == 3 ? metadatakey[2] : null, mer.getLanguage(), mer.getValue()); - } - } + metadataConverter.setMetadata(context, group, groupRest.getMetadata()); } catch (SQLException excSQL) { throw new RuntimeException(excSQL.getMessage(), excSQL); } diff --git a/dspace-spring-rest/src/main/java/org/dspace/app/rest/repository/ItemRestRepository.java b/dspace-spring-rest/src/main/java/org/dspace/app/rest/repository/ItemRestRepository.java index 4b70b06ac6..44ab659fe1 100644 --- a/dspace-spring-rest/src/main/java/org/dspace/app/rest/repository/ItemRestRepository.java +++ b/dspace-spring-rest/src/main/java/org/dspace/app/rest/repository/ItemRestRepository.java @@ -22,16 +22,15 @@ import com.fasterxml.jackson.databind.ObjectMapper; import org.apache.commons.lang3.StringUtils; import org.apache.log4j.Logger; import org.dspace.app.rest.converter.ItemConverter; +import org.dspace.app.rest.converter.MetadataConverter; import org.dspace.app.rest.exception.PatchBadRequestException; import org.dspace.app.rest.exception.RepositoryMethodNotImplementedException; import org.dspace.app.rest.exception.UnprocessableEntityException; import org.dspace.app.rest.model.ItemRest; -import org.dspace.app.rest.model.MetadataEntryRest; import org.dspace.app.rest.model.hateoas.ItemResource; import org.dspace.app.rest.model.patch.Operation; import org.dspace.app.rest.model.patch.Patch; import org.dspace.app.rest.repository.patch.ItemPatch; -import org.dspace.app.rest.utils.DSpaceObjectUtils; import org.dspace.authorize.AuthorizeException; import org.dspace.content.Collection; import org.dspace.content.Item; @@ -67,6 +66,9 @@ public class ItemRestRepository extends DSpaceRestRepository { @Autowired ItemConverter converter; + @Autowired + MetadataConverter metadataConverter; + @Autowired ItemPatch itemPatch; @@ -79,9 +81,6 @@ public class ItemRestRepository extends DSpaceRestRepository { @Autowired CollectionService collectionService; - @Autowired - DSpaceObjectUtils dspaceObjectUtils; - @Autowired InstallItemService installItemService; @@ -239,7 +238,7 @@ public class ItemRestRepository extends DSpaceRestRepository { item.setOwningCollection(collection); item.setDiscoverable(itemRest.getDiscoverable()); item.setLastModified(itemRest.getLastModified()); - dspaceObjectUtils.replaceMetadataValues(context, item, itemRest.getMetadata()); + metadataConverter.setMetadata(context, item, itemRest.getMetadata()); Item itemToReturn = installItemService.installItem(context, workspaceItem); @@ -266,10 +265,7 @@ public class ItemRestRepository extends DSpaceRestRepository { } if (StringUtils.equals(uuid.toString(), itemRest.getId())) { - List metadataEntryRestList = itemRest.getMetadata(); - item = (Item) dspaceObjectUtils.replaceMetadataValues(context, - item, - metadataEntryRestList); + metadataConverter.setMetadata(context, item, itemRest.getMetadata()); } else { throw new IllegalArgumentException("The UUID in the Json and the UUID in the url do not match: " + uuid + ", " diff --git a/dspace-spring-rest/src/main/java/org/dspace/app/rest/utils/DSpaceObjectUtils.java b/dspace-spring-rest/src/main/java/org/dspace/app/rest/utils/DSpaceObjectUtils.java deleted file mode 100644 index eff3dcd657..0000000000 --- a/dspace-spring-rest/src/main/java/org/dspace/app/rest/utils/DSpaceObjectUtils.java +++ /dev/null @@ -1,62 +0,0 @@ -/** - * 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.utils; - -import java.sql.SQLException; -import java.util.List; - -import org.dspace.app.rest.model.MetadataEntryRest; -import org.dspace.authorize.AuthorizeException; -import org.dspace.content.DSpaceObject; -import org.dspace.content.Item; -import org.dspace.content.factory.ContentServiceFactory; -import org.dspace.content.service.DSpaceObjectService; -import org.dspace.core.Context; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.stereotype.Component; - -/** - * This class will be used as a Utils class to implement certain useful methods that can be reused by - * multiple DSpaceObject instances - */ -@Component -public class DSpaceObjectUtils { - - @Autowired - ContentServiceFactory contentServiceFactory; - - /** - * This method will replace ALL MetadataValues from the given DSpaceObject with the MetadataValues passed along - * in the metadataEntryRestList. These MetadataEntryRest objects will be analysed to use the MetadataValue key, - * language and value to build a proper MetadataValue for the given DSpaceObject. - * This will result in the DSpaceObject only containing MetadataValues that are represented in the given - * metadataEntryRestList. - * @param context The relevant DSpace context - * @param dSpaceObject The DSpaceObject for which the MetadataValues will be cleared and filled up - * with the MetadataValues created from the metadataEntryRestList - * @param metadataEntryRestList The list of MetadataEntryRest objects that will be used to construct - * MetadataValue objects for the given DSpaceObject - * @return Returns the DSpaceObject - * @throws SQLException If something goes wrong - * @throws AuthorizeException If something goes wrong - */ - public DSpaceObject replaceMetadataValues(Context context, - DSpaceObject dSpaceObject, - List metadataEntryRestList) - throws SQLException, AuthorizeException { - DSpaceObjectService dSpaceObjectService = contentServiceFactory.getDSpaceObjectService(dSpaceObject); - dSpaceObjectService.clearMetadata(context, dSpaceObject, Item.ANY, Item.ANY, Item.ANY, Item.ANY); - for (MetadataEntryRest mer : metadataEntryRestList) { - String[] metadatakey = mer.getKey().split("\\."); - dSpaceObjectService.addMetadata(context, dSpaceObject, metadatakey[0], metadatakey[1], - metadatakey.length == 3 ? metadatakey[2] : null, mer.getLanguage(), mer.getValue()); - } - dSpaceObjectService.update(context, dSpaceObject); - return dSpaceObject; - } -} diff --git a/dspace-spring-rest/src/test/java/org/dspace/app/rest/BrowsesResourceControllerIT.java b/dspace-spring-rest/src/test/java/org/dspace/app/rest/BrowsesResourceControllerIT.java index beacc1b838..980604a8ae 100644 --- a/dspace-spring-rest/src/test/java/org/dspace/app/rest/BrowsesResourceControllerIT.java +++ b/dspace-spring-rest/src/test/java/org/dspace/app/rest/BrowsesResourceControllerIT.java @@ -7,10 +7,10 @@ */ package org.dspace.app.rest; +import static org.dspace.app.rest.matcher.MetadataMatcher.matchMetadata; import static org.hamcrest.Matchers.contains; import static org.hamcrest.Matchers.containsInAnyOrder; import static org.hamcrest.Matchers.containsString; -import static org.hamcrest.Matchers.hasItem; import static org.hamcrest.Matchers.hasSize; import static org.hamcrest.Matchers.is; import static org.hamcrest.Matchers.not; @@ -31,6 +31,7 @@ import org.dspace.content.Collection; import org.dspace.content.Community; import org.dspace.content.Item; import org.dspace.eperson.Group; +import org.hamcrest.Matchers; import org.junit.Test; import org.springframework.test.web.servlet.result.MockMvcResultHandlers; @@ -380,14 +381,10 @@ public class BrowsesResourceControllerIT extends AbstractControllerIntegrationTe "An embargoed publication", "2017-08-10")))) - //The private item must not be present - .andExpect(jsonPath("$._embedded.items[*].metadata[?(@.key=='dc.title')].value", - not(hasItem("This is a private item")))) - - //The internal item must not be present - .andExpect(jsonPath("$._embedded.items[*].metadata[?(@.key=='dc.title')].value", - not(hasItem("Internal publication")))) - ; + //The private and internal items must not be present + .andExpect(jsonPath("$._embedded.items[*].metadata", Matchers.allOf( + not(matchMetadata("dc.title", "This is a private item")), + not(matchMetadata("dc.title", "Internal publication"))))); } @Test @@ -883,4 +880,4 @@ public class BrowsesResourceControllerIT extends AbstractControllerIntegrationTe ))); } -} \ No newline at end of file +} diff --git a/dspace-spring-rest/src/test/java/org/dspace/app/rest/CollectionRestRepositoryIT.java b/dspace-spring-rest/src/test/java/org/dspace/app/rest/CollectionRestRepositoryIT.java index c34bcbd94a..7b160710d5 100644 --- a/dspace-spring-rest/src/test/java/org/dspace/app/rest/CollectionRestRepositoryIT.java +++ b/dspace-spring-rest/src/test/java/org/dspace/app/rest/CollectionRestRepositoryIT.java @@ -19,7 +19,6 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers. import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; -import java.util.Arrays; import java.util.UUID; import com.fasterxml.jackson.databind.ObjectMapper; @@ -27,9 +26,10 @@ import org.dspace.app.rest.builder.CollectionBuilder; import org.dspace.app.rest.builder.CommunityBuilder; import org.dspace.app.rest.converter.CollectionConverter; import org.dspace.app.rest.matcher.CollectionMatcher; -import org.dspace.app.rest.matcher.CommunityMetadataMatcher; +import org.dspace.app.rest.matcher.MetadataMatcher; import org.dspace.app.rest.model.CollectionRest; -import org.dspace.app.rest.model.MetadataEntryRest; +import org.dspace.app.rest.model.MetadataRest; +import org.dspace.app.rest.model.MetadataValueRest; import org.dspace.app.rest.test.AbstractControllerIntegrationTest; import org.dspace.authorize.service.AuthorizeService; import org.dspace.content.Collection; @@ -358,11 +358,8 @@ public class CollectionRestRepositoryIT extends AbstractControllerIntegrationTes CollectionRest collectionRest = collectionConverter.fromModel(col1); - MetadataEntryRest metadataEntryRest = new MetadataEntryRest(); - metadataEntryRest.setKey("dc.title"); - metadataEntryRest.setValue("Electronic theses and dissertations"); - - collectionRest.setMetadata(Arrays.asList(metadataEntryRest)); + collectionRest.setMetadata(new MetadataRest() + .put("dc.title", new MetadataValueRest("Electronic theses and dissertations"))); getClient(token).perform(put("/api/core/collections/" + col1.getID().toString()) .contentType(MediaType.APPLICATION_JSON) @@ -479,32 +476,18 @@ public class CollectionRestRepositoryIT extends AbstractControllerIntegrationTes CollectionRest collectionRest = new CollectionRest(); // We send a name but the created collection should set this to the title collectionRest.setName("Collection"); - MetadataEntryRest description = new MetadataEntryRest(); - description.setKey("dc.description"); - description.setValue("

Some cool HTML code here

"); - - MetadataEntryRest abs = new MetadataEntryRest(); - abs.setKey("dc.description.abstract"); - abs.setValue("Sample top-level community created via the REST API"); - - MetadataEntryRest contents = new MetadataEntryRest(); - contents.setKey("dc.description.tableofcontents"); - contents.setValue("

HTML News

"); - - MetadataEntryRest copyright = new MetadataEntryRest(); - copyright.setKey("dc.rights"); - copyright.setValue("Custom Copyright Text"); - - MetadataEntryRest title = new MetadataEntryRest(); - title.setKey("dc.title"); - title.setValue("Title Text"); - - collectionRest.setMetadata(Arrays.asList(description, - abs, - contents, - copyright, - title)); + collectionRest.setMetadata(new MetadataRest() + .put("dc.description", + new MetadataValueRest("

Some cool HTML code here

")) + .put("dc.description.abstract", + new MetadataValueRest("Sample top-level community created via the REST API")) + .put("dc.description.tableofcontents", + new MetadataValueRest("

HTML News

")) + .put("dc.rights", + new MetadataValueRest("Custom Copyright Text")) + .put("dc.title", + new MetadataValueRest("Title Text"))); String authToken = getAuthToken(admin.getEmail(), password); getClient(authToken).perform(post("/api/core/collections") @@ -519,18 +502,17 @@ public class CollectionRestRepositoryIT extends AbstractControllerIntegrationTes hasJsonPath("$.name", is("Title Text")), hasJsonPath("$.handle", not(empty())), hasJsonPath("$.type", is("collection")), - hasJsonPath("$.metadata", Matchers.containsInAnyOrder( - CommunityMetadataMatcher.matchMetadata("dc.description", - "

Some cool HTML code here

"), - CommunityMetadataMatcher.matchMetadata("dc.description.abstract", - "Sample top-level community " + - "created via the REST API"), - CommunityMetadataMatcher.matchMetadata("dc.description.tableofcontents", - "

HTML News

"), - CommunityMetadataMatcher.matchMetadata("dc.rights", - "Custom Copyright Text"), - CommunityMetadataMatcher.matchMetadata("dc.title", - "Title Text") + hasJsonPath("$.metadata", Matchers.allOf( + MetadataMatcher.matchMetadata("dc.description", + "

Some cool HTML code here

"), + MetadataMatcher.matchMetadata("dc.description.abstract", + "Sample top-level community created via the REST API"), + MetadataMatcher.matchMetadata("dc.description.tableofcontents", + "

HTML News

"), + MetadataMatcher.matchMetadata("dc.rights", + "Custom Copyright Text"), + MetadataMatcher.matchMetadata("dc.title", + "Title Text") ))))); } @@ -621,11 +603,8 @@ public class CollectionRestRepositoryIT extends AbstractControllerIntegrationTes CollectionRest collectionRest = collectionConverter.fromModel(col1); - MetadataEntryRest metadataEntryRest = new MetadataEntryRest(); - metadataEntryRest.setKey("dc.title"); - metadataEntryRest.setValue("Electronic theses and dissertations"); - - collectionRest.setMetadata(Arrays.asList(metadataEntryRest)); + collectionRest.setMetadata(new MetadataRest() + .put("dc.title", new MetadataValueRest("Electronic theses and dissertations"))); getClient(token).perform(put("/api/core/collections/" + col1.getID().toString()) .contentType(MediaType.APPLICATION_JSON) diff --git a/dspace-spring-rest/src/test/java/org/dspace/app/rest/CommunityRestRepositoryIT.java b/dspace-spring-rest/src/test/java/org/dspace/app/rest/CommunityRestRepositoryIT.java index ec55ab4faa..b5c7178d52 100644 --- a/dspace-spring-rest/src/test/java/org/dspace/app/rest/CommunityRestRepositoryIT.java +++ b/dspace-spring-rest/src/test/java/org/dspace/app/rest/CommunityRestRepositoryIT.java @@ -8,6 +8,7 @@ package org.dspace.app.rest; import static com.jayway.jsonpath.matchers.JsonPathMatchers.hasJsonPath; +import static org.dspace.app.rest.matcher.MetadataMatcher.matchMetadata; import static org.hamcrest.Matchers.empty; import static org.hamcrest.Matchers.is; import static org.hamcrest.Matchers.not; @@ -19,7 +20,6 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers. import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; -import java.util.Arrays; import java.util.UUID; import com.fasterxml.jackson.databind.ObjectMapper; @@ -28,9 +28,10 @@ import org.dspace.app.rest.builder.CollectionBuilder; import org.dspace.app.rest.builder.CommunityBuilder; import org.dspace.app.rest.converter.CommunityConverter; import org.dspace.app.rest.matcher.CommunityMatcher; -import org.dspace.app.rest.matcher.CommunityMetadataMatcher; +import org.dspace.app.rest.matcher.MetadataMatcher; import org.dspace.app.rest.model.CommunityRest; -import org.dspace.app.rest.model.MetadataEntryRest; +import org.dspace.app.rest.model.MetadataRest; +import org.dspace.app.rest.model.MetadataValueRest; import org.dspace.app.rest.test.AbstractControllerIntegrationTest; import org.dspace.authorize.service.AuthorizeService; import org.dspace.content.Collection; @@ -58,31 +59,29 @@ public class CommunityRestRepositoryIT extends AbstractControllerIntegrationTest // We send a name but the created community should set this to the title comm.setName("Test Top-Level Community"); - MetadataEntryRest description = new MetadataEntryRest(); - description.setKey("dc.description"); + MetadataRest metadataRest = new MetadataRest(); + + MetadataValueRest description = new MetadataValueRest(); description.setValue("

Some cool HTML code here

"); + metadataRest.put("dc.description", description); - MetadataEntryRest abs = new MetadataEntryRest(); - abs.setKey("dc.description.abstract"); + MetadataValueRest abs = new MetadataValueRest(); abs.setValue("Sample top-level community created via the REST API"); + metadataRest.put("dc.description.abstract", abs); - MetadataEntryRest contents = new MetadataEntryRest(); - contents.setKey("dc.description.tableofcontents"); + MetadataValueRest contents = new MetadataValueRest(); contents.setValue("

HTML News

"); + metadataRest.put("dc.description.tableofcontents", contents); - MetadataEntryRest copyright = new MetadataEntryRest(); - copyright.setKey("dc.rights"); + MetadataValueRest copyright = new MetadataValueRest(); copyright.setValue("Custom Copyright Text"); + metadataRest.put("dc.rights", copyright); - MetadataEntryRest title = new MetadataEntryRest(); - title.setKey("dc.title"); + MetadataValueRest title = new MetadataValueRest(); title.setValue("Title Text"); + metadataRest.put("dc.title", title); - comm.setMetadata(Arrays.asList(description, - abs, - contents, - copyright, - title)); + comm.setMetadata(metadataRest); String authToken = getAuthToken(admin.getEmail(), password); getClient(authToken).perform(post("/api/core/communities") @@ -100,19 +99,14 @@ public class CommunityRestRepositoryIT extends AbstractControllerIntegrationTest hasJsonPath("$._links.logo.href", not(empty())), hasJsonPath("$._links.subcommunities.href", not(empty())), hasJsonPath("$._links.self.href", not(empty())), - hasJsonPath("$.metadata", Matchers.containsInAnyOrder( - CommunityMetadataMatcher.matchMetadata("dc.description", - "

Some cool HTML code here

"), - CommunityMetadataMatcher.matchMetadata("dc.description.abstract", - "Sample top-level community created via the REST API"), - CommunityMetadataMatcher.matchMetadata("dc.description.tableofcontents", - "

HTML News

"), - CommunityMetadataMatcher.matchMetadata("dc.rights", - "Custom Copyright Text"), - CommunityMetadataMatcher.matchMetadata("dc.title", - "Title Text") + hasJsonPath("$.metadata", Matchers.allOf( + matchMetadata("dc.description", "

Some cool HTML code here

"), + matchMetadata("dc.description.abstract", + "Sample top-level community created via the REST API"), + matchMetadata("dc.description.tableofcontents", "

HTML News

"), + matchMetadata("dc.rights", "Custom Copyright Text"), + matchMetadata("dc.title", "Title Text") ))))); - } @Test public void createWithParentTest() throws Exception { @@ -130,31 +124,18 @@ public class CommunityRestRepositoryIT extends AbstractControllerIntegrationTest CommunityRest comm = new CommunityRest(); // We send a name but the created community should set this to the title comm.setName("Test Sub-Level Community"); - MetadataEntryRest description = new MetadataEntryRest(); - description.setKey("dc.description"); - description.setValue("

Some cool HTML code here

"); - MetadataEntryRest abs = new MetadataEntryRest(); - abs.setKey("dc.description.abstract"); - abs.setValue("Sample top-level community created via the REST API"); - - MetadataEntryRest contents = new MetadataEntryRest(); - contents.setKey("dc.description.tableofcontents"); - contents.setValue("

HTML News

"); - - MetadataEntryRest copyright = new MetadataEntryRest(); - copyright.setKey("dc.rights"); - copyright.setValue("Custom Copyright Text"); - - MetadataEntryRest title = new MetadataEntryRest(); - title.setKey("dc.title"); - title.setValue("Title Text"); - - comm.setMetadata(Arrays.asList(description, - abs, - contents, - copyright, - title)); + comm.setMetadata(new MetadataRest() + .put("dc.description", + new MetadataValueRest("

Some cool HTML code here

")) + .put("dc.description.abstract", + new MetadataValueRest("Sample top-level community created via the REST API")) + .put("dc.description.tableofcontents", + new MetadataValueRest("

HTML News

")) + .put("dc.rights", + new MetadataValueRest("Custom Copyright Text")) + .put("dc.title", + new MetadataValueRest("Title Text"))); String authToken = getAuthToken(admin.getEmail(), password); getClient(authToken).perform(post("/api/core/communities") @@ -173,18 +154,17 @@ public class CommunityRestRepositoryIT extends AbstractControllerIntegrationTest hasJsonPath("$._links.logo.href", not(empty())), hasJsonPath("$._links.subcommunities.href", not(empty())), hasJsonPath("$._links.self.href", not(empty())), - hasJsonPath("$.metadata", Matchers.containsInAnyOrder( - CommunityMetadataMatcher.matchMetadata("dc.description", - "

Some cool HTML code here

"), - CommunityMetadataMatcher.matchMetadata("dc.description.abstract", - "Sample top-level community " + - "created via the REST API"), - CommunityMetadataMatcher.matchMetadata("dc.description.tableofcontents", - "

HTML News

"), - CommunityMetadataMatcher.matchMetadata("dc.rights", - "Custom Copyright Text"), - CommunityMetadataMatcher.matchMetadata("dc.title", - "Title Text") + hasJsonPath("$.metadata", Matchers.allOf( + MetadataMatcher.matchMetadata("dc.description", + "

Some cool HTML code here

"), + MetadataMatcher.matchMetadata("dc.description.abstract", + "Sample top-level community created via the REST API"), + MetadataMatcher.matchMetadata("dc.description.tableofcontents", + "

HTML News

"), + MetadataMatcher.matchMetadata("dc.rights", + "Custom Copyright Text"), + MetadataMatcher.matchMetadata("dc.title", + "Title Text") ))))); @@ -200,11 +180,13 @@ public class CommunityRestRepositoryIT extends AbstractControllerIntegrationTest CommunityRest comm = new CommunityRest(); comm.setName("Test Top-Level Community"); - MetadataEntryRest title = new MetadataEntryRest(); - title.setKey("dc.title"); - title.setValue("Title Text"); + MetadataRest metadataRest = new MetadataRest(); - comm.setMetadata(Arrays.asList(title)); + MetadataValueRest title = new MetadataValueRest(); + title.setValue("Title Text"); + metadataRest.put("dc.title", title); + + comm.setMetadata(metadataRest); // Anonymous user tries to create a community. // Should fail because user is not authenticated. Error 401. @@ -638,12 +620,8 @@ public class CommunityRestRepositoryIT extends AbstractControllerIntegrationTest CommunityRest communityRest = communityConverter.fromModel(parentCommunity); - MetadataEntryRest metadataEntryRest = new MetadataEntryRest(); - metadataEntryRest.setKey("dc.title"); - metadataEntryRest.setValue("Electronic theses and dissertations"); - - communityRest.setMetadata(Arrays.asList(metadataEntryRest)); - + communityRest.setMetadata(new MetadataRest() + .put("dc.title", new MetadataValueRest("Electronic theses and dissertations"))); getClient(token).perform(put("/api/core/communities/" + parentCommunity.getID().toString()) .contentType(MediaType.APPLICATION_JSON) @@ -845,11 +823,8 @@ public class CommunityRestRepositoryIT extends AbstractControllerIntegrationTest CommunityRest communityRest = communityConverter.fromModel(parentCommunity); - MetadataEntryRest metadataEntryRest = new MetadataEntryRest(); - metadataEntryRest.setKey("dc.title"); - metadataEntryRest.setValue("Electronic theses and dissertations"); - - communityRest.setMetadata(Arrays.asList(metadataEntryRest)); + communityRest.setMetadata(new MetadataRest() + .put("dc.title", new MetadataValueRest("Electronic theses and dissertations"))); context.setCurrentUser(eperson); authorizeService.addPolicy(context, parentCommunity, Constants.WRITE, eperson); diff --git a/dspace-spring-rest/src/test/java/org/dspace/app/rest/EPersonRestRepositoryIT.java b/dspace-spring-rest/src/test/java/org/dspace/app/rest/EPersonRestRepositoryIT.java index 677fdab754..82dc0e6a3b 100644 --- a/dspace-spring-rest/src/test/java/org/dspace/app/rest/EPersonRestRepositoryIT.java +++ b/dspace-spring-rest/src/test/java/org/dspace/app/rest/EPersonRestRepositoryIT.java @@ -8,6 +8,7 @@ package org.dspace.app.rest; import static com.jayway.jsonpath.matchers.JsonPathMatchers.hasJsonPath; +import static org.dspace.app.rest.matcher.MetadataMatcher.matchMetadata; import static org.hamcrest.Matchers.empty; import static org.hamcrest.Matchers.is; import static org.hamcrest.Matchers.not; @@ -21,7 +22,6 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers. import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; import java.util.ArrayList; -import java.util.Arrays; import java.util.List; import java.util.UUID; import javax.ws.rs.core.MediaType; @@ -32,9 +32,9 @@ import org.dspace.app.rest.builder.CommunityBuilder; import org.dspace.app.rest.builder.EPersonBuilder; import org.dspace.app.rest.builder.ItemBuilder; import org.dspace.app.rest.matcher.EPersonMatcher; -import org.dspace.app.rest.matcher.EPersonMetadataMatcher; import org.dspace.app.rest.model.EPersonRest; -import org.dspace.app.rest.model.MetadataEntryRest; +import org.dspace.app.rest.model.MetadataRest; +import org.dspace.app.rest.model.MetadataValueRest; import org.dspace.app.rest.model.patch.Operation; import org.dspace.app.rest.model.patch.ReplaceOperation; import org.dspace.app.rest.test.AbstractControllerIntegrationTest; @@ -54,15 +54,16 @@ public class EPersonRestRepositoryIT extends AbstractControllerIntegrationTest { // we should check how to get it from Spring ObjectMapper mapper = new ObjectMapper(); EPersonRest data = new EPersonRest(); + MetadataRest metadataRest = new MetadataRest(); data.setEmail("createtest@fake-email.com"); data.setCanLogIn(true); - MetadataEntryRest surname = new MetadataEntryRest(); - surname.setKey("eperson.lastname"); + MetadataValueRest surname = new MetadataValueRest(); surname.setValue("Doe"); - MetadataEntryRest firstname = new MetadataEntryRest(); - firstname.setKey("eperson.firstname"); + metadataRest.put("eperson.lastname", surname); + MetadataValueRest firstname = new MetadataValueRest(); firstname.setValue("John"); - data.setMetadata(Arrays.asList(surname, firstname)); + metadataRest.put("eperson.firstname", firstname); + data.setMetadata(metadataRest); String authToken = getAuthToken(admin.getEmail(), password); getClient(authToken).perform(post("/api/eperson/epersons") @@ -79,9 +80,9 @@ public class EPersonRestRepositoryIT extends AbstractControllerIntegrationTest { hasJsonPath("$.canLogIn", is(true)), hasJsonPath("$.requireCertificate", is(false)), hasJsonPath("$._links.self.href", not(empty())), - hasJsonPath("$.metadata", Matchers.containsInAnyOrder( - EPersonMetadataMatcher.matchFirstName("John"), - EPersonMetadataMatcher.matchLastName("Doe") + hasJsonPath("$.metadata", Matchers.allOf( + matchMetadata("eperson.firstname", "John"), + matchMetadata("eperson.lastname", "Doe") ))))); // TODO cleanup the context!!! } diff --git a/dspace-spring-rest/src/test/java/org/dspace/app/rest/ItemRestRepositoryIT.java b/dspace-spring-rest/src/test/java/org/dspace/app/rest/ItemRestRepositoryIT.java index e0098d3453..872e09a655 100644 --- a/dspace-spring-rest/src/test/java/org/dspace/app/rest/ItemRestRepositoryIT.java +++ b/dspace-spring-rest/src/test/java/org/dspace/app/rest/ItemRestRepositoryIT.java @@ -8,7 +8,6 @@ package org.dspace.app.rest; import static com.jayway.jsonpath.matchers.JsonPathMatchers.hasJsonPath; -import static org.hamcrest.Matchers.contains; import static org.hamcrest.Matchers.is; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.delete; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; @@ -21,7 +20,6 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers. import java.io.InputStream; import java.util.ArrayList; -import java.util.Arrays; import java.util.List; import java.util.Map; import java.util.UUID; @@ -38,8 +36,10 @@ import org.dspace.app.rest.builder.GroupBuilder; import org.dspace.app.rest.builder.ItemBuilder; import org.dspace.app.rest.builder.WorkspaceItemBuilder; import org.dspace.app.rest.matcher.ItemMatcher; +import org.dspace.app.rest.matcher.MetadataMatcher; import org.dspace.app.rest.model.ItemRest; -import org.dspace.app.rest.model.MetadataEntryRest; +import org.dspace.app.rest.model.MetadataRest; +import org.dspace.app.rest.model.MetadataValueRest; import org.dspace.app.rest.model.patch.Operation; import org.dspace.app.rest.model.patch.ReplaceOperation; import org.dspace.app.rest.test.AbstractControllerIntegrationTest; @@ -1449,31 +1449,12 @@ public class ItemRestRepositoryIT extends AbstractControllerIntegrationTest { itemRest.setDiscoverable(true); itemRest.setWithdrawn(false); - MetadataEntryRest description = new MetadataEntryRest(); - description.setKey("dc.description"); - description.setValue("

Some cool HTML code here

"); - - MetadataEntryRest abs = new MetadataEntryRest(); - abs.setKey("dc.description.abstract"); - abs.setValue("Sample item created via the REST API"); - - MetadataEntryRest contents = new MetadataEntryRest(); - contents.setKey("dc.description.tableofcontents"); - contents.setValue("

HTML News

"); - - MetadataEntryRest copyright = new MetadataEntryRest(); - copyright.setKey("dc.rights"); - copyright.setValue("Custom Copyright Text"); - - MetadataEntryRest title = new MetadataEntryRest(); - title.setKey("dc.title"); - title.setValue("Title Text"); - - itemRest.setMetadata(Arrays.asList(description, - abs, - contents, - copyright, - title)); + itemRest.setMetadata(new MetadataRest() + .put("dc.description", new MetadataValueRest("

Some cool HTML code here

")) + .put("dc.description.abstract", new MetadataValueRest("Sample item created via the REST API")) + .put("dc.description.tableofcontents", new MetadataValueRest("

HTML News

")) + .put("dc.rights", new MetadataValueRest("Custom Copyright Text")) + .put("dc.title", new MetadataValueRest("Title Text"))); String token = getAuthToken(admin.getEmail(), password); MvcResult mvcResult = getClient(token).perform(post("/api/core/items?owningCollection=" + @@ -1496,18 +1477,18 @@ public class ItemRestRepositoryIT extends AbstractControllerIntegrationTest { hasJsonPath("$.name", is("Title Text")), hasJsonPath("$.handle", is(itemHandleString)), hasJsonPath("$.type", is("item")), - hasJsonPath("$.metadata[?(@.key=='dc.title')].value", - contains("Title Text")), - hasJsonPath("$.metadata[?(@.key=='dc.rights')].value", - contains("Custom Copyright Text")), - hasJsonPath("$.metadata[?(@.key=='dc.description.tableofcontents')].value", - contains("

HTML News

")), - hasJsonPath("$.metadata[?(@.key=='dc.description.abstract')].value", - contains("Sample item created via the REST API")), - hasJsonPath("$.metadata[?(@.key=='dc.description')].value", - contains("

Some cool HTML code here

")) - - ))); + hasJsonPath("$.metadata", Matchers.allOf( + MetadataMatcher.matchMetadata("dc.description", + "

Some cool HTML code here

"), + MetadataMatcher.matchMetadata("dc.description.abstract", + "Sample item created via the REST API"), + MetadataMatcher.matchMetadata("dc.description.tableofcontents", + "

HTML News

"), + MetadataMatcher.matchMetadata("dc.rights", + "Custom Copyright Text"), + MetadataMatcher.matchMetadata("dc.title", + "Title Text") + ))))); } @Test @@ -1532,31 +1513,6 @@ public class ItemRestRepositoryIT extends AbstractControllerIntegrationTest { itemRest.setDiscoverable(true); itemRest.setWithdrawn(false); - MetadataEntryRest description = new MetadataEntryRest(); - description.setKey("dc.description"); - description.setValue("

Some cool HTML code here

"); - - MetadataEntryRest abs = new MetadataEntryRest(); - abs.setKey("dc.description.abstract"); - abs.setValue("Sample item created via the REST API"); - - MetadataEntryRest contents = new MetadataEntryRest(); - contents.setKey("dc.description.tableofcontents"); - contents.setValue("

HTML News

"); - - MetadataEntryRest copyright = new MetadataEntryRest(); - copyright.setKey("dc.rights"); - copyright.setValue("Custom Copyright Text"); - - MetadataEntryRest title = new MetadataEntryRest(); - title.setKey("dc.title"); - title.setValue("Title Text"); - - itemRest.setMetadata(Arrays.asList(description, - abs, - contents, - copyright, - title)); String token = getAuthToken(admin.getEmail(), password); MvcResult mvcResult = getClient(token).perform(post("/api/core/items?owningCollection=" + @@ -1571,18 +1527,15 @@ public class ItemRestRepositoryIT extends AbstractControllerIntegrationTest { String itemUuidString = String.valueOf(map.get("uuid")); String itemHandleString = String.valueOf(map.get("handle")); - - title.setValue("New title"); - copyright.setValue("New Custom Copyright Text"); + itemRest.setMetadata(new MetadataRest() + .put("dc.description", new MetadataValueRest("

Some cool HTML code here

")) + .put("dc.description.abstract", new MetadataValueRest("Sample item created via the REST API")) + .put("dc.description.tableofcontents", new MetadataValueRest("

HTML News

")) + .put("dc.rights", new MetadataValueRest("New Custom Copyright Text")) + .put("dc.title", new MetadataValueRest("New title"))); itemRest.setUuid(itemUuidString); itemRest.setHandle(itemHandleString); - itemRest.setMetadata(Arrays.asList(description, - abs, - contents, - copyright, - title)); - itemRest.setName("New title"); mvcResult = getClient(token).perform(put("/api/core/items/" + itemUuidString) .content(mapper.writeValueAsBytes(itemRest)) @@ -1602,18 +1555,18 @@ public class ItemRestRepositoryIT extends AbstractControllerIntegrationTest { hasJsonPath("$.name", is("New title")), hasJsonPath("$.handle", is(itemHandleString)), hasJsonPath("$.type", is("item")), - hasJsonPath("$.metadata[?(@.key=='dc.title')].value", - contains("New title")), - hasJsonPath("$.metadata[?(@.key=='dc.rights')].value", - contains("New Custom Copyright Text")), - hasJsonPath("$.metadata[?(@.key=='dc.description.tableofcontents')].value", - contains("

HTML News

")), - hasJsonPath("$.metadata[?(@.key=='dc.description.abstract')].value", - contains("Sample item created via the REST API")), - hasJsonPath("$.metadata[?(@.key=='dc.description')].value", - contains("

Some cool HTML code here

")) - - ))); + hasJsonPath("$.metadata", Matchers.allOf( + MetadataMatcher.matchMetadata("dc.description", + "

Some cool HTML code here

"), + MetadataMatcher.matchMetadata("dc.description.abstract", + "Sample item created via the REST API"), + MetadataMatcher.matchMetadata("dc.description.tableofcontents", + "

HTML News

"), + MetadataMatcher.matchMetadata("dc.rights", + "New Custom Copyright Text"), + MetadataMatcher.matchMetadata("dc.title", + "New title") + ))))); } @@ -1642,31 +1595,12 @@ public class ItemRestRepositoryIT extends AbstractControllerIntegrationTest { itemRest.setDiscoverable(true); itemRest.setWithdrawn(false); - MetadataEntryRest description = new MetadataEntryRest(); - description.setKey("dc.description"); - description.setValue("

Some cool HTML code here

"); - - MetadataEntryRest abs = new MetadataEntryRest(); - abs.setKey("dc.description.abstract"); - abs.setValue("Sample item created via the REST API"); - - MetadataEntryRest contents = new MetadataEntryRest(); - contents.setKey("dc.description.tableofcontents"); - contents.setValue("

HTML News

"); - - MetadataEntryRest copyright = new MetadataEntryRest(); - copyright.setKey("dc.rights"); - copyright.setValue("Custom Copyright Text"); - - MetadataEntryRest title = new MetadataEntryRest(); - title.setKey("dc.title"); - title.setValue("Title Text"); - - itemRest.setMetadata(Arrays.asList(description, - abs, - contents, - copyright, - title)); + itemRest.setMetadata(new MetadataRest() + .put("dc.description", new MetadataValueRest("

Some cool HTML code here

")) + .put("dc.description.abstract", new MetadataValueRest("Sample item created via the REST API")) + .put("dc.description.tableofcontents", new MetadataValueRest("

HTML News

")) + .put("dc.rights", new MetadataValueRest("Custom Copyright Text")) + .put("dc.title", new MetadataValueRest("Title Text"))); String token = getAuthToken(admin.getEmail(), password); MvcResult mvcResult = getClient(token).perform(post("/api/core/items?owningCollection=" + @@ -1690,18 +1624,18 @@ public class ItemRestRepositoryIT extends AbstractControllerIntegrationTest { hasJsonPath("$.name", is("Title Text")), hasJsonPath("$.handle", is(itemHandleString)), hasJsonPath("$.type", is("item")), - hasJsonPath("$.metadata[?(@.key=='dc.title')].value", - contains("Title Text")), - hasJsonPath("$.metadata[?(@.key=='dc.rights')].value", - contains("Custom Copyright Text")), - hasJsonPath("$.metadata[?(@.key=='dc.description.tableofcontents')].value", - contains("

HTML News

")), - hasJsonPath("$.metadata[?(@.key=='dc.description.abstract')].value", - contains("Sample item created via the REST API")), - hasJsonPath("$.metadata[?(@.key=='dc.description')].value", - contains("

Some cool HTML code here

")) - - ))); + hasJsonPath("$.metadata", Matchers.allOf( + MetadataMatcher.matchMetadata("dc.description", + "

Some cool HTML code here

"), + MetadataMatcher.matchMetadata("dc.description.abstract", + "Sample item created via the REST API"), + MetadataMatcher.matchMetadata("dc.description.tableofcontents", + "

HTML News

"), + MetadataMatcher.matchMetadata("dc.rights", + "Custom Copyright Text"), + MetadataMatcher.matchMetadata("dc.title", + "Title Text") + ))))); getClient(token).perform(delete("/api/core/items/" + itemUuidString)) .andExpect(status().isNoContent()); @@ -1735,31 +1669,12 @@ public class ItemRestRepositoryIT extends AbstractControllerIntegrationTest { itemRest.setDiscoverable(true); itemRest.setWithdrawn(false); - MetadataEntryRest description = new MetadataEntryRest(); - description.setKey("dc.description"); - description.setValue("

Some cool HTML code here

"); - - MetadataEntryRest abs = new MetadataEntryRest(); - abs.setKey("dc.description.abstract"); - abs.setValue("Sample item created via the REST API"); - - MetadataEntryRest contents = new MetadataEntryRest(); - contents.setKey("dc.description.tableofcontents"); - contents.setValue("

HTML News

"); - - MetadataEntryRest copyright = new MetadataEntryRest(); - copyright.setKey("dc.rights"); - copyright.setValue("Custom Copyright Text"); - - MetadataEntryRest title = new MetadataEntryRest(); - title.setKey("dc.title"); - title.setValue("Title Text"); - - itemRest.setMetadata(Arrays.asList(description, - abs, - contents, - copyright, - title)); + itemRest.setMetadata(new MetadataRest() + .put("dc.description", new MetadataValueRest("

Some cool HTML code here

")) + .put("dc.description.abstract", new MetadataValueRest("Sample item created via the REST API")) + .put("dc.description.tableofcontents", new MetadataValueRest("

HTML News

")) + .put("dc.rights", new MetadataValueRest("Custom Copyright Text")) + .put("dc.title", new MetadataValueRest("Title Text"))); String token = getAuthToken(admin.getEmail(), password); MvcResult mvcResult = getClient(token).perform(post("/api/core/items?owningCollection=" + @@ -1783,18 +1698,18 @@ public class ItemRestRepositoryIT extends AbstractControllerIntegrationTest { hasJsonPath("$.name", is("Title Text")), hasJsonPath("$.handle", is(itemHandleString)), hasJsonPath("$.type", is("item")), - hasJsonPath("$.metadata[?(@.key=='dc.title')].value", - contains("Title Text")), - hasJsonPath("$.metadata[?(@.key=='dc.rights')].value", - contains("Custom Copyright Text")), - hasJsonPath("$.metadata[?(@.key=='dc.description.tableofcontents')].value", - contains("

HTML News

")), - hasJsonPath("$.metadata[?(@.key=='dc.description.abstract')].value", - contains("Sample item created via the REST API")), - hasJsonPath("$.metadata[?(@.key=='dc.description')].value", - contains("

Some cool HTML code here

")) - - ))); + hasJsonPath("$.metadata", Matchers.allOf( + MetadataMatcher.matchMetadata("dc.description", + "

Some cool HTML code here

"), + MetadataMatcher.matchMetadata("dc.description.abstract", + "Sample item created via the REST API"), + MetadataMatcher.matchMetadata("dc.description.tableofcontents", + "

HTML News

"), + MetadataMatcher.matchMetadata("dc.rights", + "Custom Copyright Text"), + MetadataMatcher.matchMetadata("dc.title", + "Title Text") + ))))); getClient().perform(delete("/api/core/items/" + itemUuidString)) .andExpect(status().isUnauthorized()); diff --git a/dspace-spring-rest/src/test/java/org/dspace/app/rest/matcher/BitstreamMatcher.java b/dspace-spring-rest/src/test/java/org/dspace/app/rest/matcher/BitstreamMatcher.java index db9caa857c..9ff71928e5 100644 --- a/dspace-spring-rest/src/test/java/org/dspace/app/rest/matcher/BitstreamMatcher.java +++ b/dspace-spring-rest/src/test/java/org/dspace/app/rest/matcher/BitstreamMatcher.java @@ -8,8 +8,8 @@ package org.dspace.app.rest.matcher; import static com.jayway.jsonpath.matchers.JsonPathMatchers.hasJsonPath; +import static org.dspace.app.rest.matcher.MetadataMatcher.matchMetadata; import static org.hamcrest.Matchers.allOf; -import static org.hamcrest.Matchers.containsInAnyOrder; import static org.hamcrest.Matchers.containsString; import static org.hamcrest.Matchers.empty; import static org.hamcrest.Matchers.is; @@ -30,9 +30,9 @@ public class BitstreamMatcher { hasJsonPath("$.uuid", is(bitstream.getID().toString())), hasJsonPath("$.name", is(bitstream.getName())), hasJsonPath("$.bundleName", is("ORIGINAL")), - hasJsonPath("$.metadata", containsInAnyOrder( - BitstreamMetadataMatcher.matchTitle(bitstream.getName()), - BitstreamMetadataMatcher.matchDescription(bitstream.getDescription()) + hasJsonPath("$.metadata", allOf( + matchMetadata("dc.title", bitstream.getName()), + matchMetadata("dc.description", bitstream.getDescription()) )), hasJsonPath("$.sizeBytes", is((int) bitstream.getSizeBytes())), hasJsonPath("$.checkSum", matchChecksum()), diff --git a/dspace-spring-rest/src/test/java/org/dspace/app/rest/matcher/BitstreamMetadataMatcher.java b/dspace-spring-rest/src/test/java/org/dspace/app/rest/matcher/BitstreamMetadataMatcher.java deleted file mode 100644 index 1fba985560..0000000000 --- a/dspace-spring-rest/src/test/java/org/dspace/app/rest/matcher/BitstreamMetadataMatcher.java +++ /dev/null @@ -1,33 +0,0 @@ -/** - * 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.matcher; - -import static com.jayway.jsonpath.matchers.JsonPathMatchers.hasJsonPath; -import static org.hamcrest.Matchers.allOf; -import static org.hamcrest.Matchers.is; - -import org.hamcrest.Matcher; - -public class BitstreamMetadataMatcher { - - private BitstreamMetadataMatcher() { } - - public static Matcher matchTitle(String title) { - return allOf( - hasJsonPath("$.key", is("dc.title")), - hasJsonPath("$.value", is(title)) - ); - } - - public static Matcher matchDescription(String description) { - return allOf( - hasJsonPath("$.key", is("dc.description")), - hasJsonPath("$.value", is(description)) - ); - } -} diff --git a/dspace-spring-rest/src/test/java/org/dspace/app/rest/matcher/CollectionMatcher.java b/dspace-spring-rest/src/test/java/org/dspace/app/rest/matcher/CollectionMatcher.java index 5381c62d93..8e8579c64c 100644 --- a/dspace-spring-rest/src/test/java/org/dspace/app/rest/matcher/CollectionMatcher.java +++ b/dspace-spring-rest/src/test/java/org/dspace/app/rest/matcher/CollectionMatcher.java @@ -32,8 +32,8 @@ public class CollectionMatcher { hasJsonPath("$.name", is(name)), hasJsonPath("$.handle", is(handle)), hasJsonPath("$.type", is("collection")), - hasJsonPath("$.metadata", Matchers.hasItem( - CollectionMetadataMatcher.matchTitle(name) + hasJsonPath("$.metadata", Matchers.allOf( + MetadataMatcher.matchMetadata("dc.title", name) )), matchLinks(uuid), matchLogo(logo) diff --git a/dspace-spring-rest/src/test/java/org/dspace/app/rest/matcher/CollectionMetadataMatcher.java b/dspace-spring-rest/src/test/java/org/dspace/app/rest/matcher/CollectionMetadataMatcher.java deleted file mode 100644 index 3f5bbefe59..0000000000 --- a/dspace-spring-rest/src/test/java/org/dspace/app/rest/matcher/CollectionMetadataMatcher.java +++ /dev/null @@ -1,26 +0,0 @@ -/** - * 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.matcher; - -import static com.jayway.jsonpath.matchers.JsonPathMatchers.hasJsonPath; -import static org.hamcrest.CoreMatchers.is; -import static org.hamcrest.Matchers.allOf; - -import org.hamcrest.Matcher; - -public class CollectionMetadataMatcher { - - private CollectionMetadataMatcher() { } - - public static Matcher matchTitle(String title) { - return allOf( - hasJsonPath("$.key", is("dc.title")), - hasJsonPath("$.value", is(title)) - ); - } -} diff --git a/dspace-spring-rest/src/test/java/org/dspace/app/rest/matcher/CommunityMatcher.java b/dspace-spring-rest/src/test/java/org/dspace/app/rest/matcher/CommunityMatcher.java index 600f67ab52..f1fffaa5c3 100644 --- a/dspace-spring-rest/src/test/java/org/dspace/app/rest/matcher/CommunityMatcher.java +++ b/dspace-spring-rest/src/test/java/org/dspace/app/rest/matcher/CommunityMatcher.java @@ -36,8 +36,8 @@ public class CommunityMatcher { hasJsonPath("$.name", is(name)), hasJsonPath("$.handle", is(handle)), hasJsonPath("$.type", is("community")), - hasJsonPath("$.metadata", Matchers.hasItem( - CommunityMetadataMatcher.matchMetadata("dc.title", name) + hasJsonPath("$.metadata", Matchers.allOf( + MetadataMatcher.matchMetadata("dc.title", name) )) ); } diff --git a/dspace-spring-rest/src/test/java/org/dspace/app/rest/matcher/EPersonMatcher.java b/dspace-spring-rest/src/test/java/org/dspace/app/rest/matcher/EPersonMatcher.java index 421af32dbe..819f1dccf6 100644 --- a/dspace-spring-rest/src/test/java/org/dspace/app/rest/matcher/EPersonMatcher.java +++ b/dspace-spring-rest/src/test/java/org/dspace/app/rest/matcher/EPersonMatcher.java @@ -29,9 +29,9 @@ public class EPersonMatcher { hasJsonPath("$.type", is("eperson")), hasJsonPath("$.canLogIn", not(empty())), hasJsonPath("$._links.self.href", containsString("/api/eperson/epersons/" + ePerson.getID().toString())), - hasJsonPath("$.metadata", Matchers.hasItems( - EPersonMetadataMatcher.matchFirstName(ePerson.getFirstName()), - EPersonMetadataMatcher.matchLastName(ePerson.getLastName()) + hasJsonPath("$.metadata", Matchers.allOf( + MetadataMatcher.matchMetadata("eperson.firstname", ePerson.getFirstName()), + MetadataMatcher.matchMetadata("eperson.lastname", ePerson.getLastName()) )) ); } diff --git a/dspace-spring-rest/src/test/java/org/dspace/app/rest/matcher/EPersonMetadataMatcher.java b/dspace-spring-rest/src/test/java/org/dspace/app/rest/matcher/EPersonMetadataMatcher.java deleted file mode 100644 index 74ff800632..0000000000 --- a/dspace-spring-rest/src/test/java/org/dspace/app/rest/matcher/EPersonMetadataMatcher.java +++ /dev/null @@ -1,40 +0,0 @@ -/** - * 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.matcher; - -import static com.jayway.jsonpath.matchers.JsonPathMatchers.hasJsonPath; -import static org.hamcrest.Matchers.allOf; -import static org.hamcrest.Matchers.is; - -import org.hamcrest.Matcher; - -public class EPersonMetadataMatcher { - - private EPersonMetadataMatcher() { } - - public static Matcher matchFirstName(String firstName) { - return allOf( - hasJsonPath("$.key", is("eperson.firstname")), - hasJsonPath("$.value", is(firstName)) - ); - } - - public static Matcher matchLastName(String lastName) { - return allOf( - hasJsonPath("$.key", is("eperson.lastname")), - hasJsonPath("$.value", is(lastName)) - ); - } - - public static Matcher matchLanguage(String language) { - return allOf( - hasJsonPath("$.key", is("eperson.language")), - hasJsonPath("$.value", is(language)) - ); - } -} diff --git a/dspace-spring-rest/src/test/java/org/dspace/app/rest/matcher/ItemMatcher.java b/dspace-spring-rest/src/test/java/org/dspace/app/rest/matcher/ItemMatcher.java index e5176eea26..0f6eb553f4 100644 --- a/dspace-spring-rest/src/test/java/org/dspace/app/rest/matcher/ItemMatcher.java +++ b/dspace-spring-rest/src/test/java/org/dspace/app/rest/matcher/ItemMatcher.java @@ -8,9 +8,9 @@ package org.dspace.app.rest.matcher; import static com.jayway.jsonpath.matchers.JsonPathMatchers.hasJsonPath; +import static org.dspace.app.rest.matcher.MetadataMatcher.matchMetadata; import static org.dspace.app.rest.test.AbstractControllerIntegrationTest.REST_SERVER_URL; import static org.hamcrest.Matchers.allOf; -import static org.hamcrest.Matchers.contains; import static org.hamcrest.Matchers.is; import static org.hamcrest.Matchers.notNullValue; import static org.hamcrest.Matchers.startsWith; @@ -34,8 +34,9 @@ public class ItemMatcher { matchItemProperties(item), //Check core metadata (the JSON Path expression evaluates to a collection so we have to use contains) - hasJsonPath("$.metadata[?(@.key=='dc.title')].value", contains(title)), - hasJsonPath("$.metadata[?(@.key=='dc.date.issued')].value", contains(dateIssued)), + hasJsonPath("$.metadata", allOf( + matchMetadata("dc.title", title), + matchMetadata("dc.date.issued", dateIssued))), //Check links matchItemLinks(item) diff --git a/dspace-spring-rest/src/test/java/org/dspace/app/rest/matcher/CommunityMetadataMatcher.java b/dspace-spring-rest/src/test/java/org/dspace/app/rest/matcher/MetadataMatcher.java similarity index 56% rename from dspace-spring-rest/src/test/java/org/dspace/app/rest/matcher/CommunityMetadataMatcher.java rename to dspace-spring-rest/src/test/java/org/dspace/app/rest/matcher/MetadataMatcher.java index ec251e86ea..73ed1381b9 100644 --- a/dspace-spring-rest/src/test/java/org/dspace/app/rest/matcher/CommunityMetadataMatcher.java +++ b/dspace-spring-rest/src/test/java/org/dspace/app/rest/matcher/MetadataMatcher.java @@ -8,19 +8,20 @@ package org.dspace.app.rest.matcher; import static com.jayway.jsonpath.matchers.JsonPathMatchers.hasJsonPath; -import static org.hamcrest.Matchers.allOf; +import static org.hamcrest.Matchers.contains; import static org.hamcrest.Matchers.is; import org.hamcrest.Matcher; -public class CommunityMetadataMatcher { +public class MetadataMatcher { - private CommunityMetadataMatcher() { } + private MetadataMatcher() { } public static Matcher matchMetadata(String key, String value) { - return allOf( - hasJsonPath("$.key", is(key)), - hasJsonPath("$.value", is(value)) - ); + return hasJsonPath("$.['" + key + "'][*].value", contains(value)); + } + + public static Matcher matchMetadata(String key, String value, int position) { + return hasJsonPath("$.['" + key + "'][" + position + "].value", is(value)); } } From 0609a6f0b7321a36eb7225f16de4a6eaa55c03c9 Mon Sep 17 00:00:00 2001 From: Chris Wilper Date: Thu, 7 Feb 2019 06:12:04 -0500 Subject: [PATCH 61/68] DS-4107 Improve javadocs --- .../app/rest/converter/MetadataConverter.java | 15 ++++++++++--- .../converter/MetadataValueConverter.java | 21 +++++++++++++------ .../app/rest/model/DSpaceObjectRest.java | 5 +++++ .../dspace/app/rest/model/MetadataRest.java | 14 ++++++++++--- .../app/rest/model/MetadataValueRest.java | 13 ++++++++++++ .../app/rest/matcher/MetadataMatcher.java | 18 ++++++++++++++++ 6 files changed, 74 insertions(+), 12 deletions(-) diff --git a/dspace-spring-rest/src/main/java/org/dspace/app/rest/converter/MetadataConverter.java b/dspace-spring-rest/src/main/java/org/dspace/app/rest/converter/MetadataConverter.java index ec1835bf07..51710f45af 100644 --- a/dspace-spring-rest/src/main/java/org/dspace/app/rest/converter/MetadataConverter.java +++ b/dspace-spring-rest/src/main/java/org/dspace/app/rest/converter/MetadataConverter.java @@ -29,6 +29,9 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.core.convert.converter.Converter; import org.springframework.stereotype.Component; +/** + * Converter to translate between lists of domain {@link MetadataValue}s and {@link MetadataRest} representations. + */ @Component public class MetadataConverter implements Converter, MetadataRest> { @@ -38,6 +41,12 @@ public class MetadataConverter implements Converter, Metadat @Autowired private MetadataValueConverter valueConverter; + /** + * Gets a rest representation of the given list of domain metadata values. + * + * @param metadataValueList the domain values. + * @return the rest representation. + */ @Override public MetadataRest convert(List metadataValueList) { // Convert each value to a DTO while retaining place order in a map of key -> SortedSet @@ -64,11 +73,11 @@ public class MetadataConverter implements Converter, Metadat } /** - * Completely replaces the metadata in the given dso. + * Sets a DSpace object's domain metadata values from a rest representation. * * @param context the context to use. - * @param dso the dso whose metadata should be updated. - * @param metadataRest the new metadata. + * @param dso the DSpace object. + * @param metadataRest the rest representation of the new metadata. * @throws SQLException if a database error occurs. * @throws AuthorizeException if an authorization error occurs. */ diff --git a/dspace-spring-rest/src/main/java/org/dspace/app/rest/converter/MetadataValueConverter.java b/dspace-spring-rest/src/main/java/org/dspace/app/rest/converter/MetadataValueConverter.java index 1dfe4ab12f..1095e1870a 100644 --- a/dspace-spring-rest/src/main/java/org/dspace/app/rest/converter/MetadataValueConverter.java +++ b/dspace-spring-rest/src/main/java/org/dspace/app/rest/converter/MetadataValueConverter.java @@ -12,17 +12,26 @@ import org.dspace.content.MetadataValue; import org.springframework.core.convert.converter.Converter; import org.springframework.stereotype.Component; +/** + * Converter to translate between domain {@link MetadataValue}s and {@link MetadataValueRest} representations. + */ @Component public class MetadataValueConverter implements Converter { + /** + * Gets a rest representation of the given domain metadata value. + * + * @param metadataValue the domain value. + * @return the rest representation. + */ @Override - public MetadataValueRest convert(MetadataValue model) { + public MetadataValueRest convert(MetadataValue metadataValue) { MetadataValueRest metadataValueRest = new MetadataValueRest(); - metadataValueRest.setValue(model.getValue()); - metadataValueRest.setLanguage(model.getLanguage()); - metadataValueRest.setAuthority(model.getAuthority()); - metadataValueRest.setConfidence(model.getConfidence()); - metadataValueRest.setPlace(model.getPlace()); + metadataValueRest.setValue(metadataValue.getValue()); + metadataValueRest.setLanguage(metadataValue.getLanguage()); + metadataValueRest.setAuthority(metadataValue.getAuthority()); + metadataValueRest.setConfidence(metadataValue.getConfidence()); + metadataValueRest.setPlace(metadataValue.getPlace()); return metadataValueRest; } } diff --git a/dspace-spring-rest/src/main/java/org/dspace/app/rest/model/DSpaceObjectRest.java b/dspace-spring-rest/src/main/java/org/dspace/app/rest/model/DSpaceObjectRest.java index c0ef206aa1..1b71eb8957 100644 --- a/dspace-spring-rest/src/main/java/org/dspace/app/rest/model/DSpaceObjectRest.java +++ b/dspace-spring-rest/src/main/java/org/dspace/app/rest/model/DSpaceObjectRest.java @@ -51,6 +51,11 @@ public abstract class DSpaceObjectRest extends BaseObjectRest { this.handle = handle; } + /** + * Gets the rest representation of all metadata of the DSpace object. + * + * @return the metadata. + */ public MetadataRest getMetadata() { return metadata; } diff --git a/dspace-spring-rest/src/main/java/org/dspace/app/rest/model/MetadataRest.java b/dspace-spring-rest/src/main/java/org/dspace/app/rest/model/MetadataRest.java index 6225843d62..f1f5ec11ce 100644 --- a/dspace-spring-rest/src/main/java/org/dspace/app/rest/model/MetadataRest.java +++ b/dspace-spring-rest/src/main/java/org/dspace/app/rest/model/MetadataRest.java @@ -15,11 +15,19 @@ import java.util.TreeMap; import com.fasterxml.jackson.annotation.JsonAnyGetter; import com.fasterxml.jackson.annotation.JsonAnySetter; +/** + * Rest representation of a map of metadata keys to ordered lists of values. + */ public class MetadataRest { @JsonAnySetter private SortedMap> map = new TreeMap(); + /** + * Gets the map. + * + * @return the map of keys to ordered values. + */ @JsonAnyGetter public SortedMap> getMap() { return map; @@ -30,9 +38,9 @@ public class MetadataRest { * * @param key the key. * @param values the values. The values will be ordered according to their {@code place} value, if - * nonnegative. Values that are negative (the default is -1) are assume to be non-explicitly - * set and will will be ordered at the end of the list, after the last value with an order, - * in the order they are passed to this method. + * nonnegative. Values that are negative (the default is -1) are assumed to be non-explicitly + * set and will will be ordered at the end of any explicitly ordered values, in the order + * they are passed to this method. * @return this instance, to support chaining calls for easy initialization. */ public MetadataRest put(String key, MetadataValueRest... values) { diff --git a/dspace-spring-rest/src/main/java/org/dspace/app/rest/model/MetadataValueRest.java b/dspace-spring-rest/src/main/java/org/dspace/app/rest/model/MetadataValueRest.java index 768a81a4ff..013e235847 100644 --- a/dspace-spring-rest/src/main/java/org/dspace/app/rest/model/MetadataValueRest.java +++ b/dspace-spring-rest/src/main/java/org/dspace/app/rest/model/MetadataValueRest.java @@ -7,7 +7,10 @@ */ package org.dspace.app.rest.model; +import java.util.List; + import com.fasterxml.jackson.annotation.JsonIgnore; +import org.dspace.app.rest.converter.MetadataConverter; /** * An embeddable representation of the Metadata to use in with DSpace REST @@ -25,6 +28,16 @@ public class MetadataValueRest { int confidence; + /** + * The order of this metadata value with respect to others in the same DSO with the same key. + * + * In the REST representation, all values of the same key are given as a json array that expresses + * their relative order, so there is no need to expose the exact numeric value publicly. The numeric + * value is only used at this level to ensure the intended order is respected when converting to/from json. + * + * @see MetadataConverter#convert(List) + * @see MetadataRest#put(String, MetadataValueRest...) + */ @JsonIgnore int place = -1; diff --git a/dspace-spring-rest/src/test/java/org/dspace/app/rest/matcher/MetadataMatcher.java b/dspace-spring-rest/src/test/java/org/dspace/app/rest/matcher/MetadataMatcher.java index 73ed1381b9..ed821d1f60 100644 --- a/dspace-spring-rest/src/test/java/org/dspace/app/rest/matcher/MetadataMatcher.java +++ b/dspace-spring-rest/src/test/java/org/dspace/app/rest/matcher/MetadataMatcher.java @@ -13,14 +13,32 @@ import static org.hamcrest.Matchers.is; import org.hamcrest.Matcher; +/** + * Utility class to provide convenient matchers for metadata. + */ public class MetadataMatcher { private MetadataMatcher() { } + /** + * Gets a matcher to ensure a given value is present among all values for a given metadata key. + * + * @param key the metadata key. + * @param value the value that must be present. + * @return the matcher. + */ public static Matcher matchMetadata(String key, String value) { return hasJsonPath("$.['" + key + "'][*].value", contains(value)); } + /** + * Gets a matcher to ensure a given value is present at a specific position in the list of values for a given key. + * + * @param key the metadata key. + * @param value the value that must be present. + * @param position the position it must be present at. + * @return the matcher. + */ public static Matcher matchMetadata(String key, String value, int position) { return hasJsonPath("$.['" + key + "'][" + position + "].value", is(value)); } From 644970eba4adb6fb5793b94c182ce9ace64b437d Mon Sep 17 00:00:00 2001 From: Terry Brady Date: Mon, 25 Feb 2019 08:38:18 -0800 Subject: [PATCH 62/68] set build dir owner to dspace --- Dockerfile.jdk8 | 3 ++- Dockerfile.jdk8-test | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/Dockerfile.jdk8 b/Dockerfile.jdk8 index ec465b50b3..afe2285f49 100644 --- a/Dockerfile.jdk8 +++ b/Dockerfile.jdk8 @@ -15,7 +15,8 @@ WORKDIR /app # The dspace-install directory will be written to /install RUN mkdir /install \ - && chown -Rv dspace: /install + && chown -Rv dspace: /install \ + && chown -Rv dspace: /app USER dspace diff --git a/Dockerfile.jdk8-test b/Dockerfile.jdk8-test index e4f398d867..01697a045c 100644 --- a/Dockerfile.jdk8-test +++ b/Dockerfile.jdk8-test @@ -15,7 +15,8 @@ WORKDIR /app # The dspace-install directory will be written to /install RUN mkdir /install \ - && chown -Rv dspace: /install + && chown -Rv dspace: /install \ + && chown -Rv dspace: /app USER dspace From 1ad0c88c3c4da81e4534b644eb7b81214314be70 Mon Sep 17 00:00:00 2001 From: Chris Wilper Date: Sat, 29 Dec 2018 15:22:00 -0500 Subject: [PATCH 63/68] DS-3908 Add PATCH support for DSO metadata --- dspace-spring-rest/pom.xml | 6 ++ .../rest/converter/JsonPatchConverter.java | 3 +- .../dspace/app/rest/model/MetadataRest.java | 5 ++ .../rest/model/patch/JsonValueEvaluator.java | 3 + .../repository/BitstreamRestRepository.java | 29 ++++--- .../repository/CollectionRestRepository.java | 32 ++++--- .../repository/CommunityRestRepository.java | 32 ++++--- .../DSpaceObjectRestRepository.java | 70 ++++++++++++++++ .../repository/EPersonRestRepository.java | 65 +++++---------- .../rest/repository/GroupRestRepository.java | 23 +++-- .../rest/repository/ItemRestRepository.java | 83 +++++++------------ .../rest/repository/SiteRestRepository.java | 31 ++++--- .../patch/AbstractResourcePatch.java | 2 +- .../repository/patch/DSpaceObjectPatch.java | 66 +++++++++++++++ .../rest/repository/patch/EPersonPatch.java | 2 +- .../app/rest/repository/patch/ItemPatch.java | 2 +- 16 files changed, 304 insertions(+), 150 deletions(-) create mode 100644 dspace-spring-rest/src/main/java/org/dspace/app/rest/repository/DSpaceObjectRestRepository.java create mode 100644 dspace-spring-rest/src/main/java/org/dspace/app/rest/repository/patch/DSpaceObjectPatch.java diff --git a/dspace-spring-rest/pom.xml b/dspace-spring-rest/pom.xml index 3ea878747b..0e9e03f034 100644 --- a/dspace-spring-rest/pom.xml +++ b/dspace-spring-rest/pom.xml @@ -226,6 +226,12 @@ test + + com.flipkart.zjsonpatch + zjsonpatch + 0.4.6 + + org.springframework.data diff --git a/dspace-spring-rest/src/main/java/org/dspace/app/rest/converter/JsonPatchConverter.java b/dspace-spring-rest/src/main/java/org/dspace/app/rest/converter/JsonPatchConverter.java index c9d387b591..aba4d6a995 100644 --- a/dspace-spring-rest/src/main/java/org/dspace/app/rest/converter/JsonPatchConverter.java +++ b/dspace-spring-rest/src/main/java/org/dspace/app/rest/converter/JsonPatchConverter.java @@ -116,7 +116,8 @@ public class JsonPatchConverter implements PatchConverter { Object value = operation.getValue(); if (value != null) { - opNode.set("value", mapper.valueToTree(value)); + opNode.set("value", value instanceof JsonValueEvaluator ? ((JsonValueEvaluator) value).getValueNode() + : mapper.valueToTree(value)); } patchNode.add(opNode); diff --git a/dspace-spring-rest/src/main/java/org/dspace/app/rest/model/MetadataRest.java b/dspace-spring-rest/src/main/java/org/dspace/app/rest/model/MetadataRest.java index f1f5ec11ce..23474e793e 100644 --- a/dspace-spring-rest/src/main/java/org/dspace/app/rest/model/MetadataRest.java +++ b/dspace-spring-rest/src/main/java/org/dspace/app/rest/model/MetadataRest.java @@ -61,4 +61,9 @@ public class MetadataRest { map.put(key, Arrays.asList(values)); return this; } + + @Override + public boolean equals(Object object) { + return object instanceof MetadataRest && ((MetadataRest) object).getMap().equals(map); + } } \ No newline at end of file diff --git a/dspace-spring-rest/src/main/java/org/dspace/app/rest/model/patch/JsonValueEvaluator.java b/dspace-spring-rest/src/main/java/org/dspace/app/rest/model/patch/JsonValueEvaluator.java index 596fa3049b..76802d9aa0 100644 --- a/dspace-spring-rest/src/main/java/org/dspace/app/rest/model/patch/JsonValueEvaluator.java +++ b/dspace-spring-rest/src/main/java/org/dspace/app/rest/model/patch/JsonValueEvaluator.java @@ -45,4 +45,7 @@ public class JsonValueEvaluator implements LateObjectEvaluator { } } + public JsonNode getValueNode() { + return this.valueNode; + } } diff --git a/dspace-spring-rest/src/main/java/org/dspace/app/rest/repository/BitstreamRestRepository.java b/dspace-spring-rest/src/main/java/org/dspace/app/rest/repository/BitstreamRestRepository.java index 18233a3906..d1ce46a719 100644 --- a/dspace-spring-rest/src/main/java/org/dspace/app/rest/repository/BitstreamRestRepository.java +++ b/dspace-spring-rest/src/main/java/org/dspace/app/rest/repository/BitstreamRestRepository.java @@ -14,11 +14,14 @@ import java.util.ArrayList; import java.util.Iterator; import java.util.List; import java.util.UUID; +import javax.servlet.http.HttpServletRequest; import org.dspace.app.rest.converter.BitstreamConverter; import org.dspace.app.rest.exception.UnprocessableEntityException; import org.dspace.app.rest.model.BitstreamRest; import org.dspace.app.rest.model.hateoas.BitstreamResource; +import org.dspace.app.rest.model.patch.Patch; +import org.dspace.app.rest.repository.patch.DSpaceObjectPatch; import org.dspace.authorize.AuthorizeException; import org.dspace.content.Bitstream; import org.dspace.content.service.BitstreamService; @@ -39,16 +42,15 @@ import org.springframework.stereotype.Component; */ @Component(BitstreamRest.CATEGORY + "." + BitstreamRest.NAME) -public class BitstreamRestRepository extends DSpaceRestRepository { +public class BitstreamRestRepository extends DSpaceObjectRestRepository { + + private final BitstreamService bs; @Autowired - BitstreamService bs; - - @Autowired - BitstreamConverter converter; - - public BitstreamRestRepository() { - System.out.println("Repository initialized by Spring"); + public BitstreamRestRepository(BitstreamService dsoService, + BitstreamConverter dsoConverter) { + super(dsoService, dsoConverter, new DSpaceObjectPatch() { }); + this.bs = dsoService; } @Override @@ -70,7 +72,7 @@ public class BitstreamRestRepository extends DSpaceRestRepository page = new PageImpl(bit, pageable, total).map(converter); + Page page = new PageImpl(bit, pageable, total).map(dsoConverter); return page; } + @Override + @PreAuthorize("hasPermission(#id, 'BITSTREAM', 'WRITE')") + protected void patch(Context context, HttpServletRequest request, String apiCategory, String model, UUID id, + Patch patch) throws AuthorizeException, SQLException { + patchDSpaceObject(apiCategory, model, id, patch); + } + @Override public Class getDomainClass() { return BitstreamRest.class; diff --git a/dspace-spring-rest/src/main/java/org/dspace/app/rest/repository/CollectionRestRepository.java b/dspace-spring-rest/src/main/java/org/dspace/app/rest/repository/CollectionRestRepository.java index c00b7d3b07..a7ba1a5552 100644 --- a/dspace-spring-rest/src/main/java/org/dspace/app/rest/repository/CollectionRestRepository.java +++ b/dspace-spring-rest/src/main/java/org/dspace/app/rest/repository/CollectionRestRepository.java @@ -12,7 +12,6 @@ import java.sql.SQLException; import java.util.ArrayList; import java.util.List; import java.util.UUID; - import javax.servlet.ServletInputStream; import javax.servlet.http.HttpServletRequest; import javax.ws.rs.BadRequestException; @@ -29,6 +28,8 @@ import org.dspace.app.rest.exception.UnprocessableEntityException; import org.dspace.app.rest.model.CollectionRest; import org.dspace.app.rest.model.CommunityRest; import org.dspace.app.rest.model.hateoas.CollectionResource; +import org.dspace.app.rest.model.patch.Patch; +import org.dspace.app.rest.repository.patch.DSpaceObjectPatch; import org.dspace.app.rest.utils.CollectionRestEqualityUtils; import org.dspace.authorize.AuthorizeException; import org.dspace.content.Collection; @@ -53,14 +54,13 @@ import org.springframework.stereotype.Component; */ @Component(CollectionRest.CATEGORY + "." + CollectionRest.NAME) -public class CollectionRestRepository extends DSpaceRestRepository { +public class CollectionRestRepository extends DSpaceObjectRestRepository { + + private final CollectionService cs; @Autowired CommunityService communityService; - @Autowired - CollectionService cs; - @Autowired CollectionConverter converter; @@ -71,8 +71,10 @@ public class CollectionRestRepository extends DSpaceRestRepository() {}); + this.cs = dsoService; } @Override @@ -87,7 +89,7 @@ public class CollectionRestRepository extends DSpaceRestRepository page = new PageImpl(collections, pageable, total).map(converter); + Page page = new PageImpl(collections, pageable, total).map(dsoConverter); return page; } @@ -128,7 +130,7 @@ public class CollectionRestRepository extends DSpaceRestRepository page = utils.getPage(collections, pageable).map(converter); + Page page = utils.getPage(collections, pageable).map(dsoConverter); return page; } @@ -145,10 +147,17 @@ public class CollectionRestRepository extends DSpaceRestRepository page = utils.getPage(collections, pageable).map(converter); + Page page = utils.getPage(collections, pageable).map(dsoConverter); return page; } + @Override + @PreAuthorize("hasPermission(#id, 'COLLECTION', 'WRITE')") + protected void patch(Context context, HttpServletRequest request, String apiCategory, String model, UUID id, + Patch patch) throws AuthorizeException, SQLException { + patchDSpaceObject(apiCategory, model, id, patch); + } + @Override public Class getDomainClass() { return CollectionRest.class; @@ -252,5 +261,4 @@ public class CollectionRestRepository extends DSpaceRestRepository { +public class CommunityRestRepository extends DSpaceObjectRestRepository { - @Autowired - CommunityService cs; + private final CommunityService cs; @Autowired CommunityConverter converter; @@ -63,8 +63,10 @@ public class CommunityRestRepository extends DSpaceRestRepository() {}); + this.cs = dsoService; } @Override @@ -107,7 +109,7 @@ public class CommunityRestRepository extends DSpaceRestRepository page = new PageImpl(communities, pageable, total).map(converter); + Page page = new PageImpl(communities, pageable, total).map(dsoConverter); return page; } @@ -153,7 +155,7 @@ public class CommunityRestRepository extends DSpaceRestRepository page = utils.getPage(topCommunities, pageable).map(converter); + Page page = utils.getPage(topCommunities, pageable).map(dsoConverter); return page; } @@ -174,10 +176,17 @@ public class CommunityRestRepository extends DSpaceRestRepository page = utils.getPage(subCommunities, pageable).map(converter); + Page page = utils.getPage(subCommunities, pageable).map(dsoConverter); return page; } + @Override + @PreAuthorize("hasPermission(#id, 'COMMUNITY', 'WRITE')") + protected void patch(Context context, HttpServletRequest request, String apiCategory, String model, UUID id, + Patch patch) throws AuthorizeException, SQLException { + patchDSpaceObject(apiCategory, model, id, patch); + } + @Override public Class getDomainClass() { return CommunityRest.class; @@ -233,5 +242,4 @@ public class CommunityRestRepository extends DSpaceRestRepository + extends DSpaceRestRepository { + + final DSpaceObjectService dsoService; + final DSpaceObjectPatch dsoPatch; + final DSpaceObjectConverter dsoConverter; + + @Autowired + MetadataConverter metadataConverter; + + DSpaceObjectRestRepository(DSpaceObjectService dsoService, + DSpaceObjectConverter dsoConverter, + DSpaceObjectPatch dsoPatch) { + this.dsoService = dsoService; + this.dsoPatch = dsoPatch; + this.dsoConverter = dsoConverter; + } + + protected void patchDSpaceObject(String apiCategory, String model, UUID id, Patch patch) + throws AuthorizeException, PatchBadRequestException, ResourceNotFoundException, + SQLException, UnprocessableEntityException { + M dso = dsoService.find(obtainContext(), id); + if (dso == null) { + throw new ResourceNotFoundException(apiCategory + "." + model + " with id: " + id + " not found"); + } + R dsoRest = dsoPatch.patch(findOne(id), patch.getOperations()); + updateDSpaceObject(dso, dsoRest); + } + + /** + * Applies the changes in the given rest DSpace object to the model DSpace object. + * The default implementation updates metadata if needed. Subclasses should extend + * to support updates of additional properties. + * + * @param dso the dso to apply changes to. + * @param dsoRest the rest representation of the new desired state. + */ + protected void updateDSpaceObject(M dso, R dsoRest) + throws AuthorizeException, SQLException { + R origDsoRest = dsoConverter.fromModel(dso); + if (!origDsoRest.getMetadata().equals(dsoRest.getMetadata())) { + metadataConverter.setMetadata(obtainContext(), dso, dsoRest.getMetadata()); + } + } +} diff --git a/dspace-spring-rest/src/main/java/org/dspace/app/rest/repository/EPersonRestRepository.java b/dspace-spring-rest/src/main/java/org/dspace/app/rest/repository/EPersonRestRepository.java index 37853dfb88..f672e64077 100644 --- a/dspace-spring-rest/src/main/java/org/dspace/app/rest/repository/EPersonRestRepository.java +++ b/dspace-spring-rest/src/main/java/org/dspace/app/rest/repository/EPersonRestRepository.java @@ -12,7 +12,6 @@ import java.sql.SQLException; import java.util.List; import java.util.Objects; import java.util.UUID; - import javax.servlet.http.HttpServletRequest; import com.fasterxml.jackson.databind.ObjectMapper; @@ -21,25 +20,21 @@ import org.dspace.app.rest.Parameter; import org.dspace.app.rest.SearchRestMethod; import org.dspace.app.rest.converter.EPersonConverter; import org.dspace.app.rest.converter.MetadataConverter; -import org.dspace.app.rest.exception.PatchBadRequestException; import org.dspace.app.rest.exception.RESTAuthorizationException; import org.dspace.app.rest.exception.UnprocessableEntityException; import org.dspace.app.rest.model.EPersonRest; import org.dspace.app.rest.model.hateoas.EPersonResource; -import org.dspace.app.rest.model.patch.Operation; import org.dspace.app.rest.model.patch.Patch; import org.dspace.app.rest.repository.patch.EPersonPatch; import org.dspace.authorize.AuthorizeException; import org.dspace.authorize.service.AuthorizeService; import org.dspace.core.Context; import org.dspace.eperson.EPerson; -import org.dspace.eperson.factory.EPersonServiceFactory; import org.dspace.eperson.service.EPersonService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.domain.Page; import org.springframework.data.domain.PageImpl; 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; @@ -51,14 +46,12 @@ import org.springframework.stereotype.Component; */ @Component(EPersonRest.CATEGORY + "." + EPersonRest.NAME) -public class EPersonRestRepository extends DSpaceRestRepository { - EPersonService es = EPersonServiceFactory.getInstance().getEPersonService(); +public class EPersonRestRepository extends DSpaceObjectRestRepository { @Autowired AuthorizeService authorizeService; - @Autowired - EPersonConverter converter; + private final EPersonService es; @Autowired MetadataConverter metadataConverter; @@ -66,6 +59,13 @@ public class EPersonRestRepository extends DSpaceRestRepository page = new PageImpl(epersons, pageable, total).map(converter); + Page page = new PageImpl(epersons, pageable, total).map(dsoConverter); return page; } @@ -156,7 +156,7 @@ public class EPersonRestRepository extends DSpaceRestRepository page = new PageImpl(epersons, pageable, total).map(converter); + Page page = new PageImpl(epersons, pageable, total).map(dsoConverter); return page; } @@ -182,42 +182,22 @@ public class EPersonRestRepository extends DSpaceRestRepository operations = patch.getOperations(); - EPersonRest ePersonRest = findOne(context, uuid); - EPersonRest patchedModel = (EPersonRest) epersonPatch.patch(ePersonRest, operations); - updatePatchedValues(context, patchedModel, eperson); - - } catch (SQLException e) { - throw new RuntimeException(e.getMessage(), e); - } + protected void patch(Context context, HttpServletRequest request, String apiCategory, String model, UUID uuid, + Patch patch) throws AuthorizeException, SQLException { + patchDSpaceObject(apiCategory, model, uuid, patch); } - /** - * Applies changes in the rest model. - * @param context - * @param ePersonRest the updated eperson rest - * @param ePerson the eperson content object - * @throws SQLException - * @throws AuthorizeException - */ - private void updatePatchedValues(Context context, EPersonRest ePersonRest, EPerson ePerson) - throws SQLException, AuthorizeException { + @Override + protected void updateDSpaceObject(EPerson ePerson, EPersonRest ePersonRest) + throws AuthorizeException, SQLException { + super.updateDSpaceObject(ePerson, ePersonRest); + Context context = obtainContext(); if (ePersonRest.getPassword() != null) { es.setPassword(ePerson, ePersonRest.getPassword()); } @@ -232,7 +212,6 @@ public class EPersonRestRepository extends DSpaceRestRepository { +public class GroupRestRepository extends DSpaceObjectRestRepository { @Autowired GroupService gs; @Autowired - GroupConverter converter; + GroupRestRepository(GroupService dsoService, + GroupConverter dsoConverter) { + super(dsoService, dsoConverter, new DSpaceObjectPatch() {}); + this.gs = dsoService; + } @Autowired MetadataConverter metadataConverter; @@ -73,7 +79,7 @@ public class GroupRestRepository extends DSpaceRestRepository { throw new RuntimeException(excSQL.getMessage(), excSQL); } - return converter.convert(group); + return dsoConverter.convert(group); } @Override @@ -88,7 +94,7 @@ public class GroupRestRepository extends DSpaceRestRepository { if (group == null) { return null; } - return converter.fromModel(group); + return dsoConverter.fromModel(group); } @PreAuthorize("hasAuthority('ADMIN')") @@ -102,10 +108,17 @@ public class GroupRestRepository extends DSpaceRestRepository { } catch (SQLException e) { throw new RuntimeException(e.getMessage(), e); } - Page page = new PageImpl(groups, pageable, total).map(converter); + Page page = new PageImpl(groups, pageable, total).map(dsoConverter); return page; } + @Override + @PreAuthorize("hasPermission(#id, 'GROUP', 'WRITE')") + protected void patch(Context context, HttpServletRequest request, String apiCategory, String model, UUID id, + Patch patch) throws AuthorizeException, SQLException { + patchDSpaceObject(apiCategory, model, id, patch); + } + @Override public Class getDomainClass() { return GroupRest.class; diff --git a/dspace-spring-rest/src/main/java/org/dspace/app/rest/repository/ItemRestRepository.java b/dspace-spring-rest/src/main/java/org/dspace/app/rest/repository/ItemRestRepository.java index 44ab659fe1..514c200333 100644 --- a/dspace-spring-rest/src/main/java/org/dspace/app/rest/repository/ItemRestRepository.java +++ b/dspace-spring-rest/src/main/java/org/dspace/app/rest/repository/ItemRestRepository.java @@ -23,12 +23,10 @@ import org.apache.commons.lang3.StringUtils; import org.apache.log4j.Logger; import org.dspace.app.rest.converter.ItemConverter; import org.dspace.app.rest.converter.MetadataConverter; -import org.dspace.app.rest.exception.PatchBadRequestException; import org.dspace.app.rest.exception.RepositoryMethodNotImplementedException; import org.dspace.app.rest.exception.UnprocessableEntityException; import org.dspace.app.rest.model.ItemRest; import org.dspace.app.rest.model.hateoas.ItemResource; -import org.dspace.app.rest.model.patch.Operation; import org.dspace.app.rest.model.patch.Patch; import org.dspace.app.rest.repository.patch.ItemPatch; import org.dspace.authorize.AuthorizeException; @@ -56,15 +54,11 @@ import org.springframework.stereotype.Component; */ @Component(ItemRest.CATEGORY + "." + ItemRest.NAME) -public class ItemRestRepository extends DSpaceRestRepository { +public class ItemRestRepository extends DSpaceObjectRestRepository { private static final Logger log = Logger.getLogger(ItemRestRepository.class); - @Autowired - ItemService is; - - @Autowired - ItemConverter converter; + private final ItemService is; @Autowired MetadataConverter metadataConverter; @@ -84,8 +78,11 @@ public class ItemRestRepository extends DSpaceRestRepository { @Autowired InstallItemService installItemService; - public ItemRestRepository() { - System.out.println("Repository initialized by Spring"); + public ItemRestRepository(ItemService dsoService, + ItemConverter dsoConverter, + ItemPatch dsoPatch) { + super(dsoService, dsoConverter, dsoPatch); + this.is = dsoService; } @Override @@ -100,7 +97,7 @@ public class ItemRestRepository extends DSpaceRestRepository { if (item == null) { return null; } - return converter.fromModel(item); + return dsoConverter.fromModel(item); } @Override @@ -119,55 +116,33 @@ public class ItemRestRepository extends DSpaceRestRepository { } catch (SQLException e) { throw new RuntimeException(e.getMessage(), e); } - Page page = new PageImpl(items, pageable, total).map(converter); + Page page = new PageImpl(items, pageable, total).map(dsoConverter); return page; } @Override - public void patch(Context context, HttpServletRequest request, String apiCategory, String model, UUID uuid, - Patch patch) - throws UnprocessableEntityException, PatchBadRequestException, SQLException, AuthorizeException, - ResourceNotFoundException { - - Item item = is.find(context, uuid); - - if (item == null) { - throw new ResourceNotFoundException(apiCategory + "." + model + " with id: " + uuid + " not found"); - } - - List operations = patch.getOperations(); - ItemRest itemRest = findOne(uuid); - - ItemRest patchedModel = (ItemRest) itemPatch.patch(itemRest, operations); - updatePatchedValues(context, patchedModel, item); + @PreAuthorize("hasPermission(#id, 'ITEM', 'WRITE')") + protected void patch(Context context, HttpServletRequest request, String apiCategory, String model, UUID id, + Patch patch) throws AuthorizeException, SQLException { + patchDSpaceObject(apiCategory, model, id, patch); } - /** - * Persists changes to the rest model. - * @param context - * @param itemRest the updated item rest resource - * @param item the item content object - * @throws SQLException - * @throws AuthorizeException - */ - private void updatePatchedValues(Context context, ItemRest itemRest, Item item) - throws SQLException, AuthorizeException { + @Override + protected void updateDSpaceObject(Item item, ItemRest itemRest) + throws AuthorizeException, SQLException { + super.updateDSpaceObject(item, itemRest); - try { - if (itemRest.getWithdrawn() != item.isWithdrawn()) { - if (itemRest.getWithdrawn()) { - is.withdraw(context, item); - } else { - is.reinstate(context, item); - } + Context context = obtainContext(); + if (itemRest.getWithdrawn() != item.isWithdrawn()) { + if (itemRest.getWithdrawn()) { + is.withdraw(context, item); + } else { + is.reinstate(context, item); } - if (itemRest.getDiscoverable() != item.isDiscoverable()) { - item.setDiscoverable(itemRest.getDiscoverable()); - is.update(context, item); - } - } catch (SQLException | AuthorizeException e) { - e.printStackTrace(); - throw e; + } + if (itemRest.getDiscoverable() != item.isDiscoverable()) { + item.setDiscoverable(itemRest.getDiscoverable()); + is.update(context, item); } } @@ -242,7 +217,7 @@ public class ItemRestRepository extends DSpaceRestRepository { Item itemToReturn = installItemService.installItem(context, workspaceItem); - return converter.fromModel(itemToReturn); + return dsoConverter.fromModel(itemToReturn); } @Override @@ -271,6 +246,6 @@ public class ItemRestRepository extends DSpaceRestRepository { + uuid + ", " + itemRest.getId()); } - return converter.fromModel(item); + return dsoConverter.fromModel(item); } } \ No newline at end of file diff --git a/dspace-spring-rest/src/main/java/org/dspace/app/rest/repository/SiteRestRepository.java b/dspace-spring-rest/src/main/java/org/dspace/app/rest/repository/SiteRestRepository.java index 5586090c2e..e374df7fca 100644 --- a/dspace-spring-rest/src/main/java/org/dspace/app/rest/repository/SiteRestRepository.java +++ b/dspace-spring-rest/src/main/java/org/dspace/app/rest/repository/SiteRestRepository.java @@ -11,10 +11,14 @@ import java.sql.SQLException; import java.util.ArrayList; import java.util.List; import java.util.UUID; +import javax.servlet.http.HttpServletRequest; import org.dspace.app.rest.converter.SiteConverter; import org.dspace.app.rest.model.SiteRest; import org.dspace.app.rest.model.hateoas.SiteResource; +import org.dspace.app.rest.model.patch.Patch; +import org.dspace.app.rest.repository.patch.DSpaceObjectPatch; +import org.dspace.authorize.AuthorizeException; import org.dspace.content.Site; import org.dspace.content.service.SiteService; import org.dspace.core.Context; @@ -22,6 +26,7 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.domain.Page; import org.springframework.data.domain.PageImpl; import org.springframework.data.domain.Pageable; +import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.stereotype.Component; /** @@ -31,16 +36,15 @@ import org.springframework.stereotype.Component; */ @Component(SiteRest.CATEGORY + "." + SiteRest.NAME) -public class SiteRestRepository extends DSpaceRestRepository { +public class SiteRestRepository extends DSpaceObjectRestRepository { + + private final SiteService sitesv; @Autowired - SiteService sitesv; - - @Autowired - SiteConverter converter; - - - public SiteRestRepository() { + public SiteRestRepository(SiteService dsoService, + SiteConverter dsoConverter) { + super(dsoService, dsoConverter, new DSpaceObjectPatch() {}); + this.sitesv = dsoService; } @Override @@ -54,7 +58,7 @@ public class SiteRestRepository extends DSpaceRestRepository { if (site == null) { return null; } - return converter.fromModel(site); + return dsoConverter.fromModel(site); } @Override @@ -66,10 +70,17 @@ public class SiteRestRepository extends DSpaceRestRepository { } catch (SQLException e) { throw new RuntimeException(e.getMessage(), e); } - Page page = new PageImpl(sites, pageable, total).map(converter); + Page page = new PageImpl(sites, pageable, total).map(dsoConverter); return page; } + @Override + @PreAuthorize("hasAuthority('ADMIN')") + protected void patch(Context context, HttpServletRequest request, String apiCategory, String model, UUID id, + Patch patch) throws AuthorizeException, SQLException { + patchDSpaceObject(apiCategory, model, id, patch); + } + @Override public Class getDomainClass() { return SiteRest.class; diff --git a/dspace-spring-rest/src/main/java/org/dspace/app/rest/repository/patch/AbstractResourcePatch.java b/dspace-spring-rest/src/main/java/org/dspace/app/rest/repository/patch/AbstractResourcePatch.java index 4033718941..547e33842d 100644 --- a/dspace-spring-rest/src/main/java/org/dspace/app/rest/repository/patch/AbstractResourcePatch.java +++ b/dspace-spring-rest/src/main/java/org/dspace/app/rest/repository/patch/AbstractResourcePatch.java @@ -30,7 +30,7 @@ public abstract class AbstractResourcePatch { * @throws UnprocessableEntityException * @throws PatchBadRequestException */ - public RestModel patch(R restModel, List operations) { + public R patch(R restModel, List operations) { // Note: the list of possible operations is taken from JsonPatchConverter class. Does not implement // test https://tools.ietf.org/html/rfc6902#section-4.6 diff --git a/dspace-spring-rest/src/main/java/org/dspace/app/rest/repository/patch/DSpaceObjectPatch.java b/dspace-spring-rest/src/main/java/org/dspace/app/rest/repository/patch/DSpaceObjectPatch.java new file mode 100644 index 0000000000..568b3abaf8 --- /dev/null +++ b/dspace-spring-rest/src/main/java/org/dspace/app/rest/repository/patch/DSpaceObjectPatch.java @@ -0,0 +1,66 @@ +/** + * 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.patch; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.node.ObjectNode; +import com.flipkart.zjsonpatch.JsonPatch; +import org.dspace.app.rest.converter.JsonPatchConverter; +import org.dspace.app.rest.model.DSpaceObjectRest; +import org.dspace.app.rest.model.MetadataRest; +import org.dspace.app.rest.model.patch.Operation; +import org.dspace.app.rest.model.patch.Patch; + +public abstract class DSpaceObjectPatch extends AbstractResourcePatch { + + private static final String METADATA_PATH = "/metadata"; + + private ObjectMapper objectMapper = new ObjectMapper(); + + private JsonPatchConverter jsonPatchConverter = new JsonPatchConverter(objectMapper); + + @Override + public R patch(R dsoRest, List operations) { + List metadataOperations = new ArrayList<>(); + List otherOperations = new ArrayList<>(); + + for (Operation operation : operations) { + String path = operation.getPath(); + if (path.equals(METADATA_PATH) || path.startsWith(METADATA_PATH + "/")) { + metadataOperations.add(operation); + } else { + otherOperations.add(operation); + } + } + + if (!metadataOperations.isEmpty()) { + dsoRest.setMetadata(applyMetadataPatch( + jsonPatchConverter.convert(new Patch(metadataOperations)), + dsoRest.getMetadata())); + } + + return super.patch(dsoRest, otherOperations); + } + + private MetadataRest applyMetadataPatch(JsonNode patch, MetadataRest metadataRest) { + try { + ObjectNode objectNode = objectMapper.createObjectNode(); + JsonNode metadataNode = objectMapper.valueToTree(metadataRest); + objectNode.replace("metadata", metadataNode); + JsonPatch.applyInPlace(patch, objectNode); + return objectMapper.treeToValue(objectNode.get("metadata"), MetadataRest.class); + } catch (IOException e) { + throw new IllegalArgumentException(e); + } + } +} diff --git a/dspace-spring-rest/src/main/java/org/dspace/app/rest/repository/patch/EPersonPatch.java b/dspace-spring-rest/src/main/java/org/dspace/app/rest/repository/patch/EPersonPatch.java index 2204c98cc8..bb9ce69090 100644 --- a/dspace-spring-rest/src/main/java/org/dspace/app/rest/repository/patch/EPersonPatch.java +++ b/dspace-spring-rest/src/main/java/org/dspace/app/rest/repository/patch/EPersonPatch.java @@ -20,7 +20,7 @@ import org.springframework.stereotype.Component; * Provides patch operations for eperson updates. */ @Component -public class EPersonPatch extends AbstractResourcePatch { +public class EPersonPatch extends DSpaceObjectPatch { @Autowired EPersonOperationFactory patchFactory; diff --git a/dspace-spring-rest/src/main/java/org/dspace/app/rest/repository/patch/ItemPatch.java b/dspace-spring-rest/src/main/java/org/dspace/app/rest/repository/patch/ItemPatch.java index d68edbf444..b34a62d1ea 100644 --- a/dspace-spring-rest/src/main/java/org/dspace/app/rest/repository/patch/ItemPatch.java +++ b/dspace-spring-rest/src/main/java/org/dspace/app/rest/repository/patch/ItemPatch.java @@ -20,7 +20,7 @@ import org.springframework.stereotype.Component; * Provides PATCH operations for item updates. */ @Component -public class ItemPatch extends AbstractResourcePatch { +public class ItemPatch extends DSpaceObjectPatch { @Autowired ItemOperationFactory patchFactory; From 185766d8d242a9b855005e33bcc1f32e0d0f2206 Mon Sep 17 00:00:00 2001 From: Chris Wilper Date: Sat, 29 Dec 2018 15:22:47 -0500 Subject: [PATCH 64/68] DS-3908 Add DSO metadata PATCH tests --- .../app/rest/BitstreamRestRepositoryIT.java | 22 +++ .../app/rest/CollectionRestRepositoryIT.java | 20 +++ .../app/rest/CommunityRestRepositoryIT.java | 19 +++ .../app/rest/EPersonRestRepositoryIT.java | 19 +++ .../app/rest/GroupRestRepositoryIT.java | 20 +++ .../dspace/app/rest/ItemRestRepositoryIT.java | 22 ++- .../dspace/app/rest/SiteRestRepositoryIT.java | 21 +++ .../app/rest/test/MetadataPatchSuite.java | 64 ++++++++ .../app/rest/test/metadata-patch-suite.json | 152 ++++++++++++++++++ 9 files changed, 358 insertions(+), 1 deletion(-) create mode 100644 dspace-spring-rest/src/test/java/org/dspace/app/rest/test/MetadataPatchSuite.java create mode 100644 dspace-spring-rest/src/test/resources/org/dspace/app/rest/test/metadata-patch-suite.json diff --git a/dspace-spring-rest/src/test/java/org/dspace/app/rest/BitstreamRestRepositoryIT.java b/dspace-spring-rest/src/test/java/org/dspace/app/rest/BitstreamRestRepositoryIT.java index 49cc5f2bdd..fad2be9f78 100644 --- a/dspace-spring-rest/src/test/java/org/dspace/app/rest/BitstreamRestRepositoryIT.java +++ b/dspace-spring-rest/src/test/java/org/dspace/app/rest/BitstreamRestRepositoryIT.java @@ -27,11 +27,13 @@ import org.dspace.app.rest.builder.ItemBuilder; import org.dspace.app.rest.matcher.BitstreamFormatMatcher; import org.dspace.app.rest.matcher.BitstreamMatcher; import org.dspace.app.rest.test.AbstractControllerIntegrationTest; +import org.dspace.app.rest.test.MetadataPatchSuite; import org.dspace.content.Bitstream; import org.dspace.content.Collection; import org.dspace.content.Community; import org.dspace.content.Item; import org.dspace.content.service.BitstreamService; +import org.dspace.eperson.EPerson; import org.hamcrest.Matchers; import org.junit.Ignore; import org.junit.Test; @@ -587,4 +589,24 @@ public class BitstreamRestRepositoryIT extends AbstractControllerIntegrationTest getClient(token).perform(delete("/api/core/bitstreams/" + col.getLogo().getID())) .andExpect(status().is(422)); } + + @Test + public void patchBitstreamMetadataAuthorized() throws Exception { + runPatchMetadataTests(admin, 200); + } + + @Test + public void patchBitstreamMetadataUnauthorized() throws Exception { + runPatchMetadataTests(eperson, 403); + } + + private void runPatchMetadataTests(EPerson asUser, int expectedStatus) throws Exception { + context.turnOffAuthorisationSystem(); + parentCommunity = CommunityBuilder.createCommunity(context).withName("Community").withLogo("logo_community") + .build(); + String token = getAuthToken(asUser.getEmail(), password); + + new MetadataPatchSuite().runWith(getClient(token), "/api/core/bitstreams/" + + parentCommunity.getLogo().getID(), expectedStatus); + } } diff --git a/dspace-spring-rest/src/test/java/org/dspace/app/rest/CollectionRestRepositoryIT.java b/dspace-spring-rest/src/test/java/org/dspace/app/rest/CollectionRestRepositoryIT.java index 7b160710d5..94d9e0d463 100644 --- a/dspace-spring-rest/src/test/java/org/dspace/app/rest/CollectionRestRepositoryIT.java +++ b/dspace-spring-rest/src/test/java/org/dspace/app/rest/CollectionRestRepositoryIT.java @@ -31,10 +31,12 @@ import org.dspace.app.rest.model.CollectionRest; import org.dspace.app.rest.model.MetadataRest; import org.dspace.app.rest.model.MetadataValueRest; import org.dspace.app.rest.test.AbstractControllerIntegrationTest; +import org.dspace.app.rest.test.MetadataPatchSuite; import org.dspace.authorize.service.AuthorizeService; import org.dspace.content.Collection; import org.dspace.content.Community; import org.dspace.core.Constants; +import org.dspace.eperson.EPerson; import org.hamcrest.Matchers; import org.junit.Test; import org.springframework.beans.factory.annotation.Autowired; @@ -624,6 +626,24 @@ public class CollectionRestRepositoryIT extends AbstractControllerIntegrationTes ; authorizeService.removePoliciesActionFilter(context, eperson, Constants.WRITE); + } + public void patchCollectionMetadataAuthorized() throws Exception { + runPatchMetadataTests(admin, 200); + } + + @Test + public void patchCollectionMetadataUnauthorized() throws Exception { + runPatchMetadataTests(eperson, 403); + } + + private void runPatchMetadataTests(EPerson asUser, int expectedStatus) throws Exception { + context.turnOffAuthorisationSystem(); + parentCommunity = CommunityBuilder.createCommunity(context).withName("Community").build(); + Collection col = CollectionBuilder.createCollection(context, parentCommunity).withName("Collection").build(); + String token = getAuthToken(asUser.getEmail(), password); + + new MetadataPatchSuite().runWith(getClient(token), "/api/core/collections/" + + col.getID(), expectedStatus); } } diff --git a/dspace-spring-rest/src/test/java/org/dspace/app/rest/CommunityRestRepositoryIT.java b/dspace-spring-rest/src/test/java/org/dspace/app/rest/CommunityRestRepositoryIT.java index b5c7178d52..17c3e49145 100644 --- a/dspace-spring-rest/src/test/java/org/dspace/app/rest/CommunityRestRepositoryIT.java +++ b/dspace-spring-rest/src/test/java/org/dspace/app/rest/CommunityRestRepositoryIT.java @@ -33,10 +33,12 @@ import org.dspace.app.rest.model.CommunityRest; import org.dspace.app.rest.model.MetadataRest; import org.dspace.app.rest.model.MetadataValueRest; import org.dspace.app.rest.test.AbstractControllerIntegrationTest; +import org.dspace.app.rest.test.MetadataPatchSuite; import org.dspace.authorize.service.AuthorizeService; import org.dspace.content.Collection; import org.dspace.content.Community; import org.dspace.core.Constants; +import org.dspace.eperson.EPerson; import org.hamcrest.Matchers; import org.junit.Test; import org.springframework.beans.factory.annotation.Autowired; @@ -852,4 +854,21 @@ public class CommunityRestRepositoryIT extends AbstractControllerIntegrationTest } + public void patchCommunityMetadataAuthorized() throws Exception { + runPatchMetadataTests(admin, 200); + } + + @Test + public void patchCommunityMetadataUnauthorized() throws Exception { + runPatchMetadataTests(eperson, 403); + } + + private void runPatchMetadataTests(EPerson asUser, int expectedStatus) throws Exception { + context.turnOffAuthorisationSystem(); + parentCommunity = CommunityBuilder.createCommunity(context).withName("Community").build(); + String token = getAuthToken(asUser.getEmail(), password); + + new MetadataPatchSuite().runWith(getClient(token), "/api/core/communities/" + + parentCommunity.getID(), expectedStatus); + } } diff --git a/dspace-spring-rest/src/test/java/org/dspace/app/rest/EPersonRestRepositoryIT.java b/dspace-spring-rest/src/test/java/org/dspace/app/rest/EPersonRestRepositoryIT.java index 82dc0e6a3b..3f48a031cc 100644 --- a/dspace-spring-rest/src/test/java/org/dspace/app/rest/EPersonRestRepositoryIT.java +++ b/dspace-spring-rest/src/test/java/org/dspace/app/rest/EPersonRestRepositoryIT.java @@ -38,6 +38,7 @@ import org.dspace.app.rest.model.MetadataValueRest; import org.dspace.app.rest.model.patch.Operation; import org.dspace.app.rest.model.patch.ReplaceOperation; import org.dspace.app.rest.test.AbstractControllerIntegrationTest; +import org.dspace.app.rest.test.MetadataPatchSuite; import org.dspace.content.Collection; import org.dspace.content.Item; import org.dspace.eperson.EPerson; @@ -1032,4 +1033,22 @@ public class EPersonRestRepositoryIT extends AbstractControllerIntegrationTest { } + @Test + public void patchEPersonMetadataAuthorized() throws Exception { + runPatchMetadataTests(admin, 200); + } + + @Test + public void patchEPersonMetadataUnauthorized() throws Exception { + runPatchMetadataTests(eperson, 403); + } + + private void runPatchMetadataTests(EPerson asUser, int expectedStatus) throws Exception { + context.turnOffAuthorisationSystem(); + EPerson ePerson = EPersonBuilder.createEPerson(context).withEmail("user@test.com").build(); + String token = getAuthToken(asUser.getEmail(), password); + + new MetadataPatchSuite().runWith(getClient(token), "/api/eperson/epersons/" + ePerson.getID(), expectedStatus); + } + } diff --git a/dspace-spring-rest/src/test/java/org/dspace/app/rest/GroupRestRepositoryIT.java b/dspace-spring-rest/src/test/java/org/dspace/app/rest/GroupRestRepositoryIT.java index b75c9cf7bb..c057c11c72 100644 --- a/dspace-spring-rest/src/test/java/org/dspace/app/rest/GroupRestRepositoryIT.java +++ b/dspace-spring-rest/src/test/java/org/dspace/app/rest/GroupRestRepositoryIT.java @@ -22,6 +22,8 @@ import org.dspace.app.rest.builder.GroupBuilder; import org.dspace.app.rest.matcher.GroupMatcher; import org.dspace.app.rest.model.GroupRest; import org.dspace.app.rest.test.AbstractControllerIntegrationTest; +import org.dspace.app.rest.test.MetadataPatchSuite; +import org.dspace.eperson.EPerson; import org.dspace.eperson.Group; import org.hamcrest.Matchers; import org.junit.Test; @@ -234,4 +236,22 @@ public class GroupRestRepositoryIT extends AbstractControllerIntegrationTest { ; } + @Test + public void patchGroupMetadataAuthorized() throws Exception { + runPatchMetadataTests(admin, 200); + } + + @Test + public void patchGroupMetadataUnauthorized() throws Exception { + runPatchMetadataTests(eperson, 403); + } + + private void runPatchMetadataTests(EPerson asUser, int expectedStatus) throws Exception { + context.turnOffAuthorisationSystem(); + Group group = GroupBuilder.createGroup(context).withName("Group").build(); + String token = getAuthToken(asUser.getEmail(), password); + + new MetadataPatchSuite().runWith(getClient(token), "/api/eperson/groups/" + + group.getID(), expectedStatus); + } } diff --git a/dspace-spring-rest/src/test/java/org/dspace/app/rest/ItemRestRepositoryIT.java b/dspace-spring-rest/src/test/java/org/dspace/app/rest/ItemRestRepositoryIT.java index 872e09a655..0743d3cd79 100644 --- a/dspace-spring-rest/src/test/java/org/dspace/app/rest/ItemRestRepositoryIT.java +++ b/dspace-spring-rest/src/test/java/org/dspace/app/rest/ItemRestRepositoryIT.java @@ -43,6 +43,7 @@ import org.dspace.app.rest.model.MetadataValueRest; import org.dspace.app.rest.model.patch.Operation; import org.dspace.app.rest.model.patch.ReplaceOperation; import org.dspace.app.rest.test.AbstractControllerIntegrationTest; +import org.dspace.app.rest.test.MetadataPatchSuite; import org.dspace.content.Bitstream; import org.dspace.content.Collection; import org.dspace.content.Community; @@ -919,7 +920,6 @@ public class ItemRestRepositoryIT extends AbstractControllerIntegrationTest { } - @Test public void useStringForBooleanTest() throws Exception { context.turnOffAuthorisationSystem(); @@ -1744,7 +1744,27 @@ public class ItemRestRepositoryIT extends AbstractControllerIntegrationTest { //Delete public item getClient(token).perform(delete("/api/core/items/" + parentCommunity.getID())) .andExpect(status().is(404)); + } + public void patchItemMetadataAuthorized() throws Exception { + runPatchMetadataTests(admin, 200); + } + + @Test + public void patchItemMetadataUnauthorized() throws Exception { + runPatchMetadataTests(eperson, 403); + } + + private void runPatchMetadataTests(EPerson asUser, int expectedStatus) throws Exception { + context.turnOffAuthorisationSystem(); + parentCommunity = CommunityBuilder.createCommunity(context).withName("Parent Community").build(); + Community child1 = CommunityBuilder.createSubCommunity(context, parentCommunity) + .withName("Sub Community").build(); + Collection col1 = CollectionBuilder.createCollection(context, child1).withName("Collection 1").build(); + Item item = ItemBuilder.createItem(context, col1).build(); + String token = getAuthToken(asUser.getEmail(), password); + + new MetadataPatchSuite().runWith(getClient(token), "/api/core/items/" + item.getID(), expectedStatus); } } \ No newline at end of file diff --git a/dspace-spring-rest/src/test/java/org/dspace/app/rest/SiteRestRepositoryIT.java b/dspace-spring-rest/src/test/java/org/dspace/app/rest/SiteRestRepositoryIT.java index 33ada54c97..6d9f9980b3 100644 --- a/dspace-spring-rest/src/test/java/org/dspace/app/rest/SiteRestRepositoryIT.java +++ b/dspace-spring-rest/src/test/java/org/dspace/app/rest/SiteRestRepositoryIT.java @@ -17,7 +17,9 @@ import java.util.UUID; import org.dspace.app.rest.builder.SiteBuilder; import org.dspace.app.rest.matcher.SiteMatcher; import org.dspace.app.rest.test.AbstractControllerIntegrationTest; +import org.dspace.app.rest.test.MetadataPatchSuite; import org.dspace.content.Site; +import org.dspace.eperson.EPerson; import org.hamcrest.Matchers; import org.junit.Test; @@ -67,4 +69,23 @@ public class SiteRestRepositoryIT extends AbstractControllerIntegrationTest { .andExpect(status().isNotFound()); } + + @Test + public void patchSiteMetadataAuthorized() throws Exception { + runPatchMetadataTests(admin, 200); + } + + @Test + public void patchSiteMetadataUnauthorized() throws Exception { + runPatchMetadataTests(eperson, 403); + } + + private void runPatchMetadataTests(EPerson asUser, int expectedStatus) throws Exception { + context.turnOffAuthorisationSystem(); + Site site = SiteBuilder.createSite(context).build(); + String token = getAuthToken(asUser.getEmail(), password); + + new MetadataPatchSuite().runWith(getClient(token), "/api/core/sites/" + site.getID(), expectedStatus); + + } } diff --git a/dspace-spring-rest/src/test/java/org/dspace/app/rest/test/MetadataPatchSuite.java b/dspace-spring-rest/src/test/java/org/dspace/app/rest/test/MetadataPatchSuite.java new file mode 100644 index 0000000000..db1a9193c6 --- /dev/null +++ b/dspace-spring-rest/src/test/java/org/dspace/app/rest/test/MetadataPatchSuite.java @@ -0,0 +1,64 @@ +/** + * 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.test; + +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.patch; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +import javax.ws.rs.core.MediaType; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import org.junit.Assert; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.ResultActions; +import org.springframework.test.web.servlet.request.MockHttpServletRequestBuilder; + +public class MetadataPatchSuite { + private final ObjectMapper objectMapper = new ObjectMapper(); + private final JsonNode suite; + + public MetadataPatchSuite() throws Exception { + suite = objectMapper.readTree(getClass().getResourceAsStream("metadata-patch-suite.json")); + } + + public void runWith(MockMvc client, String url, int expectedStatus) { + for (JsonNode testNode: suite.get("tests")) { + String requestBody = testNode.get("patch").toString(); + String expectedMetadata = testNode.get("expect").toString(); + try { + System.out.println("Running patch test: " + testNode.get("name") + "\nRequest: " + requestBody); + checkResponse("PATCH", client, patch(url).content(requestBody), expectedMetadata, expectedStatus); + if (expectedStatus >= 200 && expectedStatus < 300) { + checkResponse("GET", client, get(url), expectedMetadata, expectedStatus); + } + } catch (Throwable t) { + Assert.fail("Metadata patch test '" + testNode.get("name") + "' failed.\n" + "Request body: " + + requestBody + "\n" + "Error: " + (t instanceof AssertionError ? "" : t.getClass().getName()) + + t.getMessage()); + } + } + } + + private void checkResponse(String verb, MockMvc client, MockHttpServletRequestBuilder requestBuilder, + String expectedMetadata, int expectedStatus) throws Exception { + ResultActions resultActions = client.perform(requestBuilder + .contentType(MediaType.APPLICATION_JSON_PATCH_JSON)) + .andExpect(status().is(expectedStatus)); + if (expectedStatus >= 200 && expectedStatus < 300) { + String responseBody = resultActions.andReturn().getResponse().getContentAsString(); + JsonNode responseJson = objectMapper.readTree(responseBody); + String responseMetadata = responseJson.get("metadata").toString(); + if (!responseMetadata.equals(expectedMetadata)) { + Assert.fail("Expected metadata in " + verb + " response: " + expectedMetadata + + "\nGot metadata in " + verb + " response: " + responseMetadata); + } + } + } +} diff --git a/dspace-spring-rest/src/test/resources/org/dspace/app/rest/test/metadata-patch-suite.json b/dspace-spring-rest/src/test/resources/org/dspace/app/rest/test/metadata-patch-suite.json new file mode 100644 index 0000000000..cff452fe91 --- /dev/null +++ b/dspace-spring-rest/src/test/resources/org/dspace/app/rest/test/metadata-patch-suite.json @@ -0,0 +1,152 @@ +{ + "tests": [ + { + "name": "clear metadata", + "patch": [ + { "op": "replace", + "path": "/metadata", + "value": {} + } + ], + "expect": {} + }, + { + "name": "add first title", + "patch": [ + { + "op": "add", + "path": "/metadata/dc.title", + "value": [ + { "value": "title 1" } + ] + } + ], + "expect": { + "dc.title": [ + { "value": "title 1", "language": null, "authority": null, "confidence": -1 } + ] + } + }, + { + "name": "add second title", + "patch": [ + { + "op": "add", + "path": "/metadata/dc.title/-", + "value": { "value": "最後のタイトル", "language": "ja_JP" } + } + ], + "expect": { + "dc.title": [ + { "value": "title 1", "language": null, "authority": null, "confidence": -1 }, + { "value": "最後のタイトル", "language": "ja_JP", "authority": null, "confidence": -1 } + ] + } + }, + { + "name": "insert zeroth title", + "patch": [ + { + "op": "add", + "path": "/metadata/dc.title/0", + "value": { + "value": "title 0" + } + } + ], + "expect": { + "dc.title": [ + { "value": "title 0", "language": null, "authority": null, "confidence": -1 }, + { "value": "title 1", "language": null, "authority": null, "confidence": -1 }, + { "value": "最後のタイトル", "language": "ja_JP", "authority": null, "confidence": -1 } + ] + } + }, + { + "name": "move last title up one", + "patch": [ + { + "op": "move", + "from": "/metadata/dc.title/2", + "path": "/metadata/dc.title/1" + } + ], + "expect": { + "dc.title": [ + { "value": "title 0", "language": null, "authority": null, "confidence": -1 }, + { "value": "最後のタイトル", "language": "ja_JP", "authority": null, "confidence": -1 }, + { "value": "title 1", "language": null, "authority": null, "confidence": -1 } + ] + } + }, + { + "name": "replace title 2 value and language in two operations", + "patch": [ + { + "op": "replace", + "path": "/metadata/dc.title/1/value", + "value": "title A" + }, + { + "op": "replace", + "path": "/metadata/dc.title/1/language", + "value": "en_US" + } + ], + "expect": { + "dc.title": [ + { "value": "title 0", "language": null, "authority": null, "confidence": -1 }, + { "value": "title A", "language": "en_US", "authority": null, "confidence": -1 }, + { "value": "title 1", "language": null, "authority": null, "confidence": -1 } + ] + } + }, + { + "name": "copy title A to end of list", + "patch": [ + { + "op": "copy", + "from": "/metadata/dc.title/1", + "path": "/metadata/dc.title/-" + } + ], + "expect": { + "dc.title": [ + { "value": "title 0", "language": null, "authority": null, "confidence": -1 }, + { "value": "title A", "language": "en_US", "authority": null, "confidence": -1 }, + { "value": "title 1", "language": null, "authority": null, "confidence": -1 }, + { "value": "title A", "language": "en_US", "authority": null, "confidence": -1 } + ] + } + }, + { + "name": "remove both title A copies", + "patch": [ + { + "op": "remove", + "path": "/metadata/dc.title/1" + }, + { + "op": "remove", + "path": "/metadata/dc.title/2" + } + ], + "expect": { + "dc.title": [ + { "value": "title 0", "language": null, "authority": null, "confidence": -1 }, + { "value": "title 1", "language": null, "authority": null, "confidence": -1 } + ] + } + }, + { + "name": "remove all titles", + "patch": [ + { + "op": "remove", + "path": "/metadata/dc.title" + } + ], + "expect": {} + } + ] +} From 96d544d0755b6b93b051ba5ba3e17f704b9ec793 Mon Sep 17 00:00:00 2001 From: Chris Wilper Date: Thu, 7 Mar 2019 09:54:24 -0500 Subject: [PATCH 65/68] DS-3908 Improve javadocs --- .../DSpaceObjectRestRepository.java | 21 +++++++++++++-- .../repository/patch/DSpaceObjectPatch.java | 15 +++++++++++ .../app/rest/test/MetadataPatchSuite.java | 26 +++++++++++++++++++ 3 files changed, 60 insertions(+), 2 deletions(-) diff --git a/dspace-spring-rest/src/main/java/org/dspace/app/rest/repository/DSpaceObjectRestRepository.java b/dspace-spring-rest/src/main/java/org/dspace/app/rest/repository/DSpaceObjectRestRepository.java index faba78c578..fabebc70d2 100644 --- a/dspace-spring-rest/src/main/java/org/dspace/app/rest/repository/DSpaceObjectRestRepository.java +++ b/dspace-spring-rest/src/main/java/org/dspace/app/rest/repository/DSpaceObjectRestRepository.java @@ -23,6 +23,12 @@ import org.dspace.content.service.DSpaceObjectService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.rest.webmvc.ResourceNotFoundException; +/** + * Base class for DSpaceObject-based Rest Repositories, providing common functionality. + * + * @param the specific type of DSpaceObject. + * @param the corresponding DSpaceObjectRest. + */ public abstract class DSpaceObjectRestRepository extends DSpaceRestRepository { @@ -41,9 +47,20 @@ public abstract class DSpaceObjectRestRepository the type of DSpaceObjectRest object the class is applicable to. + */ public abstract class DSpaceObjectPatch extends AbstractResourcePatch { private static final String METADATA_PATH = "/metadata"; @@ -29,6 +34,16 @@ public abstract class DSpaceObjectPatch extends Abst private JsonPatchConverter jsonPatchConverter = new JsonPatchConverter(objectMapper); + /** + * Applies the given patch operations to the given DSpaceObjectRest instance. + * + * This extends the default implementation by first applying metadata-based patch operations, + * then applying any others. + * + * @param dsoRest the instance to apply the changes to. + * @param operations the list of patch operations. + * @return the modified DSpaceObectRest instance. + */ @Override public R patch(R dsoRest, List operations) { List metadataOperations = new ArrayList<>(); diff --git a/dspace-spring-rest/src/test/java/org/dspace/app/rest/test/MetadataPatchSuite.java b/dspace-spring-rest/src/test/java/org/dspace/app/rest/test/MetadataPatchSuite.java index db1a9193c6..423a4cbe35 100644 --- a/dspace-spring-rest/src/test/java/org/dspace/app/rest/test/MetadataPatchSuite.java +++ b/dspace-spring-rest/src/test/java/org/dspace/app/rest/test/MetadataPatchSuite.java @@ -20,14 +20,29 @@ import org.springframework.test.web.servlet.MockMvc; import org.springframework.test.web.servlet.ResultActions; import org.springframework.test.web.servlet.request.MockHttpServletRequestBuilder; +/** + * Utility class for performing metadata patch tests sourced from a common json file (see constructor). + */ public class MetadataPatchSuite { private final ObjectMapper objectMapper = new ObjectMapper(); private final JsonNode suite; + /** + * Initializes the suite by parsing the json file of tests. + * + * @throws Exception if there is an error reading the file. + */ public MetadataPatchSuite() throws Exception { suite = objectMapper.readTree(getClass().getResourceAsStream("metadata-patch-suite.json")); } + /** + * Runs all tests in the file using the given client and url, expecting the given status. + * + * @param client the client to use. + * @param url the url to issue the patch against. + * @param expectedStatus the expected http status code. If this does not match the actual code, the test fails. + */ public void runWith(MockMvc client, String url, int expectedStatus) { for (JsonNode testNode: suite.get("tests")) { String requestBody = testNode.get("patch").toString(); @@ -46,6 +61,17 @@ public class MetadataPatchSuite { } } + /** + * Issues a PATCH or GET request and checks that the body and response code match what is expected. + * + * @param verb the http verb (PATCH or GET). + * @param client the client to use. + * @param requestBuilder the request builder that has been pre-seeded with the request url and request body. + * @param expectedMetadata the expected metadata as a minimal (no extra spaces) json string. Note: This will + * only be checked if the expectedStatus is in the 200 range. + * @param expectedStatus the expected http response status. + * @throws Exception if any checked error occurs, signifying test failure. + */ private void checkResponse(String verb, MockMvc client, MockHttpServletRequestBuilder requestBuilder, String expectedMetadata, int expectedStatus) throws Exception { ResultActions resultActions = client.perform(requestBuilder From 21fe803b5308751c5b570d18648886b72de1073f Mon Sep 17 00:00:00 2001 From: Chris Wilper Date: Thu, 7 Mar 2019 09:59:19 -0500 Subject: [PATCH 66/68] DS-3908 Restore auth system state where appropriate --- .../org/dspace/app/rest/BitstreamRestRepositoryIT.java | 8 ++++++-- .../org/dspace/app/rest/CollectionRestRepositoryIT.java | 8 ++++++-- .../org/dspace/app/rest/CommunityRestRepositoryIT.java | 8 ++++++-- .../java/org/dspace/app/rest/EPersonRestRepositoryIT.java | 6 +++++- .../java/org/dspace/app/rest/GroupRestRepositoryIT.java | 8 ++++++-- .../java/org/dspace/app/rest/ItemRestRepositoryIT.java | 6 +++++- .../java/org/dspace/app/rest/SiteRestRepositoryIT.java | 6 +++++- 7 files changed, 39 insertions(+), 11 deletions(-) diff --git a/dspace-spring-rest/src/test/java/org/dspace/app/rest/BitstreamRestRepositoryIT.java b/dspace-spring-rest/src/test/java/org/dspace/app/rest/BitstreamRestRepositoryIT.java index fad2be9f78..e3ab0dfca4 100644 --- a/dspace-spring-rest/src/test/java/org/dspace/app/rest/BitstreamRestRepositoryIT.java +++ b/dspace-spring-rest/src/test/java/org/dspace/app/rest/BitstreamRestRepositoryIT.java @@ -606,7 +606,11 @@ public class BitstreamRestRepositoryIT extends AbstractControllerIntegrationTest .build(); String token = getAuthToken(asUser.getEmail(), password); - new MetadataPatchSuite().runWith(getClient(token), "/api/core/bitstreams/" - + parentCommunity.getLogo().getID(), expectedStatus); + try { + new MetadataPatchSuite().runWith(getClient(token), "/api/core/bitstreams/" + + parentCommunity.getLogo().getID(), expectedStatus); + } finally { + context.restoreAuthSystemState(); + } } } diff --git a/dspace-spring-rest/src/test/java/org/dspace/app/rest/CollectionRestRepositoryIT.java b/dspace-spring-rest/src/test/java/org/dspace/app/rest/CollectionRestRepositoryIT.java index 94d9e0d463..f5a8419ebc 100644 --- a/dspace-spring-rest/src/test/java/org/dspace/app/rest/CollectionRestRepositoryIT.java +++ b/dspace-spring-rest/src/test/java/org/dspace/app/rest/CollectionRestRepositoryIT.java @@ -643,7 +643,11 @@ public class CollectionRestRepositoryIT extends AbstractControllerIntegrationTes Collection col = CollectionBuilder.createCollection(context, parentCommunity).withName("Collection").build(); String token = getAuthToken(asUser.getEmail(), password); - new MetadataPatchSuite().runWith(getClient(token), "/api/core/collections/" - + col.getID(), expectedStatus); + try { + new MetadataPatchSuite().runWith(getClient(token), "/api/core/collections/" + + col.getID(), expectedStatus); + } finally { + context.restoreAuthSystemState(); + } } } diff --git a/dspace-spring-rest/src/test/java/org/dspace/app/rest/CommunityRestRepositoryIT.java b/dspace-spring-rest/src/test/java/org/dspace/app/rest/CommunityRestRepositoryIT.java index 17c3e49145..408552689f 100644 --- a/dspace-spring-rest/src/test/java/org/dspace/app/rest/CommunityRestRepositoryIT.java +++ b/dspace-spring-rest/src/test/java/org/dspace/app/rest/CommunityRestRepositoryIT.java @@ -868,7 +868,11 @@ public class CommunityRestRepositoryIT extends AbstractControllerIntegrationTest parentCommunity = CommunityBuilder.createCommunity(context).withName("Community").build(); String token = getAuthToken(asUser.getEmail(), password); - new MetadataPatchSuite().runWith(getClient(token), "/api/core/communities/" - + parentCommunity.getID(), expectedStatus); + try { + new MetadataPatchSuite().runWith(getClient(token), "/api/core/communities/" + + parentCommunity.getID(), expectedStatus); + } finally { + context.restoreAuthSystemState(); + } } } diff --git a/dspace-spring-rest/src/test/java/org/dspace/app/rest/EPersonRestRepositoryIT.java b/dspace-spring-rest/src/test/java/org/dspace/app/rest/EPersonRestRepositoryIT.java index 3f48a031cc..8c8e203e15 100644 --- a/dspace-spring-rest/src/test/java/org/dspace/app/rest/EPersonRestRepositoryIT.java +++ b/dspace-spring-rest/src/test/java/org/dspace/app/rest/EPersonRestRepositoryIT.java @@ -1048,7 +1048,11 @@ public class EPersonRestRepositoryIT extends AbstractControllerIntegrationTest { EPerson ePerson = EPersonBuilder.createEPerson(context).withEmail("user@test.com").build(); String token = getAuthToken(asUser.getEmail(), password); - new MetadataPatchSuite().runWith(getClient(token), "/api/eperson/epersons/" + ePerson.getID(), expectedStatus); + try { + new MetadataPatchSuite().runWith(getClient(token), "/api/eperson/epersons/" + ePerson.getID(), expectedStatus); + } finally { + context.restoreAuthSystemState(); + } } } diff --git a/dspace-spring-rest/src/test/java/org/dspace/app/rest/GroupRestRepositoryIT.java b/dspace-spring-rest/src/test/java/org/dspace/app/rest/GroupRestRepositoryIT.java index c057c11c72..de7d84aaca 100644 --- a/dspace-spring-rest/src/test/java/org/dspace/app/rest/GroupRestRepositoryIT.java +++ b/dspace-spring-rest/src/test/java/org/dspace/app/rest/GroupRestRepositoryIT.java @@ -251,7 +251,11 @@ public class GroupRestRepositoryIT extends AbstractControllerIntegrationTest { Group group = GroupBuilder.createGroup(context).withName("Group").build(); String token = getAuthToken(asUser.getEmail(), password); - new MetadataPatchSuite().runWith(getClient(token), "/api/eperson/groups/" - + group.getID(), expectedStatus); + try { + new MetadataPatchSuite().runWith(getClient(token), "/api/eperson/groups/" + + group.getID(), expectedStatus); + } finally { + context.restoreAuthSystemState(); + } } } diff --git a/dspace-spring-rest/src/test/java/org/dspace/app/rest/ItemRestRepositoryIT.java b/dspace-spring-rest/src/test/java/org/dspace/app/rest/ItemRestRepositoryIT.java index 0743d3cd79..597158c9fc 100644 --- a/dspace-spring-rest/src/test/java/org/dspace/app/rest/ItemRestRepositoryIT.java +++ b/dspace-spring-rest/src/test/java/org/dspace/app/rest/ItemRestRepositoryIT.java @@ -1764,7 +1764,11 @@ public class ItemRestRepositoryIT extends AbstractControllerIntegrationTest { Item item = ItemBuilder.createItem(context, col1).build(); String token = getAuthToken(asUser.getEmail(), password); - new MetadataPatchSuite().runWith(getClient(token), "/api/core/items/" + item.getID(), expectedStatus); + try { + new MetadataPatchSuite().runWith(getClient(token), "/api/core/items/" + item.getID(), expectedStatus); + } finally { + context.restoreAuthSystemState(); + } } } \ No newline at end of file diff --git a/dspace-spring-rest/src/test/java/org/dspace/app/rest/SiteRestRepositoryIT.java b/dspace-spring-rest/src/test/java/org/dspace/app/rest/SiteRestRepositoryIT.java index 6d9f9980b3..bfe89c6904 100644 --- a/dspace-spring-rest/src/test/java/org/dspace/app/rest/SiteRestRepositoryIT.java +++ b/dspace-spring-rest/src/test/java/org/dspace/app/rest/SiteRestRepositoryIT.java @@ -85,7 +85,11 @@ public class SiteRestRepositoryIT extends AbstractControllerIntegrationTest { Site site = SiteBuilder.createSite(context).build(); String token = getAuthToken(asUser.getEmail(), password); - new MetadataPatchSuite().runWith(getClient(token), "/api/core/sites/" + site.getID(), expectedStatus); + try { + new MetadataPatchSuite().runWith(getClient(token), "/api/core/sites/" + site.getID(), expectedStatus); + } finally { + context.restoreAuthSystemState(); + } } } From 2171b7ff83ecc3885cfcee12f7f5ca8b01a9695c Mon Sep 17 00:00:00 2001 From: Chris Wilper Date: Thu, 7 Mar 2019 10:35:17 -0500 Subject: [PATCH 67/68] DS-3908 Address minor checkstyle issues --- .../dspace/app/rest/repository/DSpaceObjectRestRepository.java | 1 - .../test/java/org/dspace/app/rest/EPersonRestRepositoryIT.java | 3 ++- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/dspace-spring-rest/src/main/java/org/dspace/app/rest/repository/DSpaceObjectRestRepository.java b/dspace-spring-rest/src/main/java/org/dspace/app/rest/repository/DSpaceObjectRestRepository.java index fabebc70d2..dc03cd54e3 100644 --- a/dspace-spring-rest/src/main/java/org/dspace/app/rest/repository/DSpaceObjectRestRepository.java +++ b/dspace-spring-rest/src/main/java/org/dspace/app/rest/repository/DSpaceObjectRestRepository.java @@ -12,7 +12,6 @@ import java.util.UUID; import org.dspace.app.rest.converter.DSpaceObjectConverter; import org.dspace.app.rest.converter.MetadataConverter; -import org.dspace.app.rest.exception.PatchBadRequestException; import org.dspace.app.rest.exception.UnprocessableEntityException; import org.dspace.app.rest.model.DSpaceObjectRest; import org.dspace.app.rest.model.patch.Patch; diff --git a/dspace-spring-rest/src/test/java/org/dspace/app/rest/EPersonRestRepositoryIT.java b/dspace-spring-rest/src/test/java/org/dspace/app/rest/EPersonRestRepositoryIT.java index 8c8e203e15..ba794f4e07 100644 --- a/dspace-spring-rest/src/test/java/org/dspace/app/rest/EPersonRestRepositoryIT.java +++ b/dspace-spring-rest/src/test/java/org/dspace/app/rest/EPersonRestRepositoryIT.java @@ -1049,7 +1049,8 @@ public class EPersonRestRepositoryIT extends AbstractControllerIntegrationTest { String token = getAuthToken(asUser.getEmail(), password); try { - new MetadataPatchSuite().runWith(getClient(token), "/api/eperson/epersons/" + ePerson.getID(), expectedStatus); + new MetadataPatchSuite().runWith(getClient(token), "/api/eperson/epersons/" + ePerson.getID(), + expectedStatus); } finally { context.restoreAuthSystemState(); } From 337daef7383e902e77259026ffe3722f56263c96 Mon Sep 17 00:00:00 2001 From: Chris Wilper Date: Thu, 7 Mar 2019 12:32:23 -0500 Subject: [PATCH 68/68] DS-3908 Restore authz state as early as possible in tests --- .../org/dspace/app/rest/BitstreamRestRepositoryIT.java | 9 +++------ .../org/dspace/app/rest/CollectionRestRepositoryIT.java | 8 ++------ .../org/dspace/app/rest/CommunityRestRepositoryIT.java | 9 +++------ .../org/dspace/app/rest/EPersonRestRepositoryIT.java | 8 ++------ .../java/org/dspace/app/rest/GroupRestRepositoryIT.java | 8 ++------ .../java/org/dspace/app/rest/ItemRestRepositoryIT.java | 7 ++----- .../java/org/dspace/app/rest/SiteRestRepositoryIT.java | 8 ++------ 7 files changed, 16 insertions(+), 41 deletions(-) diff --git a/dspace-spring-rest/src/test/java/org/dspace/app/rest/BitstreamRestRepositoryIT.java b/dspace-spring-rest/src/test/java/org/dspace/app/rest/BitstreamRestRepositoryIT.java index e3ab0dfca4..c581b08ebc 100644 --- a/dspace-spring-rest/src/test/java/org/dspace/app/rest/BitstreamRestRepositoryIT.java +++ b/dspace-spring-rest/src/test/java/org/dspace/app/rest/BitstreamRestRepositoryIT.java @@ -604,13 +604,10 @@ public class BitstreamRestRepositoryIT extends AbstractControllerIntegrationTest context.turnOffAuthorisationSystem(); parentCommunity = CommunityBuilder.createCommunity(context).withName("Community").withLogo("logo_community") .build(); + context.restoreAuthSystemState(); String token = getAuthToken(asUser.getEmail(), password); - try { - new MetadataPatchSuite().runWith(getClient(token), "/api/core/bitstreams/" - + parentCommunity.getLogo().getID(), expectedStatus); - } finally { - context.restoreAuthSystemState(); - } + new MetadataPatchSuite().runWith(getClient(token), "/api/core/bitstreams/" + + parentCommunity.getLogo().getID(), expectedStatus); } } diff --git a/dspace-spring-rest/src/test/java/org/dspace/app/rest/CollectionRestRepositoryIT.java b/dspace-spring-rest/src/test/java/org/dspace/app/rest/CollectionRestRepositoryIT.java index f5a8419ebc..0a85afaab8 100644 --- a/dspace-spring-rest/src/test/java/org/dspace/app/rest/CollectionRestRepositoryIT.java +++ b/dspace-spring-rest/src/test/java/org/dspace/app/rest/CollectionRestRepositoryIT.java @@ -641,13 +641,9 @@ public class CollectionRestRepositoryIT extends AbstractControllerIntegrationTes context.turnOffAuthorisationSystem(); parentCommunity = CommunityBuilder.createCommunity(context).withName("Community").build(); Collection col = CollectionBuilder.createCollection(context, parentCommunity).withName("Collection").build(); + context.restoreAuthSystemState(); String token = getAuthToken(asUser.getEmail(), password); - try { - new MetadataPatchSuite().runWith(getClient(token), "/api/core/collections/" - + col.getID(), expectedStatus); - } finally { - context.restoreAuthSystemState(); - } + new MetadataPatchSuite().runWith(getClient(token), "/api/core/collections/" + col.getID(), expectedStatus); } } diff --git a/dspace-spring-rest/src/test/java/org/dspace/app/rest/CommunityRestRepositoryIT.java b/dspace-spring-rest/src/test/java/org/dspace/app/rest/CommunityRestRepositoryIT.java index 408552689f..3b3c12c720 100644 --- a/dspace-spring-rest/src/test/java/org/dspace/app/rest/CommunityRestRepositoryIT.java +++ b/dspace-spring-rest/src/test/java/org/dspace/app/rest/CommunityRestRepositoryIT.java @@ -866,13 +866,10 @@ public class CommunityRestRepositoryIT extends AbstractControllerIntegrationTest private void runPatchMetadataTests(EPerson asUser, int expectedStatus) throws Exception { context.turnOffAuthorisationSystem(); parentCommunity = CommunityBuilder.createCommunity(context).withName("Community").build(); + context.restoreAuthSystemState(); String token = getAuthToken(asUser.getEmail(), password); - try { - new MetadataPatchSuite().runWith(getClient(token), "/api/core/communities/" - + parentCommunity.getID(), expectedStatus); - } finally { - context.restoreAuthSystemState(); - } + new MetadataPatchSuite().runWith(getClient(token), "/api/core/communities/" + + parentCommunity.getID(), expectedStatus); } } diff --git a/dspace-spring-rest/src/test/java/org/dspace/app/rest/EPersonRestRepositoryIT.java b/dspace-spring-rest/src/test/java/org/dspace/app/rest/EPersonRestRepositoryIT.java index ba794f4e07..741729a7cf 100644 --- a/dspace-spring-rest/src/test/java/org/dspace/app/rest/EPersonRestRepositoryIT.java +++ b/dspace-spring-rest/src/test/java/org/dspace/app/rest/EPersonRestRepositoryIT.java @@ -1046,14 +1046,10 @@ public class EPersonRestRepositoryIT extends AbstractControllerIntegrationTest { private void runPatchMetadataTests(EPerson asUser, int expectedStatus) throws Exception { context.turnOffAuthorisationSystem(); EPerson ePerson = EPersonBuilder.createEPerson(context).withEmail("user@test.com").build(); + context.restoreAuthSystemState(); String token = getAuthToken(asUser.getEmail(), password); - try { - new MetadataPatchSuite().runWith(getClient(token), "/api/eperson/epersons/" + ePerson.getID(), - expectedStatus); - } finally { - context.restoreAuthSystemState(); - } + new MetadataPatchSuite().runWith(getClient(token), "/api/eperson/epersons/" + ePerson.getID(), expectedStatus); } } diff --git a/dspace-spring-rest/src/test/java/org/dspace/app/rest/GroupRestRepositoryIT.java b/dspace-spring-rest/src/test/java/org/dspace/app/rest/GroupRestRepositoryIT.java index de7d84aaca..259961c12d 100644 --- a/dspace-spring-rest/src/test/java/org/dspace/app/rest/GroupRestRepositoryIT.java +++ b/dspace-spring-rest/src/test/java/org/dspace/app/rest/GroupRestRepositoryIT.java @@ -249,13 +249,9 @@ public class GroupRestRepositoryIT extends AbstractControllerIntegrationTest { private void runPatchMetadataTests(EPerson asUser, int expectedStatus) throws Exception { context.turnOffAuthorisationSystem(); Group group = GroupBuilder.createGroup(context).withName("Group").build(); + context.restoreAuthSystemState(); String token = getAuthToken(asUser.getEmail(), password); - try { - new MetadataPatchSuite().runWith(getClient(token), "/api/eperson/groups/" - + group.getID(), expectedStatus); - } finally { - context.restoreAuthSystemState(); - } + new MetadataPatchSuite().runWith(getClient(token), "/api/eperson/groups/" + group.getID(), expectedStatus); } } diff --git a/dspace-spring-rest/src/test/java/org/dspace/app/rest/ItemRestRepositoryIT.java b/dspace-spring-rest/src/test/java/org/dspace/app/rest/ItemRestRepositoryIT.java index 597158c9fc..09e4cd1f0f 100644 --- a/dspace-spring-rest/src/test/java/org/dspace/app/rest/ItemRestRepositoryIT.java +++ b/dspace-spring-rest/src/test/java/org/dspace/app/rest/ItemRestRepositoryIT.java @@ -1762,13 +1762,10 @@ public class ItemRestRepositoryIT extends AbstractControllerIntegrationTest { .withName("Sub Community").build(); Collection col1 = CollectionBuilder.createCollection(context, child1).withName("Collection 1").build(); Item item = ItemBuilder.createItem(context, col1).build(); + context.restoreAuthSystemState(); String token = getAuthToken(asUser.getEmail(), password); - try { - new MetadataPatchSuite().runWith(getClient(token), "/api/core/items/" + item.getID(), expectedStatus); - } finally { - context.restoreAuthSystemState(); - } + new MetadataPatchSuite().runWith(getClient(token), "/api/core/items/" + item.getID(), expectedStatus); } } \ No newline at end of file diff --git a/dspace-spring-rest/src/test/java/org/dspace/app/rest/SiteRestRepositoryIT.java b/dspace-spring-rest/src/test/java/org/dspace/app/rest/SiteRestRepositoryIT.java index bfe89c6904..e3b1f23559 100644 --- a/dspace-spring-rest/src/test/java/org/dspace/app/rest/SiteRestRepositoryIT.java +++ b/dspace-spring-rest/src/test/java/org/dspace/app/rest/SiteRestRepositoryIT.java @@ -83,13 +83,9 @@ public class SiteRestRepositoryIT extends AbstractControllerIntegrationTest { private void runPatchMetadataTests(EPerson asUser, int expectedStatus) throws Exception { context.turnOffAuthorisationSystem(); Site site = SiteBuilder.createSite(context).build(); + context.restoreAuthSystemState(); String token = getAuthToken(asUser.getEmail(), password); - try { - new MetadataPatchSuite().runWith(getClient(token), "/api/core/sites/" + site.getID(), expectedStatus); - } finally { - context.restoreAuthSystemState(); - } - + new MetadataPatchSuite().runWith(getClient(token), "/api/core/sites/" + site.getID(), expectedStatus); } }