From dfe33d273e2c1189b4eae54041e8c45914e07c17 Mon Sep 17 00:00:00 2001 From: Kristof De Langhe Date: Tue, 7 Jul 2020 17:43:36 +0200 Subject: [PATCH 1/4] 71701: item link on BundleRest + embedded parent tests for bundle and bitstream --- .../org/dspace/app/rest/model/BundleRest.java | 5 ++ .../repository/BundleItemLinkRepository.java | 59 +++++++++++++++++++ .../app/rest/BitstreamRestRepositoryIT.java | 53 +++++++++++++++++ .../app/rest/BundleRestRepositoryIT.java | 17 ++++++ .../app/rest/matcher/BundleMatcher.java | 4 +- 5 files changed, 137 insertions(+), 1 deletion(-) create mode 100644 dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/BundleItemLinkRepository.java diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/BundleRest.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/BundleRest.java index 71f05c8333e2..dd4a80d488a8 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/BundleRest.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/BundleRest.java @@ -16,6 +16,10 @@ * @author Jelle Pelgrims (jelle.pelgrims at atmire.com) */ @LinksRest(links = { + @LinkRest( + name = BundleRest.ITEM, + method = "getItem" + ), @LinkRest( name = BundleRest.BITSTREAMS, method = "getBitstreams" @@ -30,6 +34,7 @@ public class BundleRest extends DSpaceObjectRest { public static final String PLURAL_NAME = "bundles"; public static final String CATEGORY = RestAddressableModel.CORE; + public static final String ITEM = "item"; public static final String BITSTREAMS = "bitstreams"; public static final String PRIMARY_BITSTREAM = "primaryBitstream"; diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/BundleItemLinkRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/BundleItemLinkRepository.java new file mode 100644 index 000000000000..93098290854c --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/BundleItemLinkRepository.java @@ -0,0 +1,59 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the LICENSE and NOTICE files at the root of the source + * tree and available online at + * + * http://www.dspace.org/license/ + */ +package org.dspace.app.rest.repository; + +import java.sql.SQLException; +import java.util.UUID; +import javax.annotation.Nullable; +import javax.servlet.http.HttpServletRequest; + +import org.dspace.app.rest.model.BundleRest; +import org.dspace.app.rest.model.ItemRest; +import org.dspace.app.rest.projection.Projection; +import org.dspace.content.Bundle; +import org.dspace.content.Item; +import org.dspace.content.service.BundleService; +import org.dspace.core.Context; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.domain.Pageable; +import org.springframework.data.rest.webmvc.ResourceNotFoundException; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.stereotype.Component; + +/** + * Link repository for "item" subresource of an individual bundle. + */ +@Component(BundleRest.CATEGORY + "." + BundleRest.NAME + "." + BundleRest.ITEM) +public class BundleItemLinkRepository extends AbstractDSpaceRestRepository + implements LinkRestRepository { + + @Autowired + BundleService bundleService; + + @PreAuthorize("hasPermission(#bundleId, 'BUNDLE', 'READ')") + public ItemRest getItem(@Nullable HttpServletRequest request, + UUID bundleId, + @Nullable Pageable optionalPageable, + Projection projection) { + try { + Context context = obtainContext(); + Bundle bundle = bundleService.find(context, bundleId); + if (bundle == null) { + throw new ResourceNotFoundException("No such bundle: " + bundleId); + } + Item item = bundle.getItems().get(0); + if (item == null) { + return null; + } + return converter.toRest(item, projection); + } catch (SQLException e) { + throw new RuntimeException(e); + } + } + +} diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/BitstreamRestRepositoryIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/BitstreamRestRepositoryIT.java index 34bb56cbec60..e609f02d7e20 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/BitstreamRestRepositoryIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/BitstreamRestRepositoryIT.java @@ -30,11 +30,13 @@ import org.dspace.app.rest.builder.ResourcePolicyBuilder; import org.dspace.app.rest.matcher.BitstreamFormatMatcher; import org.dspace.app.rest.matcher.BitstreamMatcher; +import org.dspace.app.rest.matcher.BundleMatcher; import org.dspace.app.rest.matcher.HalMatcher; import org.dspace.app.rest.test.AbstractControllerIntegrationTest; import org.dspace.app.rest.test.MetadataPatchSuite; import org.dspace.authorize.service.ResourcePolicyService; import org.dspace.content.Bitstream; +import org.dspace.content.Bundle; import org.dspace.content.Collection; import org.dspace.content.Community; import org.dspace.content.Item; @@ -1143,5 +1145,56 @@ public void testHiddenMetadataForUserWithWriteRights() throws Exception { } + @Test + public void getEmbeddedBundleForBitstream() 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(); + + //2. One public items that is readable by Anonymous + Item publicItem1 = ItemBuilder.createItem(context, col1) + .withTitle("Test") + .withIssueDate("2010-10-17") + .withAuthor("Smith, Donald") + .withSubject("ExtraEntry") + .build(); + + String bitstreamContent = "ThisIsSomeDummyText"; + + //Add a bitstream to an item + Bitstream bitstream = null; + try (InputStream is = IOUtils.toInputStream(bitstreamContent, CharEncoding.UTF_8)) { + bitstream = BitstreamBuilder. + createBitstream(context, publicItem1, is) + .withName("Bitstream") + .withDescription("Description") + .withMimeType("text/plain") + .build(); + } + + Bundle bundle = bitstream.getBundles().get(0); + + //Get the bitstream with embedded bundle + getClient().perform(get("/api/core/bitstreams/" + bitstream.getID() + "?embed=bundle")) + .andExpect(status().isOk()) + .andExpect(content().contentType(contentType)) + .andExpect(jsonPath("$._embedded.bundle", + BundleMatcher.matchProperties( + bundle.getName(), + bundle.getID(), + bundle.getHandle(), + bundle.getType() + ) + )); + } } diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/BundleRestRepositoryIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/BundleRestRepositoryIT.java index 07d6645c00b1..bdf3447124b9 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/BundleRestRepositoryIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/BundleRestRepositoryIT.java @@ -37,6 +37,7 @@ import org.dspace.app.rest.matcher.BitstreamMatcher; import org.dspace.app.rest.matcher.BundleMatcher; import org.dspace.app.rest.matcher.HalMatcher; +import org.dspace.app.rest.matcher.ItemMatcher; import org.dspace.app.rest.matcher.MetadataMatcher; import org.dspace.app.rest.model.BundleRest; import org.dspace.app.rest.model.MetadataRest; @@ -636,4 +637,20 @@ public void deleteBundle_NoAuthToken() throws Exception { .andExpect(status().isOk()); } + @Test + public void getEmbeddedItemForBundle() throws Exception { + context.turnOffAuthorisationSystem(); + + bundle1 = BundleBuilder.createBundle(context, item) + .withName("testname") + .build(); + + context.restoreAuthSystemState(); + + getClient().perform(get("/api/core/bundles/" + bundle1.getID() + "?embed=item")) + .andExpect(status().isOk()) + .andExpect(content().contentType(contentType)) + .andExpect(jsonPath("$._embedded.item", ItemMatcher.matchItemWithTitleAndDateIssued(item, "Public item 1", "2017-10-17"))); + } + } diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/matcher/BundleMatcher.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/matcher/BundleMatcher.java index 4fb5606293b4..812daabf5832 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/matcher/BundleMatcher.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/matcher/BundleMatcher.java @@ -48,7 +48,8 @@ public static Matcher matchBundle(String name, UUID uuid, String public static Matcher matchFullEmbeds() { return matchEmbeds( "bitstreams[]", - "primaryBitstream" + "primaryBitstream", + "item" ); } @@ -57,6 +58,7 @@ public static Matcher matchFullEmbeds() { */ public static Matcher matchLinks(UUID uuid) { return HalMatcher.matchLinks(REST_SERVER_URL + "core/bundles/" + uuid, + "item", "bitstreams", "primaryBitstream", "self" From 3243f19127f61f793598fe8e6dab19c522b0f5ec Mon Sep 17 00:00:00 2001 From: Kristof De Langhe Date: Wed, 8 Jul 2020 11:42:36 +0200 Subject: [PATCH 2/4] 71701: Additional parent link tests for Bitstream and Bundle + JavaDocs --- .../repository/BundleItemLinkRepository.java | 3 + .../app/rest/BitstreamRestRepositoryIT.java | 78 +++++++++++++++++++ .../app/rest/BundleRestRepositoryIT.java | 29 +++++++ 3 files changed, 110 insertions(+) diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/BundleItemLinkRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/BundleItemLinkRepository.java index 93098290854c..3d3e5226f37b 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/BundleItemLinkRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/BundleItemLinkRepository.java @@ -35,6 +35,9 @@ public class BundleItemLinkRepository extends AbstractDSpaceRestRepository @Autowired BundleService bundleService; + /** + * Get the first item the provided bundle resides in + */ @PreAuthorize("hasPermission(#bundleId, 'BUNDLE', 'READ')") public ItemRest getItem(@Nullable HttpServletRequest request, UUID bundleId, diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/BitstreamRestRepositoryIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/BitstreamRestRepositoryIT.java index e609f02d7e20..9cbb5363b60d 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/BitstreamRestRepositoryIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/BitstreamRestRepositoryIT.java @@ -24,6 +24,7 @@ import org.apache.commons.codec.CharEncoding; import org.apache.commons.io.IOUtils; import org.dspace.app.rest.builder.BitstreamBuilder; +import org.dspace.app.rest.builder.BundleBuilder; import org.dspace.app.rest.builder.CollectionBuilder; import org.dspace.app.rest.builder.CommunityBuilder; import org.dspace.app.rest.builder.ItemBuilder; @@ -1197,4 +1198,81 @@ public void getEmbeddedBundleForBitstream() throws Exception { )); } + @Test + public void linksToFirstBundleWhenMultipleBundles() 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(); + + //2. One public items that is readable by Anonymous + Item publicItem1 = ItemBuilder.createItem(context, col1) + .withTitle("Test") + .withIssueDate("2010-10-17") + .withAuthor("Smith, Donald") + .withSubject("ExtraEntry") + .build(); + + String bitstreamContent = "ThisIsSomeDummyText"; + + //Add a bitstream to an item + Bitstream bitstream = null; + try (InputStream is = IOUtils.toInputStream(bitstreamContent, CharEncoding.UTF_8)) { + bitstream = BitstreamBuilder. + createBitstream(context, publicItem1, is) + .withName("Bitstream") + .withDescription("Description") + .withMimeType("text/plain") + .build(); + } + + Bundle secondBundle = BundleBuilder.createBundle(context, publicItem1) + .withName("second bundle") + .withBitstream(bitstream).build(); + + Bundle bundle = bitstream.getBundles().get(0); + + //Get bundle should contain the first bundle in the list + getClient().perform(get("/api/core/bitstreams/" + bitstream.getID() + "/bundle")) + .andExpect(status().isOk()) + .andExpect(content().contentType(contentType)) + .andExpect(jsonPath("$", + BundleMatcher.matchProperties( + bundle.getName(), + bundle.getID(), + bundle.getHandle(), + bundle.getType() + ) + )); + } + + @Test + public void linksToEmptyWhenNoBundle() throws Exception { + // We turn off the authorization system in order to create the structure as defined below + context.turnOffAuthorisationSystem(); + + // ** GIVEN ** + // 1. A community with a logo + parentCommunity = CommunityBuilder.createCommunity(context).withName("Community").withLogo("logo_community") + .build(); + + // 2. A collection with a logo + Collection col = CollectionBuilder.createCollection(context, parentCommunity).withName("Collection") + .withLogo("logo_collection").build(); + + Bitstream bitstream = parentCommunity.getLogo(); + + //Get bundle should contain an empty response + getClient().perform(get("/api/core/bitstreams/" + bitstream.getID() + "/bundle")) + .andExpect(status().isNoContent()); + } + } diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/BundleRestRepositoryIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/BundleRestRepositoryIT.java index bdf3447124b9..7e3dcead5aea 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/BundleRestRepositoryIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/BundleRestRepositoryIT.java @@ -51,6 +51,7 @@ import org.dspace.content.Bundle; import org.dspace.content.Collection; import org.dspace.content.Item; +import org.dspace.content.service.ItemService; import org.dspace.core.Constants; import org.dspace.eperson.EPerson; import org.hamcrest.Matchers; @@ -64,6 +65,9 @@ public class BundleRestRepositoryIT extends AbstractControllerIntegrationTest { @Autowired ResourcePolicyService resourcePolicyService; + @Autowired + ItemService itemService; + private Collection collection; private Item item; private Bundle bundle1; @@ -653,4 +657,29 @@ public void getEmbeddedItemForBundle() throws Exception { .andExpect(jsonPath("$._embedded.item", ItemMatcher.matchItemWithTitleAndDateIssued(item, "Public item 1", "2017-10-17"))); } + @Test + public void linksToFirstItemWhenMultipleItems() throws Exception { + context.turnOffAuthorisationSystem(); + + bundle1 = BundleBuilder.createBundle(context, item) + .withName("testname") + .build(); + + Item item2 = ItemBuilder.createItem(context, collection) + .withTitle("Public item 2") + .withIssueDate("2020-07-08") + .withAuthor("Smith, Donald").withAuthor("Doe, John") + .withSubject("SecondEntry") + .build(); + + itemService.addBundle(context, item2, bundle1); + + context.restoreAuthSystemState(); + + getClient().perform(get("/api/core/bundles/" + bundle1.getID() + "/item")) + .andExpect(status().isOk()) + .andExpect(content().contentType(contentType)) + .andExpect(jsonPath("$", ItemMatcher.matchItemWithTitleAndDateIssued(item, "Public item 1", "2017-10-17"))); + } + } From 3e33a866c6af5cd885770aba53aae92fdc323f6c Mon Sep 17 00:00:00 2001 From: Kristof De Langhe Date: Wed, 8 Jul 2020 12:47:35 +0200 Subject: [PATCH 3/4] 71701: Checkstyle fixes --- .../java/org/dspace/app/rest/BundleRestRepositoryIT.java | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/BundleRestRepositoryIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/BundleRestRepositoryIT.java index 7e3dcead5aea..20590a19922b 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/BundleRestRepositoryIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/BundleRestRepositoryIT.java @@ -654,7 +654,9 @@ public void getEmbeddedItemForBundle() throws Exception { getClient().perform(get("/api/core/bundles/" + bundle1.getID() + "?embed=item")) .andExpect(status().isOk()) .andExpect(content().contentType(contentType)) - .andExpect(jsonPath("$._embedded.item", ItemMatcher.matchItemWithTitleAndDateIssued(item, "Public item 1", "2017-10-17"))); + .andExpect(jsonPath("$._embedded.item", + ItemMatcher.matchItemWithTitleAndDateIssued(item, "Public item 1", "2017-10-17") + )); } @Test @@ -679,7 +681,9 @@ public void linksToFirstItemWhenMultipleItems() throws Exception { getClient().perform(get("/api/core/bundles/" + bundle1.getID() + "/item")) .andExpect(status().isOk()) .andExpect(content().contentType(contentType)) - .andExpect(jsonPath("$", ItemMatcher.matchItemWithTitleAndDateIssued(item, "Public item 1", "2017-10-17"))); + .andExpect(jsonPath("$", + ItemMatcher.matchItemWithTitleAndDateIssued(item, "Public item 1", "2017-10-17") + )); } } From 836876508f42f44583b8aadd22d4859473b3b9d9 Mon Sep 17 00:00:00 2001 From: jonas-atmire Date: Thu, 23 Jul 2020 08:59:10 +0200 Subject: [PATCH 4/4] Addition of explanation to tests + Small comment update --- .../dspace/app/rest/repository/BundleItemLinkRepository.java | 2 +- .../java/org/dspace/app/rest/BitstreamRestRepositoryIT.java | 5 +++++ .../java/org/dspace/app/rest/BundleRestRepositoryIT.java | 5 +++++ 3 files changed, 11 insertions(+), 1 deletion(-) diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/BundleItemLinkRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/BundleItemLinkRepository.java index 3d3e5226f37b..4df81d5054ef 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/BundleItemLinkRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/BundleItemLinkRepository.java @@ -36,7 +36,7 @@ public class BundleItemLinkRepository extends AbstractDSpaceRestRepository BundleService bundleService; /** - * Get the first item the provided bundle resides in + * Get the item where the provided bundle resides in */ @PreAuthorize("hasPermission(#bundleId, 'BUNDLE', 'READ')") public ItemRest getItem(@Nullable HttpServletRequest request, diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/BitstreamRestRepositoryIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/BitstreamRestRepositoryIT.java index 9cbb5363b60d..691bb7d07dc3 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/BitstreamRestRepositoryIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/BitstreamRestRepositoryIT.java @@ -1199,6 +1199,11 @@ public void getEmbeddedBundleForBitstream() throws Exception { } @Test + /** + * This test proves that, if a bitstream is linked to multiple bundles, we only ever return the first bundle. + * **NOTE: DSpace does NOT support or expect to have a bitstream linked to multiple bundles**. + * But, because the database does allow for it, this test simply proves the REST API will respond without an error. + */ public void linksToFirstBundleWhenMultipleBundles() throws Exception { //We turn off the authorization system in order to create the structure as defined below context.turnOffAuthorisationSystem(); diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/BundleRestRepositoryIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/BundleRestRepositoryIT.java index 20590a19922b..4aa25946ff60 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/BundleRestRepositoryIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/BundleRestRepositoryIT.java @@ -660,6 +660,11 @@ public void getEmbeddedItemForBundle() throws Exception { } @Test + /** + * This test proves that, if a bundle is linked to multiple items, we only ever return the first item. + * **NOTE: DSpace does NOT support or expect to have a bundle linked to multiple items**. + * But, because the database does allow for it, this test simply proves the REST API will respond without an error + */ public void linksToFirstItemWhenMultipleItems() throws Exception { context.turnOffAuthorisationSystem();