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