All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.demoiselle.jee.crud.pagination.PaginationHelper Maven / Gradle / Ivy

There is a newer version: 3.0.4
Show newest version
/*
 * Demoiselle Framework
 *
 * License: GNU Lesser General Public License (LGPL), version 3 or later.
 * See the lgpl.txt file in the root directory or .
 */
package org.demoiselle.jee.crud.pagination;

import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.logging.Logger;

import javax.enterprise.context.RequestScoped;
import javax.inject.Inject;
import javax.ws.rs.container.ContainerResponseContext;
import javax.ws.rs.container.ResourceInfo;
import javax.ws.rs.core.HttpHeaders;
import javax.ws.rs.core.UriInfo;

import org.demoiselle.jee.crud.AbstractDAO;
import org.demoiselle.jee.crud.CrudUtilHelper;
import org.demoiselle.jee.crud.DemoiselleRequestContext;
import org.demoiselle.jee.crud.ReservedHTTPHeaders;
import org.demoiselle.jee.crud.ReservedKeyWords;
import org.demoiselle.jee.crud.Search;

/**
 * Class responsible for managing the 'range' parameter comes from Url Query String.
 * 
 * Ex:
 * 
 * Given a request
 * 
 * GET @{literal http://localhost:8080/api/users?range=0-10}
 * 
* * This class will processing the request above and parse the range parameters to * {@link DemoiselleRequestContext} object. * * This object will be use on {@link AbstractDAO} class to execute the pagination on database. * */ @RequestScoped public class PaginationHelper { private UriInfo uriInfo; private ResourceInfo resourceInfo; @Inject private DemoiselleRequestContext drc; @Inject private PaginationHelperMessage message; @Inject private PaginationHelperConfig paginationConfig; private static final Logger logger = Logger.getLogger(PaginationHelper.class.getName()); public PaginationHelper() { } public PaginationHelper(ResourceInfo resourceInfo, UriInfo uriInfo, PaginationHelperConfig paginationConfig, DemoiselleRequestContext drc, PaginationHelperMessage message) { this.resourceInfo = resourceInfo; this.uriInfo = uriInfo; this.paginationConfig = paginationConfig; this.drc = drc; this.message = message; } /** * Open the request query string to extract values from 'range' parameter and * fill the {@link DemoiselleRequestContext#setOffset(Integer)} and {@link DemoiselleRequestContext#setLimit(Integer)} * * @param resourceInfo ResourceInfo * @param uriInfo UriInfo */ public void execute(ResourceInfo resourceInfo, UriInfo uriInfo) { fillObjects(resourceInfo, uriInfo); drc.setPaginationEnabled(isPaginationEnabled()); if (drc.isPaginationEnabled()) { if (isRequestPagination()) { checkAndFillRangeValues(); } if (hasSearchAnnotation() && !isRequestPagination()) { drc.setLimit(getDefaultNumberPagination() - 1); drc.setOffset(new Integer(0)); } } if (hasSearchAnnotation() && isRequestPagination()) { Search searchAnnotation = resourceInfo.getResourceMethod().getAnnotation(Search.class); // Pagination @Search.withPagination is disabled but the request parameter has 'range' parameter if (searchAnnotation.withPagination() == Boolean.FALSE){ throw new IllegalArgumentException(message.paginationIsNotEnabled()); } } } private void fillObjects(ResourceInfo resourceInfo, UriInfo uriInfo) { this.resourceInfo = resourceInfo == null ? this.resourceInfo : resourceInfo; this.uriInfo = uriInfo == null ? this.uriInfo : uriInfo; } /** * Check the pagination is enabled * * @return pagination enabled/disabled */ private Boolean isPaginationEnabled() { if (paginationConfig.getIsGlobalEnabled() == Boolean.FALSE) { return Boolean.FALSE; } if (hasSearchAnnotation()) { Search searchAnnotation = resourceInfo.getResourceMethod().getAnnotation(Search.class); return searchAnnotation.withPagination(); } return paginationConfig.getIsGlobalEnabled(); } /** * Check if the actual request has the 'range' parameter on query string * * @return is request pagination or not */ private Boolean isRequestPagination() { // Verify if contains 'range' in url if (uriInfo.getQueryParameters().containsKey(ReservedKeyWords.DEFAULT_RANGE_KEY.getKey())) { return Boolean.TRUE; } return Boolean.FALSE; } /** * Check if the value of 'range' parameter is valid using the rules: * * - Value formatted like offset-limit (range=0-10); * - The 'offset' and 'limit' should be a integer; * - The 'offset' should be less than or equals 'limit'; * * * @throws IllegalArgumentException The format is invalid */ private void checkAndFillRangeValues() throws IllegalArgumentException { List rangeList = uriInfo.getQueryParameters().get(ReservedKeyWords.DEFAULT_RANGE_KEY.getKey()); if (!rangeList.isEmpty()) { String range[] = rangeList.get(0).split("-"); if (range.length == 2) { String offset = range[0]; String limit = range[1]; try { drc.setOffset(new Integer(offset)); drc.setLimit(new Integer(limit)); if (drc.getOffset() > drc.getLimit()) { logInvalidRangeParameters(rangeList.get(0)); throw new IllegalArgumentException(this.message.invalidRangeParameters()); } if (((drc.getLimit() - drc.getOffset()) + 1) > getDefaultNumberPagination()) { logger.warning(message.defaultPaginationNumberExceed(getDefaultNumberPagination()) + ", [" + drc.toString() + "]"); throw new IllegalArgumentException(message.defaultPaginationNumberExceed(getDefaultNumberPagination())); } } catch (NumberFormatException nfe) { logInvalidRangeParameters(rangeList.get(0)); throw new IllegalArgumentException(message.invalidRangeParameters()); } } else { logInvalidRangeParameters(rangeList.get(0)); throw new IllegalArgumentException(message.invalidRangeParameters()); } } } /** * Get default pagination number, if the target method is annotated with Search annotation the default annotation * will be {@link Search#quantityPerPage()} otherwise the default pagination will be {@link PaginationHelperConfig#getDefaultPagination()} value; * * @return Number per page */ private Integer getDefaultNumberPagination() { if (hasSearchAnnotation()) { Search searchAnnotation = resourceInfo.getResourceMethod().getAnnotation(Search.class); return searchAnnotation.quantityPerPage(); } return paginationConfig.getDefaultPagination(); } private Boolean hasSearchAnnotation() { return resourceInfo.getResourceMethod().isAnnotationPresent(Search.class); } /** * Check if the actual response is a Partial Content (HTTP 206 code) * @return is partial content or not */ public Boolean isPartialContentResponse() { Integer limit = drc.getLimit() == null ? 0 : drc.getLimit(); Long count = drc.getCount() == null ? 0 : drc.getCount(); return !((limit + 1) >= count); } private void logInvalidRangeParameters(String range) { logger.warning(message.invalidRangeParameters() + ", [params: " + range + "]"); } /** * Build the 'Content-Range' HTTP Header value. * * @return 'Content-Range' value */ private String buildContentRange() { Integer limit = drc.getLimit() == null ? getDefaultNumberPagination() - 1 : drc.getLimit(); Integer offset = drc.getOffset() == null ? 0 : drc.getOffset(); Long count = drc.getCount() == null ? 0 : drc.getCount(); return offset + "-" + (limit.equals(0) ? count - 1 : limit) + "/" + count; } /** * Build the 'Accept-Range' HTTP Header value. * * @return 'Accept-Range' value */ public String buildAcceptRange() { String resource = ""; if (drc.getEntityClass() != null) { resource = drc.getEntityClass().getSimpleName().toLowerCase(); } else { if (resourceInfo != null && resourceInfo.getResourceClass() != null) { Class targetClass = CrudUtilHelper.getTargetClass(resourceInfo.getResourceClass()); if (targetClass != null) { resource = targetClass.getSimpleName().toLowerCase(); } } } if(!resource.isEmpty()){ return resource + " " + getDefaultNumberPagination(); } return null; } /** * Set the 'Content-Range', 'Accept-Range', 'Link' and 'Access-Control-Expose-Headers' HTTP headers; * * @param resourceInfo ResourceInfo * @param uriInfo UriInfo * * @return A map with HTTP headers */ public Map buildHeaders(ResourceInfo resourceInfo, UriInfo uriInfo) { fillObjects(resourceInfo, uriInfo); Map headers = new ConcurrentHashMap<>(); if (drc.isPaginationEnabled()) { headers.put(ReservedHTTPHeaders.HTTP_HEADER_CONTENT_RANGE.getKey(), buildContentRange()); headers.put(ReservedHTTPHeaders.HTTP_HEADER_ACCEPT_RANGE.getKey(), buildAcceptRange()); String linkHeader = buildLinkHeader(); if (!linkHeader.isEmpty()) { headers.put(HttpHeaders.LINK, linkHeader); } } return headers; } /** * Build the 'Link' HTTP header value * * @return 'Link' value */ private String buildLinkHeader() { StringBuffer sb = new StringBuffer(); String url = uriInfo.getRequestUri().toString(); url = url.replaceFirst(".range=([^&]*)", ""); if (drc.getOffset() == null){ drc.setOffset(new Integer(0)); } if (drc.getLimit() == null){ drc.setLimit(getDefaultNumberPagination() - 1); } Integer offset = drc.getOffset() + 1; Integer limit = drc.getLimit() + 1; Integer quantityPerPage = (limit - offset) + 1; if (uriInfo.getQueryParameters().isEmpty() || (uriInfo.getQueryParameters().size() == 1 && uriInfo.getQueryParameters().containsKey(ReservedKeyWords.DEFAULT_RANGE_KEY.getKey()))) { url += "?" + ReservedKeyWords.DEFAULT_RANGE_KEY.getKey() + "="; } else { url += "&" + ReservedKeyWords.DEFAULT_RANGE_KEY.getKey() + "="; } if (!isFirstPage()) { Integer prevPageRangeInit = (drc.getOffset() - quantityPerPage) < 0 ? 0 : (drc.getOffset() - quantityPerPage); Integer firstRange2 = quantityPerPage - 1 < drc.getOffset() - 1 ? quantityPerPage - 1 : drc.getOffset() - 1; String firstPage = url + 0 + "-" + firstRange2; String prevPage = url + prevPageRangeInit + "-" + (drc.getOffset() - 1); sb.append("<").append(firstPage).append(">; rel=\"first\","); sb.append("<").append(prevPage).append(">; rel=\"prev\","); } if (isPartialContentResponse()) { String nextPage = url + (drc.getOffset() + quantityPerPage) + "-" + (2 * quantityPerPage + drc.getOffset() - 1); String lastPage = url + (drc.getCount() - quantityPerPage) + "-" + (drc.getCount() - 1); if (offset + quantityPerPage >= drc.getCount() - 1) { nextPage = lastPage; } sb.append("<").append(nextPage).append(">; rel=\"next\","); sb.append("<").append(lastPage).append(">; rel=\"last\""); } return sb.toString(); } private Boolean isFirstPage() { return drc.getOffset().equals(0); } public void buildAcceptRangeWithResponse(ContainerResponseContext response) { if (response != null) { String acceptRangeHeader = buildAcceptRange(); if(acceptRangeHeader != null){ response.getHeaders().putSingle(ReservedHTTPHeaders.HTTP_HEADER_ACCEPT_RANGE.getKey(), acceptRangeHeader); } } } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy