Java tutorial
/* * 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); } }