ru.mystamps.web.controller.SeriesController.java Source code

Java tutorial

Introduction

Here is the source code for ru.mystamps.web.controller.SeriesController.java

Source

/*
 * Copyright (C) 2009-2017 Slava Semushin <slava.semushin@gmail.com>
 *
 * 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 ru.mystamps.web.controller;

import java.io.IOException;
import java.util.Calendar;
import java.util.Collections;
import java.util.Date;
import java.util.GregorianCalendar;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Objects;

import javax.servlet.http.HttpServletResponse;
import javax.validation.Valid;
import javax.validation.groups.Default;

import org.apache.commons.lang3.StringUtils;

import org.springframework.beans.propertyeditors.StringTrimmerEditor;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.validation.BindingResult;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.WebDataBinder;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.InitBinder;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.servlet.View;
import org.springframework.web.servlet.mvc.support.RedirectAttributes;
import org.springframework.web.servlet.view.RedirectView;

import org.springframework.security.core.annotation.AuthenticationPrincipal;

import lombok.RequiredArgsConstructor;

import ru.mystamps.web.Url;
import ru.mystamps.web.controller.converter.annotation.Category;
import ru.mystamps.web.controller.converter.annotation.Country;
import ru.mystamps.web.controller.converter.annotation.CurrentUser;
import ru.mystamps.web.dao.dto.EntityWithIdDto;
import ru.mystamps.web.dao.dto.LinkEntityDto;
import ru.mystamps.web.dao.dto.PurchaseAndSaleDto;
import ru.mystamps.web.dao.dto.SeriesInfoDto;
import ru.mystamps.web.model.AddImageForm;
import ru.mystamps.web.model.AddSeriesForm;
import ru.mystamps.web.model.AddSeriesSalesForm;
import ru.mystamps.web.service.CategoryService;
import ru.mystamps.web.service.CollectionService;
import ru.mystamps.web.service.CountryService;
import ru.mystamps.web.service.SeriesSalesService;
import ru.mystamps.web.service.SeriesService;
import ru.mystamps.web.service.TransactionParticipantService;
import ru.mystamps.web.service.dto.FirstLevelCategoryDto;
import ru.mystamps.web.service.dto.SeriesDto;
import ru.mystamps.web.support.spring.security.Authority;
import ru.mystamps.web.support.spring.security.CustomUserDetails;
import ru.mystamps.web.support.spring.security.SecurityContextUtils;
import ru.mystamps.web.support.togglz.Features;
import ru.mystamps.web.util.CatalogUtils;
import ru.mystamps.web.util.LocaleUtils;

import static ru.mystamps.web.controller.ControllerUtils.redirectTo;
import static ru.mystamps.web.validation.ValidationRules.MIN_RELEASE_YEAR;

@Controller
@RequiredArgsConstructor
@SuppressWarnings({ "PMD.AvoidDuplicateLiterals", "PMD.TooManyMethods", "PMD.GodClass" })
public class SeriesController {

    private static final Integer CURRENT_YEAR = new GregorianCalendar().get(Calendar.YEAR);

    private static final Map<Integer, Integer> YEARS;

    private final CategoryService categoryService;
    private final CollectionService collectionService;
    private final CountryService countryService;
    private final SeriesService seriesService;
    private final SeriesSalesService seriesSalesService;
    private final TransactionParticipantService transactionParticipantService;

    static {
        YEARS = new LinkedHashMap<>();
        for (Integer i = CURRENT_YEAR; i >= MIN_RELEASE_YEAR; i--) {
            YEARS.put(i, i);
        }
    }

    @InitBinder("addSeriesForm")
    protected void initBinder(WebDataBinder binder) {
        StringTrimmerEditor editor = new StringTrimmerEditor(" ", true);
        binder.registerCustomEditor(String.class, "michelNumbers", editor);
        binder.registerCustomEditor(String.class, "scottNumbers", editor);
        binder.registerCustomEditor(String.class, "yvertNumbers", editor);
        binder.registerCustomEditor(String.class, "gibbonsNumbers", editor);
        binder.registerCustomEditor(String.class, "comment", new StringTrimmerEditor(true));
    }

    @GetMapping(Url.ADD_SERIES_PAGE)
    public void showForm(@Category @RequestParam(name = "category", required = false) LinkEntityDto category,
            @Country @RequestParam(name = "country", required = false) LinkEntityDto country, Model model,
            Locale userLocale) {

        String lang = LocaleUtils.getLanguageOrNull(userLocale);

        List<FirstLevelCategoryDto> categories = categoryService.findFirstLevelCategories(lang);
        model.addAttribute("categories", categories);

        List<LinkEntityDto> countries = countryService.findAllAsLinkEntities(lang);
        model.addAttribute("countries", countries);

        model.addAttribute("years", YEARS);

        AddSeriesForm addSeriesForm = new AddSeriesForm();
        addSeriesForm.setPerforated(true);

        if (category != null) {
            addSeriesForm.setCategory(category);
        }

        if (country != null) {
            addSeriesForm.setCountry(country);
        }

        model.addAttribute("addSeriesForm", addSeriesForm);
    }

    @GetMapping(Url.ADD_SERIES_WITH_CATEGORY_PAGE)
    public View showFormWithCategory(@PathVariable("slug") String category, RedirectAttributes redirectAttributes) {

        redirectAttributes.addAttribute("category", category);

        RedirectView view = new RedirectView();
        view.setStatusCode(HttpStatus.MOVED_PERMANENTLY);
        view.setUrl(Url.ADD_SERIES_PAGE);

        return view;
    }

    @GetMapping(Url.ADD_SERIES_WITH_COUNTRY_PAGE)
    public View showFormWithCountry(@PathVariable("slug") String country, RedirectAttributes redirectAttributes) {

        redirectAttributes.addAttribute("country", country);

        RedirectView view = new RedirectView();
        view.setStatusCode(HttpStatus.MOVED_PERMANENTLY);
        view.setUrl(Url.ADD_SERIES_PAGE);

        return view;
    }

    @PostMapping(Url.ADD_SERIES_PAGE)
    public String processInput(
            @Validated({ Default.class, AddSeriesForm.ReleaseDateChecks.class,
                    AddSeriesForm.ImageChecks.class }) AddSeriesForm form,
            BindingResult result, @CurrentUser Integer currentUserId, Locale userLocale, Model model) {

        if (result.hasErrors()) {
            String lang = LocaleUtils.getLanguageOrNull(userLocale);

            List<FirstLevelCategoryDto> categories = categoryService.findFirstLevelCategories(lang);
            model.addAttribute("categories", categories);

            List<LinkEntityDto> countries = countryService.findAllAsLinkEntities(lang);
            model.addAttribute("countries", countries);

            model.addAttribute("years", YEARS);

            // don't try to re-display file upload field
            form.setImage(null);
            return null;
        }

        boolean userCanAddComments = SecurityContextUtils.hasAuthority(Authority.ADD_COMMENTS_TO_SERIES);
        Integer seriesId = seriesService.add(form, currentUserId, userCanAddComments);

        return redirectTo(Url.INFO_SERIES_PAGE, seriesId);
    }

    @GetMapping(Url.INFO_SERIES_PAGE)
    public String showInfo(@PathVariable("id") Integer seriesId, Model model, @CurrentUser Integer currentUserId,
            Locale userLocale, HttpServletResponse response) throws IOException {

        if (seriesId == null) {
            response.sendError(HttpServletResponse.SC_NOT_FOUND);
            return null;
        }

        String lang = LocaleUtils.getLanguageOrNull(userLocale);
        SeriesDto series = seriesService.findFullInfoById(seriesId, lang);
        if (series == null) {
            response.sendError(HttpServletResponse.SC_NOT_FOUND);
            return null;
        }

        Map<String, ?> commonAttrs = prepareCommonAttrsForSeriesInfo(series, currentUserId);
        model.addAllAttributes(commonAttrs);

        addSeriesSalesFormToModel(model);
        addImageFormToModel(model);

        model.addAttribute("maxQuantityOfImagesExceeded", false);

        return "series/info";
    }

    @PostMapping(Url.ADD_IMAGE_SERIES_PAGE)
    public String processImage(@Valid AddImageForm form, BindingResult result, @PathVariable("id") Integer seriesId,
            Model model, @CurrentUser Integer currentUserId, Locale userLocale, HttpServletResponse response)
            throws IOException {

        if (seriesId == null) {
            response.sendError(HttpServletResponse.SC_NOT_FOUND);
            return null;
        }

        String lang = LocaleUtils.getLanguageOrNull(userLocale);
        SeriesDto series = seriesService.findFullInfoById(seriesId, lang);
        if (series == null) {
            response.sendError(HttpServletResponse.SC_NOT_FOUND);
            return null;
        }

        boolean maxQuantityOfImagesExceeded = !isAdmin() && !isAllowedToAddingImages(series);
        model.addAttribute("maxQuantityOfImagesExceeded", maxQuantityOfImagesExceeded);

        if (result.hasErrors() || maxQuantityOfImagesExceeded) {
            Map<String, ?> commonAttrs = prepareCommonAttrsForSeriesInfo(series, currentUserId);
            model.addAllAttributes(commonAttrs);

            addSeriesSalesFormToModel(model);

            // don't try to re-display file upload field
            form.setImage(null);

            return "series/info";
        }

        seriesService.addImageToSeries(form, series.getId(), currentUserId);

        return redirectTo(Url.INFO_SERIES_PAGE, series.getId());
    }

    @PostMapping(path = Url.INFO_SERIES_PAGE, params = "action=ADD")
    public String addToCollection(@PathVariable("id") Integer seriesId,
            @AuthenticationPrincipal CustomUserDetails currentUserDetails, RedirectAttributes redirectAttributes,
            HttpServletResponse response) throws IOException {

        if (seriesId == null) {
            response.sendError(HttpServletResponse.SC_NOT_FOUND);
            return null;
        }

        boolean seriesExists = seriesService.isSeriesExist(seriesId);
        if (!seriesExists) {
            response.sendError(HttpServletResponse.SC_NOT_FOUND);
            return null;
        }

        Integer userId = currentUserDetails.getUserId();
        collectionService.addToCollection(userId, seriesId);

        redirectAttributes.addFlashAttribute("justAddedSeries", true);
        redirectAttributes.addFlashAttribute("justAddedSeriesId", seriesId);

        String collectionSlug = currentUserDetails.getUserCollectionSlug();
        return redirectTo(Url.INFO_COLLECTION_PAGE, collectionSlug);
    }

    @PostMapping(path = Url.INFO_SERIES_PAGE, params = "action=REMOVE")
    public String removeFromCollection(@PathVariable("id") Integer seriesId,
            @AuthenticationPrincipal CustomUserDetails currentUserDetails, RedirectAttributes redirectAttributes,
            HttpServletResponse response) throws IOException {

        if (seriesId == null) {
            response.sendError(HttpServletResponse.SC_NOT_FOUND);
            return null;
        }

        boolean seriesExists = seriesService.isSeriesExist(seriesId);
        if (!seriesExists) {
            response.sendError(HttpServletResponse.SC_NOT_FOUND);
            return null;
        }

        Integer userId = currentUserDetails.getUserId();
        collectionService.removeFromCollection(userId, seriesId);

        redirectAttributes.addFlashAttribute("justRemovedSeries", true);

        String collectionSlug = currentUserDetails.getUserCollectionSlug();
        return redirectTo(Url.INFO_COLLECTION_PAGE, collectionSlug);
    }

    @PostMapping(Url.ADD_SERIES_ASK_PAGE)
    public String processAskForm(@Valid AddSeriesSalesForm form, BindingResult result,
            @PathVariable("id") Integer seriesId, Model model, @CurrentUser Integer currentUserId,
            Locale userLocale, HttpServletResponse response) throws IOException {

        if (seriesId == null) {
            response.sendError(HttpServletResponse.SC_NOT_FOUND);
            return null;
        }

        String lang = LocaleUtils.getLanguageOrNull(userLocale);
        SeriesDto series = seriesService.findFullInfoById(seriesId, lang);
        if (series == null) {
            response.sendError(HttpServletResponse.SC_NOT_FOUND);
            return null;
        }

        boolean maxQuantityOfImagesExceeded = !isAdmin() && !isAllowedToAddingImages(series);
        model.addAttribute("maxQuantityOfImagesExceeded", maxQuantityOfImagesExceeded);

        if (result.hasErrors() || maxQuantityOfImagesExceeded) {
            Map<String, ?> commonAttrs = prepareCommonAttrsForSeriesInfo(series, currentUserId);
            model.addAllAttributes(commonAttrs);

            addSeriesSalesFormToModel(model);
            addImageFormToModel(model);

            return "series/info";
        }

        seriesSalesService.add(form, series.getId(), currentUserId);

        return redirectTo(Url.INFO_SERIES_PAGE, series.getId());
    }

    @GetMapping(Url.SEARCH_SERIES_BY_CATALOG)
    public String searchSeriesByCatalog(
            @RequestParam(name = "catalogNumber", defaultValue = "") String catalogNumber,
            @RequestParam(name = "catalogName", defaultValue = "") String catalogName, Model model,
            Locale userLocale, RedirectAttributes redirectAttributes) throws IOException {

        if (StringUtils.isBlank(catalogNumber)) {
            redirectAttributes.addFlashAttribute("numberIsEmpty", true);
            return "redirect:" + Url.INDEX_PAGE;
        }

        String lang = LocaleUtils.getLanguageOrNull(userLocale);
        List<SeriesInfoDto> series;
        switch (catalogName) {
        case "michel":
            series = seriesService.findByMichelNumber(catalogNumber, lang);
            break;
        case "scott":
            series = seriesService.findByScottNumber(catalogNumber, lang);
            break;
        case "yvert":
            series = seriesService.findByYvertNumber(catalogNumber, lang);
            break;
        case "gibbons":
            series = seriesService.findByGibbonsNumber(catalogNumber, lang);
            break;
        default:
            series = Collections.emptyList();
            break;
        }
        model.addAttribute("searchResults", series);

        return "series/search_result";
    }

    // CheckStyle: ignore LineLength for next 1 line
    private Map<String, ?> prepareCommonAttrsForSeriesInfo(SeriesDto series, Integer currentUserId) {
        Map<String, Object> model = new HashMap<>();

        model.put("series", series);

        String michelNumbers = CatalogUtils.toShortForm(series.getMichel().getNumbers());
        String scottNumbers = CatalogUtils.toShortForm(series.getScott().getNumbers());
        String yvertNumbers = CatalogUtils.toShortForm(series.getYvert().getNumbers());
        String gibbonsNumbers = CatalogUtils.toShortForm(series.getGibbons().getNumbers());
        model.put("michelNumbers", michelNumbers);
        model.put("scottNumbers", scottNumbers);
        model.put("yvertNumbers", yvertNumbers);
        model.put("gibbonsNumbers", gibbonsNumbers);

        boolean isSeriesInCollection = collectionService.isSeriesInCollection(currentUserId, series.getId());

        boolean userCanAddImagesToSeries = isUserCanAddImagesToSeries(series);

        model.put("isSeriesInCollection", isSeriesInCollection);
        model.put("allowAddingImages", userCanAddImagesToSeries);

        if (Features.SHOW_PURCHASES_AND_SALES.isActive()
                && SecurityContextUtils.hasAuthority(Authority.VIEW_SERIES_SALES)) {

            List<PurchaseAndSaleDto> purchasesAndSales = seriesService.findPurchasesAndSales(series.getId());
            model.put("purchasesAndSales", purchasesAndSales);
        }

        return model;
    }

    private void addSeriesSalesFormToModel(Model model) {
        if (!(Features.ADD_PURCHASES_AND_SALES.isActive()
                && SecurityContextUtils.hasAuthority(Authority.ADD_SERIES_SALES))) {
            return;
        }

        if (!model.containsAttribute("addSeriesSalesForm")) {
            AddSeriesSalesForm addSeriesSalesForm = new AddSeriesSalesForm();
            addSeriesSalesForm.setDate(new Date());
            model.addAttribute("addSeriesSalesForm", addSeriesSalesForm);
        }

        List<EntityWithIdDto> sellers = transactionParticipantService.findAllSellers();
        model.addAttribute("sellers", sellers);

        List<EntityWithIdDto> buyers = transactionParticipantService.findAllBuyers();
        model.addAttribute("buyers", buyers);
    }

    private static void addImageFormToModel(Model model) {
        AddImageForm form = new AddImageForm();
        model.addAttribute("addImageForm", form);
    }

    private static boolean isAllowedToAddingImages(SeriesDto series) {
        return series.getImageIds().size() <= series.getQuantity();
    }

    private static boolean isUserCanAddImagesToSeries(SeriesDto series) {
        return isAdmin() || isOwner(series) && isAllowedToAddingImages(series);
    }

    private static boolean isAdmin() {
        return SecurityContextUtils.hasAuthority(Authority.ADD_IMAGES_TO_SERIES);
    }

    @SuppressWarnings("PMD.UnusedNullCheckInEquals")
    private static boolean isOwner(SeriesDto series) {
        Integer userId = SecurityContextUtils.getUserId();
        return userId != null && Objects.equals(series.getCreatedBy(), userId);
    }

}