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