Skip to content

Commit

Permalink
Merge pull request #2830 from atmire/w2p-71701_Missing-parents-link-b…
Browse files Browse the repository at this point in the history
…itstream-bundle-item

Parent links for bitstream->bundle->item
  • Loading branch information
tdonohue committed Jul 23, 2020
2 parents 15d2e16 + 8368765 commit 9a9253e
Show file tree
Hide file tree
Showing 5 changed files with 261 additions and 1 deletion.
Expand Up @@ -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"
Expand All @@ -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";

Expand Down
@@ -0,0 +1,62 @@
/**
* 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;

/**
* Get the item where the provided bundle resides in
*/
@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);
}
}

}
Expand Up @@ -24,17 +24,20 @@
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;
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;
Expand Down Expand Up @@ -1143,5 +1146,138 @@ 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()
)
));
}

@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();

//** 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());
}

}
Expand Up @@ -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;
Expand All @@ -50,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;
Expand All @@ -63,6 +65,9 @@ public class BundleRestRepositoryIT extends AbstractControllerIntegrationTest {
@Autowired
ResourcePolicyService resourcePolicyService;

@Autowired
ItemService itemService;

private Collection collection;
private Item item;
private Bundle bundle1;
Expand Down Expand Up @@ -636,4 +641,54 @@ 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")
));
}

@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();

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")
));
}

}
Expand Up @@ -48,7 +48,8 @@ public static Matcher<? super Object> matchBundle(String name, UUID uuid, String
public static Matcher<? super Object> matchFullEmbeds() {
return matchEmbeds(
"bitstreams[]",
"primaryBitstream"
"primaryBitstream",
"item"
);
}

Expand All @@ -57,6 +58,7 @@ public static Matcher<? super Object> matchFullEmbeds() {
*/
public static Matcher<? super Object> matchLinks(UUID uuid) {
return HalMatcher.matchLinks(REST_SERVER_URL + "core/bundles/" + uuid,
"item",
"bitstreams",
"primaryBitstream",
"self"
Expand Down

0 comments on commit 9a9253e

Please sign in to comment.