io.springagora.store.rest.controllers.ProductRestController.java Source code

Java tutorial

Introduction

Here is the source code for io.springagora.store.rest.controllers.ProductRestController.java

Source

/*
 * Spring Agora 
 * https://github.com/ThiagoUriel/spring-agora-web
 * Copyright (C) 2014 - Thiago Uriel M. Garcia
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License along
 * with this program; if not, write to the Free Software Foundation, Inc.,
 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
 */
package io.springagora.store.rest.controllers;

import io.springagora.core.domain.Product;
import io.springagora.core.domain.Review;
import io.springagora.core.exceptions.EntityNotFoundException;

import org.springframework.data.domain.Page;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestController;

/**
 * Controller for the RESTful API related to <b>{@code Product}</b> entitites.
 * <p>
 * Methods that handle HTTP requisitions expect and responds in the HTTP procotol,
 * using verbs as actions and status code as responses. The response bodies are 
 * provided using JSON notation.
 * 
 * @author Thiago Uriel M. Garcia
 */
@RestController
@RequestMapping(value = "/products")
public class ProductRestController {
    /**
     * Lists all products, organized in pages.
     * 
     * @param page  Page number being requested. Not required, "0" is default.
     * @param size  Page size being requested. Not required, "10" is default.
     * 
     * @return
     *      Returns the list of products found, in a given page. If there is a
     *      result, then it will have a list of {@code Product} entities.
     * 
     * @throws EntityNotFoundException
     *      If the retrieved list is {@code null} or empty, then this exception
     *      will be thrown, so the framework can provide the error response.
     */
    @RequestMapping(method = RequestMethod.GET)
    @ResponseStatus(value = HttpStatus.OK)
    public Page<Product> listAllProducts(@RequestParam(value = "page", defaultValue = "0") String page,
            @RequestParam(value = "size", defaultValue = "10") String size) {
        return handleGeneralPagination(page, size, new IPaginatedFinder<Product>() {
            /** {@inheritDoc} */
            @Override
            public Page<Product> executeFind(int page, int size) {
                return Product.findAll(page, size);
            }
        });
    }

    /**
     * Lists all products flagged for the showcase display.
     * 
     * @return
     *      Returns the list of products found, in a given page. If there is a
     *      result, then it will have a list of {@code Product} entities.
     *      
     * @throws EntityNotFoundException
     *      If the retrieved list is {@code null} or empty, then this exception
     *      will be thrown, so the framework can provide the error response.
     */
    @RequestMapping(value = "/showcase", method = RequestMethod.GET)
    @ResponseStatus(value = HttpStatus.OK)
    public Page<Product> showcaseProducts(@RequestParam(value = "page", defaultValue = "0") String page,
            @RequestParam(value = "size", defaultValue = "10") String size) {
        return handleGeneralPagination(page, size, new IPaginatedFinder<Product>() {
            /** {@inheritDoc} */
            @Override
            public Page<Product> executeFind(int page, int size) {
                return Product.findForShowcase(page, size);
            }
        });
    }

    /**
     * Find a product based on it's identifier. Reviews that are registered
     * for the product (when found) are also returned in the response.
     * 
     * @param id    Identifier used as reference for the requested product.
     * 
     * @return
     *      Returns the product found for the given identifier, along with all
     *      of it's reviews. This method only returns if a product is found.
     * 
     * @throws EntityNotFoundException
     *      If no product is found for the given identifier, then this method
     *      will throw an exception of type {@code EntityNotFoundException}.
     */
    @RequestMapping(value = "/{id}", method = RequestMethod.GET)
    @ResponseStatus(value = HttpStatus.OK)
    public Product findProductById(@PathVariable Long id) {
        Product product = Product.findOne(id);
        if (product != null) {
            return product;
        }
        throw new EntityNotFoundException("Sorry, I couldn't find this product.");
    }

    /**
     * Appends a new review for a given product. 
     * 
     * @param id        Product identifier that is used as reference for the new review.
     * @param review    {@code Review} entity that is going to be assigned to product.
     * 
     * @return
     *      The {@code Product} entity, updated with the new posted review.
     *      
     * @throws EntityNotFoundException
     *      If no product is found for the given identifier, then this method
     *      will throw an exception of type {@code EntityNotFoundException}.
     */
    @RequestMapping(value = "/{id}/reviews", method = RequestMethod.POST, consumes = MediaType.APPLICATION_JSON_VALUE)
    @ResponseStatus(value = HttpStatus.CREATED)
    public Product addReviewForProduct(@PathVariable Long id, @RequestBody Review review) {
        Product product = findProductById(id);
        review.setProduct(product);
        review.persist();
        return review.getProduct();
    }

    /**
     * Internal helper method that correctly applies pagination rules for list
     * operations that support it. It won't throw any error in case of errors 
     * during the parse of the page and size directives. Instead it will always
     * assume default values.
     * <p>
     * Once processed, pagination variables are sent to {@code IPaginatedFinder}
     * implementation received by the method, that can effectivelly fetch the page.
     * 
     * @param page      {@code String}. Current page being requested.
     * @param size      {@code String}. Ammount of elements being fetched.
     * @param finder    {@code IPaginatedFinder}. Finder to be executed. 
     * 
     * @return
     *      A {@code Page} with all results for a given page, in a given range. 
     *      If problems ocurred during the conversion of page and size, it will
     *      attempt to fetch the first page, with 10 results.
     *      
     * @throws EntityNotFoundException
     *      To be compliant with the RESTful API, if no results are found for a 
     *      given operation and page, then it will throw this exception that is
     *      correctly converted to an API error by the framework.
     */
    private static Page<Product> handleGeneralPagination(String page, String size,
            IPaginatedFinder<Product> finder) {
        //
        // Page and Size conversion. In case of problems, it won't throw any parse
        // errors, and will assume default values for both variables. 
        //
        int pageNumber, sizeNumber;
        try {
            pageNumber = Integer.parseInt(page);
            sizeNumber = Integer.parseInt(size);
        } catch (NumberFormatException nfExp) {
            pageNumber = 0;
            sizeNumber = 10;
        }
        //
        // Invoke the finder callback, passing the correct paging parameters to
        // it. Following, the results are validated and returned, or the API error
        // is thrown in case of a null/empty page.
        //
        Page<Product> products = finder.executeFind(pageNumber, sizeNumber);
        if (products != null && products.hasContent()) {
            return products;
        }
        throw new EntityNotFoundException("Sorry, no products found.");
    }

    /**
     * Interface representing the callback invoked after pagination directives
     * are correctly parsed by a handler method.
     * 
     * @author Thiago Uriel M. Garcia
     */
    private interface IPaginatedFinder<T> {
        /**
         * Finds a page of elements of type T, based on the pagination directives.
         * 
         * @param page  Page index for the fetch.
         * @param size  Entity limit for the fetch.
         * 
         * @return
         *      {@code Page} containing elements of type T.
         */
        Page<T> executeFind(final int page, final int size);
    }
}