From 33fee76a329a09c276454c199bb24a9e07876b86 Mon Sep 17 00:00:00 2001 From: eugenp Date: Sun, 5 Jan 2014 14:17:38 +0200 Subject: [PATCH] pagination work --- .../web/controller/FooController.java | 3 +- ...sultsRetrievedDiscoverabilityListener.java | 107 ++++++++++++++++++ .../PaginatedResultsRetrievedEvent.java | 67 +++++++++++ .../java/org/baeldung/web/util/LinkUtil.java | 6 + 4 files changed, 182 insertions(+), 1 deletion(-) create mode 100644 spring-security-rest-full/src/main/java/org/baeldung/web/hateoas/PaginatedResultsRetrievedDiscoverabilityListener.java create mode 100644 spring-security-rest-full/src/main/java/org/baeldung/web/hateoas/PaginatedResultsRetrievedEvent.java diff --git a/spring-security-rest-full/src/main/java/org/baeldung/web/controller/FooController.java b/spring-security-rest-full/src/main/java/org/baeldung/web/controller/FooController.java index 62306ab808..167f2549d0 100644 --- a/spring-security-rest-full/src/main/java/org/baeldung/web/controller/FooController.java +++ b/spring-security-rest-full/src/main/java/org/baeldung/web/controller/FooController.java @@ -9,6 +9,7 @@ import javax.servlet.http.HttpServletResponse; import org.baeldung.persistence.model.Foo; import org.baeldung.persistence.service.IFooService; import org.baeldung.web.exception.MyResourceNotFoundException; +import org.baeldung.web.hateoas.PaginatedResultsRetrievedEvent; import org.baeldung.web.util.LinkUtil; import org.baeldung.web.util.ResourceCreated; import org.baeldung.web.util.RestPreconditions; @@ -72,7 +73,7 @@ public class FooController { if (page > resultPage.getTotalPages()) { throw new MyResourceNotFoundException(); } - // eventPublisher.publishEvent(new PaginatedResultsRetrievedEvent(Foo.class, uriBuilder, response, page, resultPage.getTotalPages(), size)); + eventPublisher.publishEvent(new PaginatedResultsRetrievedEvent(Foo.class, uriBuilder, response, page, resultPage.getTotalPages(), size)); return resultPage.getContent(); } diff --git a/spring-security-rest-full/src/main/java/org/baeldung/web/hateoas/PaginatedResultsRetrievedDiscoverabilityListener.java b/spring-security-rest-full/src/main/java/org/baeldung/web/hateoas/PaginatedResultsRetrievedDiscoverabilityListener.java new file mode 100644 index 0000000000..5d24a38ccb --- /dev/null +++ b/spring-security-rest-full/src/main/java/org/baeldung/web/hateoas/PaginatedResultsRetrievedDiscoverabilityListener.java @@ -0,0 +1,107 @@ +package org.baeldung.web.hateoas; + +import javax.servlet.http.HttpServletResponse; + +import org.baeldung.web.util.LinkUtil; +import org.springframework.context.ApplicationListener; +import org.springframework.stereotype.Component; +import org.springframework.web.util.UriComponentsBuilder; + +import com.google.common.base.Preconditions; +import com.google.common.net.HttpHeaders; + +@SuppressWarnings({ "rawtypes" }) +@Component +class PaginatedResultsRetrievedDiscoverabilityListener implements ApplicationListener { + + private static final String PAGE = "page"; + + public PaginatedResultsRetrievedDiscoverabilityListener() { + super(); + } + + // API + + @Override + public final void onApplicationEvent(final PaginatedResultsRetrievedEvent ev) { + Preconditions.checkNotNull(ev); + + addLinkHeaderOnPagedResourceRetrieval(ev.getUriBuilder(), ev.getResponse(), ev.getClazz(), ev.getPage(), ev.getTotalPages(), ev.getPageSize()); + } + + // - note: at this point, the URI is transformed into plural (added `s`) in a hardcoded way - this will change in the future + final void addLinkHeaderOnPagedResourceRetrieval(final UriComponentsBuilder uriBuilder, final HttpServletResponse response, final Class clazz, final int page, final int totalPages, final int pageSize) { + plural(uriBuilder, clazz); + + final StringBuilder linkHeader = new StringBuilder(); + if (hasNextPage(page, totalPages)) { + final String uriForNextPage = constructNextPageUri(uriBuilder, page, pageSize); + linkHeader.append(LinkUtil.createLinkHeader(uriForNextPage, LinkUtil.REL_NEXT)); + } + if (hasPreviousPage(page)) { + final String uriForPrevPage = constructPrevPageUri(uriBuilder, page, pageSize); + appendCommaIfNecessary(linkHeader); + linkHeader.append(LinkUtil.createLinkHeader(uriForPrevPage, LinkUtil.REL_PREV)); + } + if (hasFirstPage(page)) { + final String uriForFirstPage = constructFirstPageUri(uriBuilder, pageSize); + appendCommaIfNecessary(linkHeader); + linkHeader.append(LinkUtil.createLinkHeader(uriForFirstPage, LinkUtil.REL_FIRST)); + } + if (hasLastPage(page, totalPages)) { + final String uriForLastPage = constructLastPageUri(uriBuilder, totalPages, pageSize); + appendCommaIfNecessary(linkHeader); + linkHeader.append(LinkUtil.createLinkHeader(uriForLastPage, LinkUtil.REL_LAST)); + } + + if (linkHeader.length() > 0) { + response.addHeader(HttpHeaders.LINK, linkHeader.toString()); + } + } + + final String constructNextPageUri(final UriComponentsBuilder uriBuilder, final int page, final int size) { + return uriBuilder.replaceQueryParam(PAGE, page + 1).replaceQueryParam("size", size).build().encode().toUriString(); + } + + final String constructPrevPageUri(final UriComponentsBuilder uriBuilder, final int page, final int size) { + return uriBuilder.replaceQueryParam(PAGE, page - 1).replaceQueryParam("size", size).build().encode().toUriString(); + } + + final String constructFirstPageUri(final UriComponentsBuilder uriBuilder, final int size) { + return uriBuilder.replaceQueryParam(PAGE, 0).replaceQueryParam("size", size).build().encode().toUriString(); + } + + final String constructLastPageUri(final UriComponentsBuilder uriBuilder, final int totalPages, final int size) { + return uriBuilder.replaceQueryParam(PAGE, totalPages).replaceQueryParam("size", size).build().encode().toUriString(); + } + + final boolean hasNextPage(final int page, final int totalPages) { + return page < totalPages - 1; + } + + final boolean hasPreviousPage(final int page) { + return page > 0; + } + + final boolean hasFirstPage(final int page) { + return hasPreviousPage(page); + } + + final boolean hasLastPage(final int page, final int totalPages) { + return totalPages > 1 && hasNextPage(page, totalPages); + } + + final void appendCommaIfNecessary(final StringBuilder linkHeader) { + if (linkHeader.length() > 0) { + linkHeader.append(", "); + } + } + + // template + + protected void plural(final UriComponentsBuilder uriBuilder, final Class clazz) { + final String resourceName = clazz.getSimpleName() + "s"; + uriBuilder.path("/" + resourceName); + } + +} diff --git a/spring-security-rest-full/src/main/java/org/baeldung/web/hateoas/PaginatedResultsRetrievedEvent.java b/spring-security-rest-full/src/main/java/org/baeldung/web/hateoas/PaginatedResultsRetrievedEvent.java new file mode 100644 index 0000000000..a9fe55ed3f --- /dev/null +++ b/spring-security-rest-full/src/main/java/org/baeldung/web/hateoas/PaginatedResultsRetrievedEvent.java @@ -0,0 +1,67 @@ +package org.baeldung.web.hateoas; + +import java.io.Serializable; + +import javax.servlet.http.HttpServletResponse; + +import org.springframework.context.ApplicationEvent; +import org.springframework.web.util.UriComponentsBuilder; + +/** + * Event that is fired when a paginated search is performed. + *

+ * This event object contains all the information needed to create the URL for the paginated results + * + * @param + * Type of the result that is being handled (commonly Entities). + */ +public final class PaginatedResultsRetrievedEvent extends ApplicationEvent { + private final UriComponentsBuilder uriBuilder; + private final HttpServletResponse response; + private final int page; + private final int totalPages; + private final int pageSize; + + public PaginatedResultsRetrievedEvent(final Class clazz, final UriComponentsBuilder uriBuilderToSet, final HttpServletResponse responseToSet, final int pageToSet, final int totalPagesToSet, final int pageSizeToSet) { + super(clazz); + + uriBuilder = uriBuilderToSet; + response = responseToSet; + page = pageToSet; + totalPages = totalPagesToSet; + pageSize = pageSizeToSet; + } + + // API + + public final UriComponentsBuilder getUriBuilder() { + return uriBuilder; + } + + public final HttpServletResponse getResponse() { + return response; + } + + public final int getPage() { + return page; + } + + public final int getTotalPages() { + return totalPages; + } + + public final int getPageSize() { + return pageSize; + } + + /** + * The object on which the Event initially occurred. + * + * @return The object on which the Event initially occurred. + */ + @SuppressWarnings("unchecked") + public final Class getClazz() { + return (Class) getSource(); + } + +} diff --git a/spring-security-rest-full/src/main/java/org/baeldung/web/util/LinkUtil.java b/spring-security-rest-full/src/main/java/org/baeldung/web/util/LinkUtil.java index 9e8979e4bd..b2137aeeff 100644 --- a/spring-security-rest-full/src/main/java/org/baeldung/web/util/LinkUtil.java +++ b/spring-security-rest-full/src/main/java/org/baeldung/web/util/LinkUtil.java @@ -7,6 +7,12 @@ import javax.servlet.http.HttpServletResponse; */ public final class LinkUtil { + public static final String REL_COLLECTION = "collection"; + public static final String REL_NEXT = "next"; + public static final String REL_PREV = "prev"; + public static final String REL_FIRST = "first"; + public static final String REL_LAST = "last"; + private LinkUtil() { throw new AssertionError(); }