net.eusashead.hateoas.hal.response.impl.HalPageResponseBuilderImpl.java Source code

Java tutorial

Introduction

Here is the source code for net.eusashead.hateoas.hal.response.impl.HalPageResponseBuilderImpl.java

Source

package net.eusashead.hateoas.hal.response.impl;

/*
 * #[license]
 * spring-halbuilder
 * %%
 * Copyright (C) 2013 Eusa's Head
 * %%
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * 
 *      http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * %[license]
 */

import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;

import javax.servlet.http.HttpServletRequest;

import net.eusashead.hateoas.hal.adapter.RepresentationWriter;
import net.eusashead.hateoas.hal.response.HalPageResponseBuilder;
import net.eusashead.hateoas.header.ETagHeaderStrategy;
import net.eusashead.hateoas.response.ResponseBuilder;

import org.springframework.data.domain.Page;
import org.springframework.http.ResponseEntity;

import com.theoryinpractise.halbuilder.api.ReadableRepresentation;
import com.theoryinpractise.halbuilder.api.Representation;
import com.theoryinpractise.halbuilder.api.RepresentationFactory;

/**
 * {@link ResponseBuilder} used for 
 * creating a HAL {@link Representation} that
 * is a "page" of data - a subset of a result
 * set from a database used to conserve bandwidth
 * 
 * The main benefit of this class is that automates
 * the construction of "next" and "previous" links
 * within the HAL {@link Representation}
 * 
 * @author patrickvk
 *
 */
public class HalPageResponseBuilderImpl extends AbstractHalResponseBuilder implements HalPageResponseBuilder {

    private static final String CONTENT_REL_NAME = "content";

    /**
     * The name of the page paramter in the request query string
     */
    private String pageParameter = "page";

    /**
     * The underlying Page
     */
    private Page<?> page;

    /**
     * Construct with default values
     * for page size and page query
     * parameter name
     * 
     * @param representationFactory {@link RepresentationFactory} for constructing Hal {@link Representation}
     * @param request {@link HttpServletRequest} the incoming request from which URI and page parameter are to be extracted
     */
    public HalPageResponseBuilderImpl(RepresentationFactory representationFactory, HttpServletRequest request) {
        super(representationFactory, request);
    }

    /**
     * Construct with supplied values
     * for page size and page query
     * parameter name
     * 
     * @param representationFactory {@link RepresentationFactory} for constructing Hal {@link Representation}
     * @param request {@link HttpServletRequest} the incoming request from which URI and page parameter are to be extracted
     * @param pageParameter {@link String} name of the page number request query parameter
     */
    public HalPageResponseBuilderImpl(RepresentationFactory representationFactory, HttpServletRequest request,
            String pageParameter) {
        super(representationFactory, request);
        this.pageParameter = pageParameter;
    }

    @Override
    public <T> HalPageResponseBuilder page(Page<T> content) {
        this.page = content;
        this.assertPage();
        super.assertEntity();
        for (T item : safeList(content)) {
            this.entity.withBeanBasedRepresentation(CONTENT_REL_NAME, null, item);
        }
        return this;
    }

    @Override
    public <T> HalPageResponseBuilderImpl page(Page<T> content, RepresentationWriter<T> representationWriter) {
        this.page = content;
        this.assertPage();
        super.assertEntity();
        if (representationWriter == null) {
            throw new IllegalArgumentException("Parameter representationWriter cannot be null.");
        }
        for (T item : safeList(content)) {
            ReadableRepresentation rep = representationWriter.write(item, representationFactory);
            this.entity.withRepresentation(CONTENT_REL_NAME, rep);
        }
        return this;
    }

    private <T> Iterable<T> safeList(Iterable<T> iterable) {
        return iterable == null ? Collections.<T>emptyList() : iterable;
    }

    @Override
    public HalPageResponseBuilderImpl etag() {
        super.setEtagHeader();
        return this;
    }

    @Override
    public HalPageResponseBuilderImpl etag(ETagHeaderStrategy strategy) {
        super.setEtagHeader(strategy);
        return this;
    }

    @Override
    public HalPageResponseBuilderImpl etag(Date date) {
        super.setEtagHeader(date);
        return this;
    }

    @Override
    public HalPageResponseBuilderImpl etag(Integer version) {
        super.setEtagHeader(version);
        return this;
    }

    @Override
    public HalPageResponseBuilderImpl lastModified(Date date) {
        super.setLastModifiedHeader(date);
        return this;
    }

    @Override
    public HalPageResponseBuilderImpl expireIn(long millis) {
        super.setExpiryHeaders(millis);
        return this;
    }

    @Override
    public ResponseEntity<Representation> build() {

        // Check the representation is valid
        super.assertEntity();

        // Check that the underlying page is set
        this.assertPage();

        // Extract page data such as size, page number
        entity.withProperty("size", this.page.getSize());
        entity.withProperty("page", this.page.getNumber());
        entity.withProperty("numberOfElements", this.page.getNumberOfElements());
        entity.withProperty("totalElements", this.page.getTotalElements());

        // Next/back links
        if (this.page.hasNextPage()) {
            this.buildNextLink();
        }
        if (this.page.hasPreviousPage()) {
            this.buildPreviousLink();
        }

        // Assemble the response
        return super.buildResponseEntity(this.entity);
    }

    private void assertPage() {
        if (this.page == null) {
            throw new RuntimeException("Page cannot be null.");
        }

    }

    /**
     * Create the "next" link
     * for the paged search results
     */
    private void buildNextLink() {
        Map<String, String[]> params = modifyPageNumber(request, 1);
        String link = buildLink(params);
        entity.withLink("next", link);
    }

    /**
     * Create the "previous" link 
     * for the paged search results
     */
    private void buildPreviousLink() {
        Map<String, String[]> params = modifyPageNumber(request, -1);
        String link = buildLink(params);
        entity.withLink("previous", link);
    }

    /**
     * Create a link using the URI and
     * query string parameters
     * @param params
     * @return
     */
    private String buildLink(Map<String, String[]> params) {
        String queryString = buildQueryString(params);
        String link = String.format("%s?%s", request.getRequestURI(), queryString);
        return link;
    }

    /**
     * Build a query string from the supplied parameters
     * @param params
     * @return
     */
    private String buildQueryString(Map<String, String[]> params) {

        // Build the query string up from the parameters
        StringBuilder builder = new StringBuilder();
        for (String key : params.keySet()) {
            for (String val : params.get(key)) {
                builder.append(key);
                builder.append("=");
                builder.append(val);
                builder.append("&");
            }
        }

        // Remove the last ampersand if it is the trailing character
        if (builder.length() > 0) {
            if (builder.lastIndexOf("&") == (builder.length() - 1)) {
                builder.deleteCharAt(builder.length() - 1);
            }
        }
        return builder.toString();
    }

    /**
     * Get the existing request 
     * parameters and increment
     * the page parameter (or decrement).
     * 
     * The request should be identical except
     * for the page parameter
     * @param request
     * @param modifier
     * @return
     */
    private Map<String, String[]> modifyPageNumber(HttpServletRequest request, int modifier) {
        Map<String, String[]> oldParams = request.getParameterMap();
        Map<String, String[]> newParams = new HashMap<String, String[]>();

        // Set all parameters other than page number
        for (String key : oldParams.keySet()) {
            String[] value = oldParams.get(key);
            if (!key.equals(pageParameter)) {
                newParams.put(key, value);
            }
        }

        // Set page number
        int page = this.page.getNumber() + modifier;
        newParams.put(pageParameter, new String[] { Integer.valueOf(page).toString() });

        return newParams;
    }

}