com.mitre.storefront.controllers.pages.ProductPageController.java Source code

Java tutorial

Introduction

Here is the source code for com.mitre.storefront.controllers.pages.ProductPageController.java

Source

/*
 * [y] hybris Platform
 *
 * Copyright (c) 2000-2015 hybris AG
 * All rights reserved.
 *
 * This software is the confidential and proprietary information of hybris
 * ("Confidential Information"). You shall not disclose such Confidential
 * Information and shall use it only in accordance with the terms of the
 * license agreement you entered into with hybris.
 *
 *
 */
package com.mitre.storefront.controllers.pages;

import de.hybris.platform.acceleratorservices.controllers.page.PageType;
import de.hybris.platform.b2bacceleratorfacades.futurestock.B2BFutureStockFacade;
import de.hybris.platform.cms2.exceptions.CMSItemNotFoundException;
import de.hybris.platform.cms2.model.pages.AbstractPageModel;
import de.hybris.platform.commercefacades.product.ProductFacade;
import de.hybris.platform.commercefacades.product.ProductOption;
import de.hybris.platform.commercefacades.product.data.BaseOptionData;
import de.hybris.platform.commercefacades.product.data.FutureStockData;
import de.hybris.platform.commercefacades.product.data.ImageData;
import de.hybris.platform.commercefacades.product.data.ImageDataType;
import de.hybris.platform.commercefacades.product.data.ProductData;
import de.hybris.platform.commercefacades.product.data.ReviewData;
import de.hybris.platform.commerceservices.url.UrlResolver;
import de.hybris.platform.servicelayer.exceptions.UnknownIdentifierException;
import de.hybris.platform.util.Config;
import com.mitre.storefront.breadcrumb.impl.ProductBreadcrumbBuilder;
import com.mitre.storefront.constants.WebConstants;
import com.mitre.storefront.controllers.ControllerConstants;
import com.mitre.storefront.controllers.util.GlobalMessages;
import com.mitre.storefront.forms.FutureStockForm;
import com.mitre.storefront.forms.ReviewForm;
import com.mitre.storefront.util.XSSFilterUtil;
import com.mitre.storefront.variants.VariantSortStrategy;

import java.io.UnsupportedEncodingException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.validation.Valid;

import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang.StringUtils;
import org.apache.log4j.Logger;
import org.springframework.context.annotation.Scope;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.PathVariable;
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.ResponseBody;
import org.springframework.web.servlet.mvc.support.RedirectAttributes;

import com.google.common.collect.Maps;

/**
 * Controller for product details page.
 */
@Controller
@Scope("tenant")
@RequestMapping(value = "/**/p")
public class ProductPageController extends AbstractPageController {
    @SuppressWarnings("unused")
    private static final Logger LOG = Logger.getLogger(ProductPageController.class);

    /**
     * We use this suffix pattern because of an issue with Spring 3.1 where a Uri value is incorrectly extracted if it
     * contains on or more '.' characters. Please see https://jira.springsource.org/browse/SPR-6164 for a discussion on
     * the issue and future resolution.
     */
    private static final String PRODUCT_CODE_PATH_VARIABLE_PATTERN = "/{productCode:.*}";
    private static final String REVIEWS_PATH_VARIABLE_PATTERN = "{numberOfReviews:.*}";

    private static final String FUTURE_STOCK_ENABLED = "storefront.products.futurestock.enabled";
    private static final String STOCK_SERVICE_UNAVAILABLE = "basket.page.viewFuture.unavailable";
    private static final String NOT_MULTISKU_ITEM_ERROR = "basket.page.viewFuture.not.multisku";

    @Resource(name = "productDataUrlResolver")
    private UrlResolver<ProductData> productDataUrlResolver;

    @Resource(name = "b2bProductFacade")
    private ProductFacade productFacade;

    @Resource(name = "productBreadcrumbBuilder")
    private ProductBreadcrumbBuilder productBreadcrumbBuilder;

    @Resource(name = "variantSortStrategy")
    private VariantSortStrategy variantSortStrategy;

    @Resource(name = "b2bFutureStockFacade")
    private B2BFutureStockFacade b2bFutureStockFacade;

    @RequestMapping(value = PRODUCT_CODE_PATH_VARIABLE_PATTERN, method = RequestMethod.GET)
    public String productDetail(@PathVariable("productCode") final String productCode, final Model model,
            final HttpServletRequest request, final HttpServletResponse response)
            throws CMSItemNotFoundException, UnsupportedEncodingException {
        final ProductData productData = productFacade.getProductForCodeAndOptions(productCode, null);

        final String redirection = checkRequestUrl(request, response, productDataUrlResolver.resolve(productData));
        if (StringUtils.isNotEmpty(redirection)) {
            return redirection;
        }

        updatePageTitle(productData, model);
        final List<ProductOption> extraOptions = Arrays.asList(ProductOption.VARIANT_MATRIX_BASE,
                ProductOption.VARIANT_MATRIX_URL, ProductOption.VARIANT_MATRIX_MEDIA);
        populateProductDetailForDisplay(productCode, model, request, extraOptions);
        model.addAttribute(new ReviewForm());
        model.addAttribute("pageType", PageType.PRODUCT.name());
        model.addAttribute("futureStockEnabled", Boolean.valueOf(Config.getBoolean(FUTURE_STOCK_ENABLED, false)));

        return getViewForPage(model);
    }

    @RequestMapping(value = PRODUCT_CODE_PATH_VARIABLE_PATTERN + "/orderForm", method = RequestMethod.GET)
    public String productOrderForm(@PathVariable("productCode") final String productCode, final Model model,
            final HttpServletRequest request, final HttpServletResponse response) throws CMSItemNotFoundException {
        final List<ProductOption> extraOptions = Arrays.asList(ProductOption.VARIANT_MATRIX_BASE,
                ProductOption.VARIANT_MATRIX_PRICE, ProductOption.VARIANT_MATRIX_MEDIA,
                ProductOption.VARIANT_MATRIX_STOCK);

        final ProductData productData = productFacade.getProductForCodeAndOptions(productCode, extraOptions);
        updatePageTitle(productData, model);

        populateProductDetailForDisplay(productCode, model, request, extraOptions);

        if (!model.containsAttribute(WebConstants.MULTI_DIMENSIONAL_PRODUCT)) {
            return REDIRECT_PREFIX + productDataUrlResolver.resolve(productData);
        }

        return ControllerConstants.Views.Pages.Product.OrderForm;
    }

    @RequestMapping(value = PRODUCT_CODE_PATH_VARIABLE_PATTERN + "/futureStock", method = RequestMethod.GET)
    public String productFutureStock(@PathVariable("productCode") final String productCode, final Model model,
            final HttpServletRequest request, final HttpServletResponse response) throws CMSItemNotFoundException {
        final boolean futureStockEnabled = Config.getBoolean(FUTURE_STOCK_ENABLED, false);
        if (futureStockEnabled) {
            final List<FutureStockData> futureStockList = b2bFutureStockFacade.getFutureAvailability(productCode);
            if (futureStockList == null) {
                GlobalMessages.addErrorMessage(model, STOCK_SERVICE_UNAVAILABLE);
            } else if (futureStockList.isEmpty()) {
                GlobalMessages.addErrorMessage(model, "product.product.details.future.nostock");
            }
            populateProductDetailForDisplay(productCode, model, request, Collections.EMPTY_LIST);
            model.addAttribute("futureStocks", futureStockList);

            return ControllerConstants.Views.Fragments.Product.FutureStockPopup;
        } else {
            return ControllerConstants.Views.Pages.Error.ErrorNotFoundPage;
        }

    }

    @ResponseBody
    @RequestMapping(value = PRODUCT_CODE_PATH_VARIABLE_PATTERN + "/grid/skusFutureStock", method = {
            RequestMethod.POST }, produces = MediaType.APPLICATION_JSON_VALUE)
    public final Map<String, Object> productSkusFutureStock(final FutureStockForm form, final Model model) {
        final String productCode = form.getProductCode();
        final List<String> skus = form.getSkus();
        final boolean futureStockEnabled = Config.getBoolean(FUTURE_STOCK_ENABLED, false);

        Map<String, Object> result = new HashMap<>();
        if (futureStockEnabled && CollectionUtils.isNotEmpty(skus) && StringUtils.isNotBlank(productCode)) {
            final Map<String, List<FutureStockData>> futureStockData = b2bFutureStockFacade
                    .getFutureAvailabilityForSelectedVariants(productCode, skus);

            if (futureStockData == null) {
                // future availability service is down, we show this to the user
                result = Maps.newHashMap();
                final String errorMessage = getMessageSource().getMessage(NOT_MULTISKU_ITEM_ERROR, null,
                        getI18nService().getCurrentLocale());
                result.put(NOT_MULTISKU_ITEM_ERROR, errorMessage);
            } else {
                for (final Entry<String, List<FutureStockData>> entry : futureStockData.entrySet()) {
                    result.put(entry.getKey(), entry.getValue());
                }
            }
        }
        return result;
    }

    @RequestMapping(value = PRODUCT_CODE_PATH_VARIABLE_PATTERN + "/zoomImages", method = RequestMethod.GET)
    public String showZoomImages(@PathVariable("productCode") final String productCode,
            @RequestParam(value = "galleryPosition", required = false) final String galleryPosition,
            final Model model) {

        final ProductData productData = productFacade.getProductForCodeAndOptions(productCode,
                Collections.singleton(ProductOption.GALLERY));
        final List<Map<String, ImageData>> images = getGalleryImages(productData);
        model.addAttribute("galleryImages", images);
        model.addAttribute("product", productData);
        if (galleryPosition != null) {
            try {
                model.addAttribute("zoomImageUrl",
                        images.get(Integer.parseInt(galleryPosition)).get("zoom").getUrl());
            } catch (final IndexOutOfBoundsException ignore) {
                model.addAttribute("zoomImageUrl", "");
            } catch (final NumberFormatException ignore) {
                model.addAttribute("zoomImageUrl", "");
            }
        }
        return ControllerConstants.Views.Fragments.Product.ZoomImagesPopup;
    }

    @RequestMapping(value = PRODUCT_CODE_PATH_VARIABLE_PATTERN + "/quickView", method = RequestMethod.GET)
    public String showQuickView(@PathVariable("productCode") final String productCode, final Model model,
            final HttpServletRequest request) {

        final ProductData productData = productFacade.getProductForCodeAndOptions(productCode,
                Arrays.asList(ProductOption.BASIC, ProductOption.PRICE, ProductOption.SUMMARY,
                        ProductOption.DESCRIPTION, ProductOption.CATEGORIES, ProductOption.PROMOTIONS,
                        ProductOption.STOCK, ProductOption.REVIEW, ProductOption.VOLUME_PRICES));

        populateProductData(productData, model, request);

        return ControllerConstants.Views.Fragments.Product.QuickViewPopup;
    }

    @RequestMapping(value = PRODUCT_CODE_PATH_VARIABLE_PATTERN + "/review", method = RequestMethod.POST)
    public String postReview(@PathVariable final String productCode, @Valid final ReviewForm form,
            final BindingResult result, final Model model, final HttpServletRequest request,
            final RedirectAttributes redirectAttrs) throws CMSItemNotFoundException {
        final ProductData productData = productFacade.getProductForCodeAndOptions(productCode, null);

        if (result.hasErrors()) {
            updatePageTitle(productData, model);
            GlobalMessages.addErrorMessage(model, "review.general.error");
            model.addAttribute("showReviewForm", Boolean.TRUE);
            populateProductDetailForDisplay(productCode, model, request, Collections.EMPTY_LIST);
            storeCmsPageInModel(model, getPageForProduct(productCode));
            return getViewForPage(model);
        }

        final ReviewData review = new ReviewData();
        review.setHeadline(XSSFilterUtil.filter(form.getHeadline()));
        review.setComment(XSSFilterUtil.filter(form.getComment()));
        review.setRating(form.getRating());
        review.setAlias(XSSFilterUtil.filter(form.getAlias()));
        productFacade.postReview(productCode, review);
        GlobalMessages.addFlashMessage(redirectAttrs, GlobalMessages.CONF_MESSAGES_HOLDER,
                "review.confirmation.thank.you.title");
        return "redirect:" + productDataUrlResolver.resolve(productData);
    }

    @RequestMapping(value = PRODUCT_CODE_PATH_VARIABLE_PATTERN + "/reviewhtml/"
            + REVIEWS_PATH_VARIABLE_PATTERN, method = RequestMethod.GET)
    public String reviewHtml(@PathVariable("productCode") final String productCode,
            @PathVariable("numberOfReviews") final String numberOfReviews, final Model model,
            final HttpServletRequest request) {
        final ProductData productData = productFacade.getProductForCodeAndOptions(productCode, null);

        final List<ReviewData> reviews;

        if ("all".equals(numberOfReviews)) {
            reviews = productFacade.getReviews(productCode);
        } else {
            reviews = productFacade.getReviews(productCode, Integer.valueOf(numberOfReviews));
        }

        model.addAttribute("reviews", reviews);
        model.addAttribute("reviewsTotal", productData.getNumberOfReviews());
        model.addAttribute(new ReviewForm());

        return ControllerConstants.Views.Fragments.Product.ReviewsTab;
    }

    protected void updatePageTitle(final ProductData productData, final Model model) {
        storeContentPageTitleInModel(model, getPageTitleResolver().resolveProductPageTitle(productData.getCode()));
    }

    @ExceptionHandler(UnknownIdentifierException.class)
    public String handleUnknownIdentifierException(final UnknownIdentifierException exception,
            final HttpServletRequest request) {
        request.setAttribute("message", exception.getMessage());
        return FORWARD_PREFIX + "/404";
    }

    //where the example call is
    protected void populateProductDetailForDisplay(final String productCode, final Model model,
            final HttpServletRequest request, final List<ProductOption> extraOptions)
            throws CMSItemNotFoundException {

        final List<ProductOption> options = new ArrayList<>(Arrays.asList(ProductOption.VARIANT_FIRST_VARIANT,
                ProductOption.BASIC, ProductOption.URL, ProductOption.PRICE, ProductOption.SUMMARY,
                ProductOption.DESCRIPTION, ProductOption.GALLERY, ProductOption.CATEGORIES, ProductOption.REVIEW,
                ProductOption.PROMOTIONS, ProductOption.CLASSIFICATION, ProductOption.VARIANT_FULL,
                ProductOption.STOCK, ProductOption.VOLUME_PRICES, ProductOption.PRICE_RANGE));

        options.addAll(extraOptions);
        final ProductData productData = productFacade.getProductForCodeAndOptions(productCode, options);

        sortVariantOptionData(productData);
        storeCmsPageInModel(model, getPageForProduct(productCode));
        populateProductData(productData, model, request);
        final ProductData baseProductData = getBaseProduct(productData, options);
        model.addAttribute(WebConstants.BREADCRUMBS_KEY, productBreadcrumbBuilder.getBreadcrumbs(baseProductData));
        if (CollectionUtils.isNotEmpty(productData.getVariantMatrix())) {
            model.addAttribute(WebConstants.MULTI_DIMENSIONAL_PRODUCT,
                    Boolean.valueOf(CollectionUtils.isNotEmpty(productData.getVariantMatrix())));
        }
    }

    protected ProductData getBaseProduct(final ProductData product, final List<ProductOption> options) {
        final String baseProductCode = product.getBaseProduct();
        if (StringUtils.isNotBlank(baseProductCode)) {
            return productFacade.getProductForCodeAndOptions(baseProductCode, options);
        }
        return product;
    }

    protected void populateProductData(final ProductData productData, final Model model,
            final HttpServletRequest request) {
        model.addAttribute("galleryImages", getGalleryImages(productData));
        model.addAttribute("product", productData);
    }

    protected void sortVariantOptionData(final ProductData productData) {
        if (CollectionUtils.isNotEmpty(productData.getBaseOptions())) {
            for (final BaseOptionData baseOptionData : productData.getBaseOptions()) {
                if (CollectionUtils.isNotEmpty(baseOptionData.getOptions())) {
                    Collections.sort(baseOptionData.getOptions(), variantSortStrategy);
                }
            }
        }

        if (CollectionUtils.isNotEmpty(productData.getVariantOptions())) {
            Collections.sort(productData.getVariantOptions(), variantSortStrategy);
        }
    }

    protected AbstractPageModel getPageForProduct(final String productCode) throws CMSItemNotFoundException {
        return getCmsPageService().getPageForProductCode(productCode);
    }

    protected List<Map<String, ImageData>> getGalleryImages(final ProductData productData) {
        final List<Map<String, ImageData>> galleryImages = new ArrayList<Map<String, ImageData>>();
        if (CollectionUtils.isNotEmpty(productData.getImages())) {
            final List<ImageData> images = new ArrayList<ImageData>();
            for (final ImageData image : productData.getImages()) {
                if (ImageDataType.GALLERY.equals(image.getImageType())) {
                    images.add(image);
                }
            }
            Collections.sort(images, new Comparator<ImageData>() {
                @Override
                public int compare(final ImageData image1, final ImageData image2) {
                    return image1.getGalleryIndex().compareTo(image2.getGalleryIndex());
                }
            });

            if (CollectionUtils.isNotEmpty(images)) {
                int currentIndex = images.get(0).getGalleryIndex().intValue();
                Map<String, ImageData> formats = new HashMap<String, ImageData>();
                for (final ImageData image : images) {
                    if (currentIndex != image.getGalleryIndex().intValue()) {
                        galleryImages.add(formats);
                        formats = new HashMap<String, ImageData>();
                        currentIndex = image.getGalleryIndex().intValue();
                    }
                    formats.put(image.getFormat(), image);
                }
                if (!formats.isEmpty()) {
                    galleryImages.add(formats);
                }
            }
        }
        return galleryImages;
    }

}