diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/ConverterService.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/ConverterService.java index 9e4f6c00f1..52b1d4ac30 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/ConverterService.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/ConverterService.java @@ -32,6 +32,7 @@ import org.springframework.core.type.filter.AssignableTypeFilter; import org.springframework.data.domain.Page; import org.springframework.data.domain.PageImpl; import org.springframework.data.domain.Pageable; +import org.springframework.hateoas.Link; import org.springframework.hateoas.Resource; import org.springframework.stereotype.Component; import org.springframework.stereotype.Service; @@ -155,9 +156,12 @@ public class ConverterService { * @throws ClassCastException if the resource type is not compatible with the inferred return type. */ public T toResource(RestModel restObject) { + return toResource(restObject, new Link[] {}); + } + public T toResource(RestModel restObject, Link... oldLinks) { T halResource = getResource(restObject); if (restObject instanceof RestAddressableModel) { - utils.embedOrLinkClassLevelRels(halResource); + utils.embedOrLinkClassLevelRels(halResource, oldLinks); halLinkService.addLinks(halResource); Projection projection = ((RestAddressableModel) restObject).getProjection(); return projection.transformResource(halResource); diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/projection/AbstractProjection.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/projection/AbstractProjection.java index 47b911eafa..0d61942ce6 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/projection/AbstractProjection.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/projection/AbstractProjection.java @@ -11,6 +11,7 @@ import org.dspace.app.rest.model.LinkRest; import org.dspace.app.rest.model.RestAddressableModel; import org.dspace.app.rest.model.RestModel; import org.dspace.app.rest.model.hateoas.HALResource; +import org.springframework.hateoas.Link; /** * Abstract base class for projections. @@ -35,7 +36,7 @@ public abstract class AbstractProjection implements Projection { } @Override - public boolean allowEmbedding(HALResource halResource, LinkRest linkRest) { + public boolean allowEmbedding(HALResource halResource, LinkRest linkRest, Link... oldLinks) { return false; } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/projection/CompositeProjection.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/projection/CompositeProjection.java index 290f064b3d..7581777419 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/projection/CompositeProjection.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/projection/CompositeProjection.java @@ -13,6 +13,7 @@ import org.dspace.app.rest.model.LinkRest; import org.dspace.app.rest.model.RestAddressableModel; import org.dspace.app.rest.model.RestModel; import org.dspace.app.rest.model.hateoas.HALResource; +import org.springframework.hateoas.Link; /** * A projection that combines the behavior of multiple projections. @@ -61,9 +62,9 @@ public class CompositeProjection implements Projection { } @Override - public boolean allowEmbedding(HALResource halResource, LinkRest linkRest) { + public boolean allowEmbedding(HALResource halResource, LinkRest linkRest, Link... oldLinks) { for (Projection projection : projections) { - if (projection.allowEmbedding(halResource, linkRest)) { + if (projection.allowEmbedding(halResource, linkRest, oldLinks)) { return true; } } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/projection/EmbedRelsProjection.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/projection/EmbedRelsProjection.java index dffc7f23a3..03c32fd13f 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/projection/EmbedRelsProjection.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/projection/EmbedRelsProjection.java @@ -12,6 +12,7 @@ import java.util.Set; import org.dspace.app.rest.model.LinkRest; import org.dspace.app.rest.model.RestAddressableModel; import org.dspace.app.rest.model.hateoas.HALResource; +import org.springframework.hateoas.Link; /** * Projection that allows a given set of rels to be embedded. @@ -32,9 +33,22 @@ public class EmbedRelsProjection extends AbstractProjection { } @Override - public boolean allowEmbedding(HALResource halResource, LinkRest linkRest) { - if (halResource.getContent().getEmbedLevel() == 0) - return embedRels.contains(linkRest.name()); + public boolean allowEmbedding(HALResource halResource, LinkRest linkRest, Link... oldLinks) { + if (halResource.getContent().getEmbedLevel() == 0 && embedRels.contains(linkRest.name())) + return true; + StringBuilder fullName = new StringBuilder(); + for (Link oldLink : oldLinks) { + fullName.append(oldLink.getRel()).append("/"); + } + fullName.append(linkRest.name()); + if (embedRels.contains(fullName.toString())) { + return true; + } + fullName.append("/"); + for (String embedRel : embedRels) { + if (embedRel.startsWith(fullName.toString())) + return true; + } return false; } } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/projection/FullProjection.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/projection/FullProjection.java index 9c5a4a9f9e..15f637e828 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/projection/FullProjection.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/projection/FullProjection.java @@ -9,6 +9,7 @@ package org.dspace.app.rest.projection; import org.dspace.app.rest.model.LinkRest; import org.dspace.app.rest.model.hateoas.HALResource; +import org.springframework.hateoas.Link; import org.springframework.stereotype.Component; /** @@ -24,7 +25,7 @@ public class FullProjection extends AbstractProjection { } @Override - public boolean allowEmbedding(HALResource halResource, LinkRest linkRest) { + public boolean allowEmbedding(HALResource halResource, LinkRest linkRest, Link... oldLinks) { return true; } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/projection/Projection.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/projection/Projection.java index 1e56423e08..1fa18cba81 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/projection/Projection.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/projection/Projection.java @@ -15,6 +15,7 @@ import org.dspace.app.rest.model.RestModel; import org.dspace.app.rest.model.hateoas.HALResource; import org.dspace.app.rest.repository.DSpaceRestRepository; import org.dspace.app.rest.utils.Utils; +import org.springframework.hateoas.Link; import org.springframework.stereotype.Component; import org.springframework.web.bind.annotation.RestController; @@ -118,7 +119,7 @@ public interface Projection { * @param linkRest the LinkRest annotation through which the related resource was discovered on the rest object. * @return true if allowed, false otherwise. */ - boolean allowEmbedding(HALResource halResource, LinkRest linkRest); + boolean allowEmbedding(HALResource halResource, LinkRest linkRest, Link... oldLinks); /** * Tells whether this projection permits the linking of a particular linkable subresource. diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/utils/Utils.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/utils/Utils.java index c2d2216f37..18e2a877f7 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/utils/Utils.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/utils/Utils.java @@ -516,13 +516,14 @@ public class Utils { * Adds embeds or links for all class-level LinkRel annotations for which embeds or links are allowed. * * @param halResource the resource. + * @param oldLinks previously traversed links */ - public void embedOrLinkClassLevelRels(HALResource halResource) { + public void embedOrLinkClassLevelRels(HALResource halResource, Link... oldLinks) { Projection projection = halResource.getContent().getProjection(); getLinkRests(halResource.getContent().getClass()).stream().forEach((linkRest) -> { Link link = linkToSubResource(halResource.getContent(), linkRest.name()); - if (projection.allowEmbedding(halResource, linkRest)) { - embedRelFromRepository(halResource, linkRest.name(), link, linkRest); + if (projection.allowEmbedding(halResource, linkRest, oldLinks)) { + embedRelFromRepository(halResource, linkRest.name(), link, linkRest, oldLinks); halResource.add(link); // unconditionally link if embedding was allowed } else if (projection.allowLinking(halResource, linkRest)) { halResource.add(link); @@ -561,6 +562,10 @@ public class Utils { */ void embedRelFromRepository(HALResource resource, String rel, Link link, LinkRest linkRest) { + embedRelFromRepository(resource, rel, link, linkRest, new Link[] {}); + } + void embedRelFromRepository(HALResource resource, + String rel, Link link, LinkRest linkRest, Link... oldLinks) { if (resource.getContent().getEmbedLevel() == EMBED_MAX_LEVELS) { return; } @@ -572,7 +577,7 @@ public class Utils { Object contentId = getContentIdForLinkMethod(resource.getContent(), method); try { Object linkedObject = method.invoke(linkRepository, null, contentId, null, projection); - resource.embedResource(rel, wrapForEmbedding(resource, linkedObject, link)); + resource.embedResource(rel, wrapForEmbedding(resource, linkedObject, link, oldLinks)); } catch (InvocationTargetException e) { if (e.getTargetException() instanceof RuntimeException) { throw (RuntimeException) e.getTargetException(); @@ -677,17 +682,23 @@ public class Utils { */ private Object wrapForEmbedding(HALResource resource, Object linkedObject, Link link) { + return wrapForEmbedding(resource, linkedObject, link, new Link[] {}); + } + private Object wrapForEmbedding(HALResource resource, + Object linkedObject, Link link, Link... oldLinks) { int childEmbedLevel = resource.getContent().getEmbedLevel() + 1; + Link[] newList = Arrays.copyOf(oldLinks, oldLinks.length+1); + newList[oldLinks.length] = link; if (linkedObject instanceof RestAddressableModel) { RestAddressableModel restObject = (RestAddressableModel) linkedObject; restObject.setEmbedLevel(childEmbedLevel); - return converter.toResource(restObject); + return converter.toResource(restObject, newList); } else if (linkedObject instanceof Page) { // The first page has already been constructed by a link repository and we only need to wrap it Page page = (Page) linkedObject; return new EmbeddedPage(link.getHref(), page.map((restObject) -> { restObject.setEmbedLevel(childEmbedLevel); - return converter.toResource(restObject); + return converter.toResource(restObject, newList); }), null, link.getRel()); } else if (linkedObject instanceof List) { // The full list has been retrieved and we need to provide the first page for embedding @@ -699,7 +710,7 @@ public class Utils { return new EmbeddedPage(link.getHref(), page.map((restObject) -> { restObject.setEmbedLevel(childEmbedLevel); - return converter.toResource(restObject); + return converter.toResource(restObject, newList); }), list, link.getRel()); } else { diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/projection/MockProjection.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/projection/MockProjection.java index 0c8c976a03..0ecae14cda 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/projection/MockProjection.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/projection/MockProjection.java @@ -87,7 +87,7 @@ public class MockProjection implements Projection { return halResource; } - public boolean allowEmbedding(HALResource halResource, LinkRest linkRest) { + public boolean allowEmbedding(HALResource halResource, LinkRest linkRest, Link... oldLinks) { return true; }