/**
* Copyright (c) 2009 - 2012 Red Hat, Inc.
*
* This software is licensed to you under the GNU General Public License,
* version 2 (GPLv2). There is NO WARRANTY for this software, express or
* implied, including the implied warranties of MERCHANTABILITY or FITNESS
* FOR A PARTICULAR PURPOSE. You should have received a copy of GPLv2
* along with this software; if not, see
* http://www.gnu.org/licenses/old-licenses/gpl-2.0.txt.
*
* Red Hat trademarks are not licensed under GPLv2. No permission is
* granted to use or replicate Red Hat trademarks that are incorporated
* in this software or its documentation.
*/
package org.candlepin.common.resteasy.filter;
import org.candlepin.common.config.Configuration;
import org.candlepin.common.paging.Page;
import org.candlepin.common.paging.PageRequest;
import org.candlepin.common.paging.Paginate;
import com.google.inject.Inject;
import com.google.inject.name.Named;
import org.jboss.resteasy.spi.LinkHeader;
import org.jboss.resteasy.spi.ResteasyProviderFactory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.List;
import java.util.Map.Entry;
import javax.annotation.Priority;
import javax.servlet.ServletContext;
import javax.ws.rs.Priorities;
import javax.ws.rs.container.ContainerRequestContext;
import javax.ws.rs.container.ContainerResponseContext;
import javax.ws.rs.container.ContainerResponseFilter;
import javax.ws.rs.core.MultivaluedMap;
import javax.ws.rs.core.UriBuilder;
import javax.ws.rs.ext.Provider;
/**
* LinkHeaderPostProcessor
*/
@Paginate
@Provider
@Priority(Priorities.HEADER_DECORATOR)
public class LinkHeaderResponseFilter implements ContainerResponseFilter {
private static Logger log = LoggerFactory.getLogger(LinkHeaderResponseFilter.class);
public static final String LINK_HEADER = "Link";
private String apiUrlPrefixKey;
private Configuration config;
private String contextPath;
@Inject
public LinkHeaderResponseFilter(Configuration config,
@Named("PREFIX_APIURL_KEY") String apiUrlPrefixKey) {
this.config = config;
this.apiUrlPrefixKey = apiUrlPrefixKey;
}
@SuppressWarnings("rawtypes")
public void filter(ContainerRequestContext reqContext, ContainerResponseContext respContext) {
Page page = ResteasyProviderFactory.getContextData(Page.class);
// Make sure we have page information in the context
if (page == null) {
return;
}
// If we aren't paging, then no need for Link headers.
if (page.getPageRequest() == null || !page.getPageRequest().isPaging()) {
return;
}
UriBuilder builder = buildBaseUrl(reqContext);
// If builder is null, we couldn't read the request URI, so stop.
if (builder == null) {
return;
}
MultivaluedMap<String, String> params = null;
params = reqContext.getUriInfo().getQueryParameters();
builder = addUnchangingQueryParams(builder, params);
//TODO add missing parameters like the default limit if no limit is given.
LinkHeader header = new LinkHeader();
Integer next = getNextPage(page);
if (next != null) {
header.addLink(null, "next", buildPageLink(builder, next), null);
}
Integer prev = getPrevPage(page);
if (prev != null) {
header.addLink(null, "prev", buildPageLink(builder, prev), null);
}
header.addLink(null, "first", buildPageLink(builder, 1), null);
header.addLink(null, "last", buildPageLink(builder, getLastPage(page)), null);
respContext.getHeaders().add(LINK_HEADER, header.toString());
}
protected String buildPageLink(UriBuilder b, int value) {
// Copy so we can use the same builder for building each link.
UriBuilder builder = b.clone();
builder.queryParam(PageRequest.PAGE_PARAM, String.valueOf(value));
return builder.build().toString();
}
protected Integer getLastPage(Page<?> page) {
PageRequest pageRequest = page.getPageRequest();
// The last page is ceiling(maxRecords/recordsPerPage)
int lastPage = page.getMaxRecords() / pageRequest.getPerPage();
if (page.getMaxRecords() % pageRequest.getPerPage() != 0) {
lastPage++;
}
return lastPage;
}
protected Integer getPrevPage(Page<?> page) {
Integer prev = page.getPageRequest().getPage() - 1;
// if the calculated page is out of bounds, return null
return (prev < 1 || prev >= getLastPage(page)) ? null : prev;
}
protected Integer getNextPage(Page<?> page) {
Integer next = page.getPageRequest().getPage() + 1;
return (next > getLastPage(page)) ? null : next;
}
protected UriBuilder buildBaseUrl(ContainerRequestContext reqContext) {
if (config.containsKey(this.apiUrlPrefixKey) && !"".equals(config.getString(this.apiUrlPrefixKey))) {
ServletContext servletContext = ResteasyProviderFactory.getContextData(ServletContext.class);
contextPath = servletContext.getContextPath();
StringBuffer url = new StringBuffer(config.getString(this.apiUrlPrefixKey));
// The default value of PREFIX_APIURL doesn't specify a scheme.
if (url.indexOf("://") == -1) {
url = new StringBuffer("https://").append(url);
}
String requestUri = reqContext.getUriInfo().getRequestUri().toString();
int offset = requestUri.lastIndexOf(contextPath);
if (offset >= 0) {
// Strip off the context
url.append(requestUri.substring(offset + contextPath.length()));
}
else {
log.warn("Could not find servlet context in {}", requestUri);
return null;
}
try {
UriBuilder builder = UriBuilder.fromUri(url.toString());
return builder;
}
catch (IllegalArgumentException e) {
log.warn("Couldn't build URI for link header using {}", url, e);
return null;
}
}
else {
return reqContext.getUriInfo().getRequestUriBuilder();
}
}
protected UriBuilder addUnchangingQueryParams(UriBuilder builder,
MultivaluedMap<String, String> params) {
// This will take care of adding back any order, per_page, or sort_by
// parameters provided too.
if (params != null) {
for (Entry<String, List<String>> e : params.entrySet()) {
if (!e.getKey().equals(PageRequest.PAGE_PARAM)) {
for (String v : e.getValue()) {
builder = builder.queryParam(e.getKey(), v);
}
}
}
}
return builder;
}
}