Skip to content
Snippets Groups Projects

Update to add external hook for page size for paginated results

5 files
+ 112
59
Compare changes
  • Side-by-side
  • Inline
Files
5
@@ -4,9 +4,11 @@ import java.io.IOException;
@@ -4,9 +4,11 @@ import java.io.IOException;
import java.util.List;
import java.util.List;
import javax.enterprise.inject.Instance;
import javax.enterprise.inject.Instance;
 
import javax.servlet.http.HttpServletResponse;
import javax.ws.rs.container.ContainerRequestContext;
import javax.ws.rs.container.ContainerRequestContext;
import javax.ws.rs.container.ContainerResponseContext;
import javax.ws.rs.container.ContainerResponseContext;
import javax.ws.rs.container.ContainerResponseFilter;
import javax.ws.rs.container.ContainerResponseFilter;
 
import javax.ws.rs.core.Context;
import javax.ws.rs.core.MultivaluedMap;
import javax.ws.rs.core.MultivaluedMap;
import javax.ws.rs.core.UriBuilder;
import javax.ws.rs.core.UriBuilder;
import javax.ws.rs.core.UriInfo;
import javax.ws.rs.core.UriInfo;
@@ -18,15 +20,20 @@ import org.jboss.resteasy.core.ResteasyContext;
@@ -18,15 +20,20 @@ import org.jboss.resteasy.core.ResteasyContext;
import org.jboss.resteasy.spi.LinkHeader;
import org.jboss.resteasy.spi.LinkHeader;
/**
/**
* Adds pagination and Link headers to the response by slicing the response entity if its a list
* Adds pagination and Link headers to the response by slicing the response
* entity. This will not dig into complex entities to avoid false positives.
* entity if its a list entity. This will not dig into complex entities to avoid
 
* false positives.
*
*
* @author Martin Lowe
* @author Martin Lowe
*/
*/
@Provider
@Provider
public class PaginatedResultsFilter implements ContainerResponseFilter {
public class PaginatedResultsFilter implements ContainerResponseFilter {
// should be set whenever we pass a limited subset to the response to be paginated of a larger subset
// should be set whenever we pass a limited subset to the response to be
 
// paginated of a larger subset
public static final String MAX_RESULTS_SIZE_HEADER = "X-Max-Result-Size";
public static final String MAX_RESULTS_SIZE_HEADER = "X-Max-Result-Size";
 
// should be set whenever we request data that has a maximum page size that is
 
// different than the default here
 
public static final String MAX_PAGE_SIZE_HEADER = "X-Max-Page-Size";
@ConfigProperty(name = "eclipse.pagination.enabled", defaultValue = "true")
@ConfigProperty(name = "eclipse.pagination.enabled", defaultValue = "true")
Instance<Boolean> enablePagination;
Instance<Boolean> enablePagination;
@@ -40,29 +47,30 @@ public class PaginatedResultsFilter implements ContainerResponseFilter {
@@ -40,29 +47,30 @@ public class PaginatedResultsFilter implements ContainerResponseFilter {
@ConfigProperty(name = "eclipse.pagination.scheme.value", defaultValue = "https")
@ConfigProperty(name = "eclipse.pagination.scheme.value", defaultValue = "https")
Instance<String> linkScheme;
Instance<String> linkScheme;
 
@Context
 
HttpServletResponse response;
 
@Override
@Override
public void filter(
public void filter(ContainerRequestContext requestContext, ContainerResponseContext responseContext)
ContainerRequestContext requestContext, ContainerResponseContext responseContext)
throws IOException {
throws IOException {
if (enablePagination.get()) {
if (enablePagination.get()) {
int pageSize = getCurrentLimit(responseContext);
int pageSize = defaultPageSize.get();
Object entity = responseContext.getEntity();
Object entity = responseContext.getEntity();
// only try and paginate if there are multiple entities
// only try and paginate if there are multiple entities
if (entity instanceof List) {
if (entity instanceof List) {
List<?> listEntity = (List<?>) entity;
List<?> listEntity = (List<?>) entity;
int page = getRequestedPage(listEntity);
int page = getRequestedPage(listEntity, pageSize);
// if available, use max results header value
// if available, use max results header value
String rawMaxSize = responseContext.getHeaderString(MAX_RESULTS_SIZE_HEADER);
String rawMaxSize = response.getHeader(MAX_RESULTS_SIZE_HEADER);
int maxSize = listEntity.size();
int maxSize = listEntity.size();
if (StringUtils.isNumeric(rawMaxSize)) {
if (StringUtils.isNumeric(rawMaxSize)) {
maxSize = Integer.valueOf(rawMaxSize);
maxSize = Integer.valueOf(rawMaxSize);
}
}
 
int lastPage = (int) Math.ceil((double) maxSize / pageSize);
int lastPage = (int) Math.ceil((double) maxSize / pageSize);
// set the sliced array as the entity
// set the sliced array as the entity
responseContext.setEntity(
responseContext
listEntity.subList(
.setEntity(listEntity.subList(getArrayLimitedNumber(listEntity, Math.max(0, page - 1) * pageSize),
getArrayLimitedNumber(listEntity, Math.max(0, page - 1) * pageSize),
getArrayLimitedNumber(listEntity, pageSize * page)));
getArrayLimitedNumber(listEntity, pageSize * page)));
// set the link header to the response
// set the link header to the response
@@ -73,6 +81,7 @@ public class PaginatedResultsFilter implements ContainerResponseFilter {
@@ -73,6 +81,7 @@ public class PaginatedResultsFilter implements ContainerResponseFilter {
private LinkHeader createLinkHeader(int page, int lastPage) {
private LinkHeader createLinkHeader(int page, int lastPage) {
// add link headers for paginated page hints
// add link headers for paginated page hints
 
UriBuilder builder = getUriInfo().getRequestUriBuilder();
UriBuilder builder = getUriInfo().getRequestUriBuilder();
LinkHeader lh = new LinkHeader();
LinkHeader lh = new LinkHeader();
// add first + last page link headers
// add first + last page link headers
@@ -90,19 +99,21 @@ public class PaginatedResultsFilter implements ContainerResponseFilter {
@@ -90,19 +99,21 @@ public class PaginatedResultsFilter implements ContainerResponseFilter {
}
}
/**
/**
* Gets the current requested page, rounding down to max if larger than the max page number, and
* Gets the current requested page, rounding down to max if larger than the max
* up if below 1.
* page number, and up if below 1.
*
*
* @param listEntity list entity used to determine the number of pages present for current call.
* @param listEntity list entity used to determine the number of pages present
* @return the current page number if set, the last page if greater, or 1 if not set or negative.
* for current call.
 
* @return the current page number if set, the last page if greater, or 1 if not
 
* set or negative.
*/
*/
private int getRequestedPage(List<?> listEntity) {
private int getRequestedPage(List<?> listEntity, int pageSize) {
MultivaluedMap<String, String> params = getUriInfo().getQueryParameters();
MultivaluedMap<String, String> params = getUriInfo().getQueryParameters();
if (params.containsKey("page")) {
if (params.containsKey("page")) {
try {
try {
int page = Integer.parseInt(params.getFirst("page"));
int page = Integer.parseInt(params.getFirst("page"));
// use double cast int to allow ceil call to round up for pages
// use double cast int to allow ceil call to round up for pages
int maxPage = (int) Math.ceil((double) listEntity.size() / defaultPageSize.get());
int maxPage = (int) Math.ceil((double) listEntity.size() / pageSize);
// get page, with min of 1 and max of last page
// get page, with min of 1 and max of last page
return Math.min(Math.max(1, page), maxPage);
return Math.min(Math.max(1, page), maxPage);
} catch (NumberFormatException e) {
} catch (NumberFormatException e) {
@@ -114,11 +125,29 @@ public class PaginatedResultsFilter implements ContainerResponseFilter {
@@ -114,11 +125,29 @@ public class PaginatedResultsFilter implements ContainerResponseFilter {
}
}
/**
/**
* Builds an href for a paginated link using the BaseUri UriBuilder from the UriInfo object,
* Allows for external bindings to affect the current page size, defaulting to
* replacing just the page query parameter.
* the internal set configuration.
 
*
 
* @param responseContext
 
* @return
 
*/
 
private int getCurrentLimit(ContainerResponseContext responseContext) {
 
if (response.getHeaderNames().contains(MAX_PAGE_SIZE_HEADER)) {
 
try {
 
return Integer.parseInt(response.getHeader(MAX_PAGE_SIZE_HEADER));
 
} catch (NumberFormatException e) {
 
// page size isn't a number, allow to return default outside current scope
 
}
 
}
 
return defaultPageSize.get();
 
}
 
 
/**
 
* Builds an href for a paginated link using the BaseUri UriBuilder from the
 
* UriInfo object, replacing just the page query parameter.
*
*
* @param builder base URI builder from the UriInfo object.
* @param builder base URI builder from the UriInfo object.
* @param page the page to link to in the returned link
* @param page the page to link to in the returned link
* @return fully qualified HREF for the paginated results
* @return fully qualified HREF for the paginated results
*/
*/
private String buildHref(UriBuilder builder, int page) {
private String buildHref(UriBuilder builder, int page) {
@@ -132,9 +161,10 @@ public class PaginatedResultsFilter implements ContainerResponseFilter {
@@ -132,9 +161,10 @@ public class PaginatedResultsFilter implements ContainerResponseFilter {
* Gets an int bound by the size of a list.
* Gets an int bound by the size of a list.
*
*
* @param list the list to bind the number by
* @param list the list to bind the number by
* @param num the number to check for exceeding bounds.
* @param num the number to check for exceeding bounds.
* @return the passed number if its within the size of the given array, 0 if the number is
* @return the passed number if its within the size of the given array, 0 if the
* negative, and the array size if greater than the maximum bounds.
* number is negative, and the array size if greater than the maximum
 
* bounds.
*/
*/
private int getArrayLimitedNumber(List<?> list, int num) {
private int getArrayLimitedNumber(List<?> list, int num) {
if (num < 0) {
if (num < 0) {
Loading