From 62170ab767273462a61d2d90242d5e9d2ddd9e8c Mon Sep 17 00:00:00 2001 From: Oliver Gierke Date: Tue, 19 May 2015 08:28:06 +0200 Subject: [PATCH] DATACMNS-701 - PagedResourcesAssembler now adds first and last links. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit We now add first and last links to the PagedResources created in line with the appearance of the prev and next links. The PagedResourcesAssembler can be configured to enforce the rendering using setForceFirstAndLastRels(…). --- .../data/web/PagedResourcesAssembler.java | 48 +++++++++++++--- .../web/PagedResourcesAssemblerUnitTests.java | 55 ++++++++++++++++++- 2 files changed, 93 insertions(+), 10 deletions(-) diff --git a/src/main/java/org/springframework/data/web/PagedResourcesAssembler.java b/src/main/java/org/springframework/data/web/PagedResourcesAssembler.java index fb7ac652f..6c0442aaf 100644 --- a/src/main/java/org/springframework/data/web/PagedResourcesAssembler.java +++ b/src/main/java/org/springframework/data/web/PagedResourcesAssembler.java @@ -23,6 +23,7 @@ import java.util.List; import org.springframework.core.MethodParameter; import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageRequest; import org.springframework.data.domain.Pageable; import org.springframework.hateoas.Link; import org.springframework.hateoas.PagedResources; @@ -51,6 +52,8 @@ public class PagedResourcesAssembler implements ResourceAssembler, Pa private final UriComponents baseUri; private final EmbeddedWrappers wrappers = new EmbeddedWrappers(false); + private boolean forceFirstAndLastRels = false; + /** * Creates a new {@link PagedResourcesAssembler} using the given {@link PageableHandlerMethodArgumentResolver} and * base URI. If the former is {@literal null}, a default one will be created. If the latter is {@literal null}, calls @@ -65,6 +68,19 @@ public class PagedResourcesAssembler implements ResourceAssembler, Pa this.baseUri = baseUri; } + /** + * Configures whether to always add {@code first} and {@code last} links to the {@link PagedResources} created. + * Defaults to {@literal false} which means that {@code first} and {@code last} links only appear in conjunction with + * {@code prev} and {@code next} links. + * + * @param forceFirstAndLastRels whether to always add {@code first} and {@code last} links to the + * {@link PagedResources} created. + * @since 1.11 + */ + public void setForceFirstAndLastRels(boolean forceFirstAndLastRels) { + this.forceFirstAndLastRels = forceFirstAndLastRels; + } + /* * (non-Javadoc) * @see org.springframework.hateoas.ResourceAssembler#toResource(java.lang.Object) @@ -136,8 +152,7 @@ public class PagedResourcesAssembler implements ResourceAssembler, Pa EmbeddedWrapper wrapper = wrappers.emptyCollectionOf(type); List embedded = Collections.singletonList(wrapper); - return new PagedResources(embedded, metadata, - createLink(getUriTemplate(link), null, Link.REL_SELF)); + return addPaginationLinks(new PagedResources(embedded, metadata), page, link); } /** @@ -167,20 +182,35 @@ public class PagedResourcesAssembler implements ResourceAssembler, Pa resources.add(assembler.toResource(element)); } + return addPaginationLinks(new PagedResources(resources, asPageMetadata(page)), page, link); + } + + private PagedResources addPaginationLinks(PagedResources resources, Page page, Link link) { + UriTemplate base = getUriTemplate(link); - Link selfLink = createLink(base, null, Link.REL_SELF); - PagedResources pagedResources = new PagedResources(resources, asPageMetadata(page), selfLink); - - if (page.hasNext()) { - pagedResources.add(createLink(base, page.nextPageable(), Link.REL_NEXT)); + if (page.hasPrevious() || forceFirstAndLastRels) { + resources.add(createLink(base, new PageRequest(0, page.getSize(), page.getSort()), Link.REL_FIRST)); } if (page.hasPrevious()) { - pagedResources.add(createLink(base, page.previousPageable(), Link.REL_PREVIOUS)); + resources.add(createLink(base, page.previousPageable(), Link.REL_PREVIOUS)); } - return pagedResources; + resources.add(createLink(base, null, Link.REL_SELF)); + + if (page.hasNext()) { + resources.add(createLink(base, page.nextPageable(), Link.REL_NEXT)); + } + + if (page.hasNext() || forceFirstAndLastRels) { + + int lastIndex = page.getTotalPages() == 0 ? 0 : page.getTotalPages() - 1; + + resources.add(createLink(base, new PageRequest(lastIndex, page.getSize(), page.getSort()), Link.REL_LAST)); + } + + return resources; } /** diff --git a/src/test/java/org/springframework/data/web/PagedResourcesAssemblerUnitTests.java b/src/test/java/org/springframework/data/web/PagedResourcesAssemblerUnitTests.java index 4154633a2..68f7983b7 100644 --- a/src/test/java/org/springframework/data/web/PagedResourcesAssemblerUnitTests.java +++ b/src/test/java/org/springframework/data/web/PagedResourcesAssemblerUnitTests.java @@ -30,6 +30,7 @@ import org.springframework.data.domain.AbstractPageRequest; import org.springframework.data.domain.Page; import org.springframework.data.domain.PageImpl; import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Pageable; import org.springframework.hateoas.Link; import org.springframework.hateoas.PagedResources; import org.springframework.hateoas.Resource; @@ -47,7 +48,8 @@ import org.springframework.web.util.UriComponentsBuilder; */ public class PagedResourcesAssemblerUnitTests { - static final Page EMPTY_PAGE = new PageImpl(Collections.emptyList()); + static final Pageable PAGEABLE = new PageRequest(0, 20); + static final Page EMPTY_PAGE = new PageImpl(Collections. emptyList(), PAGEABLE, 0); HateoasPageableHandlerMethodArgumentResolver resolver = new HateoasPageableHandlerMethodArgumentResolver(); PagedResourcesAssembler assembler = new PagedResourcesAssembler(resolver, null); @@ -220,6 +222,57 @@ public class PagedResourcesAssemblerUnitTests { assembler.toEmptyResource(EMPTY_PAGE, null, null); } + /** + * @see DATACMNS-701 + */ + @Test + public void addsFirstAndLastLinksForMultiplePages() { + + PagedResources> resources = assembler.toResource(createPage(1)); + + assertThat(resources.getLink(Link.REL_FIRST).getHref(), endsWith("?page=0&size=1")); + assertThat(resources.getLink(Link.REL_LAST).getHref(), endsWith("?page=2&size=1")); + } + + /** + * @see DATACMNS-701 + */ + @Test + public void doesNotAddFirstLinkForFirstPageByDefault() { + + PagedResources> resources = assembler.toResource(createPage(0)); + + assertThat(resources.getLink(Link.REL_FIRST), is(nullValue())); + assertThat(resources.getLink(Link.REL_LAST).getHref(), endsWith("?page=2&size=1")); + } + + /** + * @see DATACMNS-701 + */ + @Test + public void doesNotAddLastLinkForLastPageByDefault() { + + PagedResources> resources = assembler.toResource(createPage(2)); + + assertThat(resources.getLink(Link.REL_FIRST).getHref(), endsWith("?page=0&size=1")); + assertThat(resources.getLink(Link.REL_LAST), is(nullValue())); + } + + /** + * @see DATACMNS-701 + */ + @Test + public void alwaysAddsFirstAndLastLinkIfConfiguredTo() { + + PagedResourcesAssembler assembler = new PagedResourcesAssembler(resolver, null); + assembler.setForceFirstAndLastRels(true); + + PagedResources> resources = assembler.toResource(EMPTY_PAGE); + + assertThat(resources.getLink(Link.REL_FIRST).getHref(), endsWith("?page=0&size=20")); + assertThat(resources.getLink(Link.REL_LAST).getHref(), endsWith("?page=0&size=20")); + } + private static Page createPage(int index) { AbstractPageRequest request = new PageRequest(index, 1);