package com.github.aesteve.vertx.nubes.context; import com.github.aesteve.vertx.nubes.handlers.impl.DefaultErrorHandler; import io.vertx.core.http.HttpServerRequest; import io.vertx.core.json.JsonObject; import io.vertx.ext.web.RoutingContext; import java.util.ArrayList; import java.util.List; import static java.util.Collections.emptyList; /** * Reflects the pagination state for a RoutingContext (request parameters) * Provides service methods to : * - generate pagination Link headers * - generate paginationContext to render a page on the server * * @author aesteve */ public class PaginationContext { public static final String DATA_ATTR = "paginationContext"; private static final String CURRENT_PAGE_QUERY_PARAM = "page"; private static final String PER_PAGE_QUERY_PARAM = "perPage"; private static final Integer DEFAULT_PER_PAGE = 30; private static final Integer MAX_PER_PAGE = 100; private Integer pageAsked = 1; private Integer itemsPerPage = DEFAULT_PER_PAGE; private Integer totalPages; // Will be set once the request has been processed (payload) /** * Prefer using fromRoutingContext but can be instanciated directly * (for static page generation for instance) * */ private PaginationContext(Integer pageAsked, Integer itemsPerPage) { if (pageAsked != null) this.pageAsked = pageAsked; if (itemsPerPage != null) this.itemsPerPage = itemsPerPage; } /** * The preferred way to create a PaginationContext * */ public static PaginationContext fromContext(RoutingContext context) { HttpServerRequest request = context.request(); String pageStr = request.getParam(PaginationContext.CURRENT_PAGE_QUERY_PARAM); String perPageStr = request.getParam(PaginationContext.PER_PAGE_QUERY_PARAM); Integer page = null; Integer perPage = null; try { if (pageStr != null) { page = Integer.parseInt(pageStr); } if (perPageStr != null) { perPage = Integer.parseInt(perPageStr); } } catch (NumberFormatException e) { DefaultErrorHandler.badRequest(context, "Invalid pagination parameters : expecting integers"); } if (perPage != null && perPage > PaginationContext.MAX_PER_PAGE) { DefaultErrorHandler.badRequest(context, "Invalid " + PaginationContext.PER_PAGE_QUERY_PARAM + " parameter, max is " + PaginationContext.MAX_PER_PAGE); } return new PaginationContext(page, perPage); } public String buildLinkHeader(HttpServerRequest request) { List<String> links = getNavLinks(request); if (links.isEmpty()) { return null; } return String.join(", ", links); } private List<String> getNavLinks(HttpServerRequest request) { if (totalPages == null) { return emptyList(); } List<String> links = new ArrayList<>(); if (pageAsked > 1) { links.add(pageUrl(request, 1, "first")); links.add(pageUrl(request, pageAsked - 1, "prev")); } if (pageAsked < totalPages) { links.add(pageUrl(request, totalPages, "last")); links.add(pageUrl(request, pageAsked + 1, "next")); } return links; } private String pageUrl(HttpServerRequest request, int pageNum, String rel) { StringBuilder sb = new StringBuilder("<"); String url = request.absoluteURI(); if (!url.contains("?")) { // can't rely on params() 'cause we might have injected some stuff (routing) url += "?" + CURRENT_PAGE_QUERY_PARAM + "=" + pageNum; url += "&" + PER_PAGE_QUERY_PARAM + "=" + itemsPerPage; } else { if (url.indexOf(CURRENT_PAGE_QUERY_PARAM + "=") > url.indexOf('?')) { url = url.replaceAll(CURRENT_PAGE_QUERY_PARAM + "=([^&]+)", CURRENT_PAGE_QUERY_PARAM + "=" + pageNum); } else { url += "&" + CURRENT_PAGE_QUERY_PARAM + "=" + pageNum; } if (!url.contains("&" + PER_PAGE_QUERY_PARAM) && !url.contains("?" + PER_PAGE_QUERY_PARAM)) { url += "&" + PER_PAGE_QUERY_PARAM + "=" + itemsPerPage; } } sb.append(url); sb.append(">; "); sb.append("rel=\"").append(rel).append("\""); return sb.toString(); } public Integer getItemsPerPage() { return itemsPerPage; } public Integer getTotalPages() { return totalPages; } public void setNbItems(Integer nbTotalItems) { this.totalPages = nbTotalItems / itemsPerPage; if (totalPages == 0) { totalPages = 1; } int modulo = nbTotalItems % itemsPerPage; if (modulo > 0) { this.totalPages = this.totalPages + 1; } } public void setNbItems(Long nbTotalItems) { setNbItems(nbTotalItems.intValue()); } public boolean hasMorePages() { return totalPages > pageAsked; } public int firstItemInPage() { return itemsPerPage * (pageAsked - 1); } public int lastItemInPage() { return firstItemInPage() + itemsPerPage; } /** * Creates a JsonObject containing all pagination * that can be injected and evaluated * when rendering a page on the server * * @return a JsonObject containing pagination informations */ public JsonObject toJsonObject() { JsonObject json = new JsonObject(); json.put("current", pageAsked); json.put("next", pageAsked.equals(totalPages) ? null : pageAsked + 1); json.put("last", pageAsked.equals(totalPages) ? null : totalPages); json.put("prev", pageAsked.equals(1) ? null : pageAsked - 1); json.put("first", pageAsked == 1 ? null : 1); json.put("perPage", itemsPerPage); json.put("total", totalPages); return json; } }