com.acc.controller.ProductsController.java Source code

Java tutorial

Introduction

Here is the source code for com.acc.controller.ProductsController.java

Source

/*
 * [y] hybris Platform
 *
 * Copyright (c) 2000-2013 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.acc.controller;

import de.hybris.platform.catalog.enums.ProductReferenceTypeEnum;
import de.hybris.platform.commercefacades.catalog.CatalogFacade;
import de.hybris.platform.commercefacades.product.ProductExportFacade;
import de.hybris.platform.commercefacades.product.ProductFacade;
import de.hybris.platform.commercefacades.product.ProductOption;
import de.hybris.platform.commercefacades.product.data.ProductData;
import de.hybris.platform.commercefacades.product.data.ProductReferenceData;
import de.hybris.platform.commercefacades.product.data.ProductReferencesData;
import de.hybris.platform.commercefacades.product.data.ProductResultData;
import de.hybris.platform.commercefacades.product.data.ReviewData;
import de.hybris.platform.commercefacades.product.data.SuggestionData;
import de.hybris.platform.commercefacades.search.ProductSearchFacade;
import de.hybris.platform.commercefacades.search.data.AutocompleteSuggestionData;
import de.hybris.platform.commercefacades.search.data.SearchStateData;
import de.hybris.platform.commercefacades.storefinder.StoreFinderStockFacade;
import de.hybris.platform.commercefacades.storefinder.data.StoreFinderStockSearchPageData;
import de.hybris.platform.commerceservices.search.facetdata.FacetSearchPageData;
import de.hybris.platform.commerceservices.search.facetdata.ProductSearchPageData;
import de.hybris.platform.commerceservices.search.pagedata.PageableData;
import de.hybris.platform.commerceservices.search.solrfacetsearch.data.SolrSearchQueryData;
import de.hybris.platform.commerceservices.store.data.GeoPoint;
import de.hybris.platform.converters.Populator;
import de.hybris.platform.servicelayer.dto.converter.Converter;
import de.hybris.platform.site.BaseSiteService;

import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Set;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;

import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.httpclient.util.DateParseException;
import org.apache.commons.lang.StringUtils;
import org.apache.log4j.Logger;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.access.annotation.Secured;
import org.springframework.stereotype.Controller;
import org.springframework.validation.BeanPropertyBindingResult;
import org.springframework.validation.Errors;
import org.springframework.validation.Validator;
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 com.acc.constants.YcommercewebservicesConstants;
import com.acc.expressupdate.data.ProductExpressUpdateElementData;
import com.acc.expressupdate.data.ProductExpressUpdateElementDataList;
import com.acc.expressupdate.impl.ProductExpressUpdateQueue;
import com.acc.formatters.WsDateFormatter;
import com.acc.product.data.ProductDataList;
import com.acc.product.data.SuggestionDataList;
import com.acc.util.ws.SearchQueryCodec;
import com.acc.validator.CustomValidationException;
import com.acc.validator.ReviewDataValidator;
import com.google.common.base.Splitter;
import com.google.common.collect.Lists;

/**
 * Web Services Controller to expose the functionality of the {@link ProductFacade} and SearchFacade.
 */
@Controller
@RequestMapping(value = "/v1/{baseSiteId}/products")
public class ProductsController extends BaseController {

    private static final String BASIC_OPTION = "BASIC";
    private static final String MAX_INTEGER = "2147483647";
    private static final String DEFAULT_PAGE_VALUE = "0";
    private static final int CATALOG_ID_POS = 0;
    private static final int CATALOG_VERSION_POS = 1;
    private static final Logger LOG = Logger.getLogger(ProductsController.class);
    @Resource
    StoreFinderStockFacade storeFinderStockFacade;
    @Resource(name = "cwsProductFacade")
    private ProductFacade productFacade;
    @Resource(name = "cwsProductExportFacade")
    private ProductExportFacade productExportFacade;
    @Resource(name = "cwsSearchQueryCodec")
    private SearchQueryCodec<SolrSearchQueryData> searchQueryCodec;
    @Resource(name = "wsDateFormatter")
    private WsDateFormatter wsDateFormatter;
    @Resource(name = "productSearchFacade")
    private ProductSearchFacade<ProductData> productSearchFacade;
    @Resource(name = "solrSearchStateConverter")
    private Converter<SolrSearchQueryData, SearchStateData> solrSearchStateConverter;
    @Resource(name = "httpRequestReviewDataPopulator")
    private Populator<HttpServletRequest, ReviewData> httpRequestReviewDataPopulator;
    @Resource(name = "reviewValidator")
    private Validator reviewValidator;
    @Resource(name = "productExpressUpdateQueue")
    private ProductExpressUpdateQueue productExpressUpdateQueue;
    @Resource(name = "catalogFacade")
    private CatalogFacade catalogFacade;
    @Autowired
    private BaseSiteService baseSiteService;

    //@Autowired
    //private ProductLocationService productLocationService;

    /**
     * Web service handler for search. Implementation has to catch up once the SearchFacade exists.
     * 
     * @param query
     *           serialized query in format: freeTextSearch:sort:facetKey1:facetValue1:facetKey2:facetValue2
     * @param currentPage
     *           the current result page requested
     * @param pageSize
     *           the number of results returned per page
     * @param sort
     *           sorting method applied to the display search results
     * @return {@link FacetSearchPageData}
     */
    @RequestMapping(method = RequestMethod.GET)
    @ResponseBody
    public ProductSearchPageData<SearchStateData, ProductData> searchProducts(
            @RequestParam(required = false) final String query,
            @RequestParam(required = false, defaultValue = DEFAULT_PAGE_VALUE) final int currentPage,
            @RequestParam(required = false, defaultValue = "20") final int pageSize,
            @RequestParam(required = false) final String sort) {
        baseSiteService.setCurrentBaseSite("electronics", true);
        final SolrSearchQueryData searchQueryData = searchQueryCodec.decodeQuery(query);
        final PageableData pageable = new PageableData();
        pageable.setCurrentPage(currentPage);
        pageable.setPageSize(pageSize);
        pageable.setSort(sort);

        return productSearchFacade.textSearch(solrSearchStateConverter.convert(searchQueryData), pageable);
    }

    /**
     * Web service handler for product export. If no 'options' query parameter is defined, it will assume BASIC. The
     * options are turned into a Set<ProductOption> and passed on to the facade. <br>
     * Sample Call: http://localhost:9001/rest/v1/{SITE}/products/export/full
     * 
     * @param currentPage
     *           - index position of the first Product, which will be included in the returned List
     * @param pageSize
     *           - number of Products which will be returned in each page
     * @param options
     *           - a String enumerating the detail level, values are BASIC, PROMOTIONS, STOCK, REVIEW, CLASSIFICATION,
     *           REFERENCES. Combine by using a ',', which needs to be encoded as part of a URI using URLEncoding: %2C
     * @return {@link ProductDataList}
     */
    @Secured("ROLE_TRUSTED_CLIENT")
    @RequestMapping(value = "/export/full", method = RequestMethod.GET)
    @ResponseBody
    public ProductDataList exportProducts(
            @RequestParam(required = false, defaultValue = DEFAULT_PAGE_VALUE) final int currentPage,
            @RequestParam(required = false, defaultValue = MAX_INTEGER) final int pageSize,
            @RequestParam(required = false, defaultValue = BASIC_OPTION) final String options,
            @RequestParam(required = false) final String catalog,
            @RequestParam(required = false) final String version) {
        final Set<ProductOption> opts = extractOptions(options);

        final ProductResultData products = productExportFacade.getAllProductsForOptions(catalog, version, opts,
                currentPage, pageSize);

        //addUrlsToResult(catalog, version, products);
        final ProductDataList result = convertResultset(currentPage, pageSize, catalog, version, products);

        return result;
    }

    /**
     * Web service handler for incremental product export. Timestamp specifies which product to export. If no 'options'
     * query parameter is defined, it will assume BASIC. The options are turned into a Set<ProductOption> and passed on
     * to the facade. <br>
     * Sample Call: http://localhost:9001/rest/v1/{SITE}/products/export/incremental
     * 
     * @param currentPage
     *           - index position of the first Product, which will be included in the returned List
     * @param pageSize
     *           - number of Products which will be returned in each page
     * @param options
     *           - a String enumerating the detail level, values are BASIC, PROMOTIONS, STOCK, REVIEW, CLASSIFICATION,
     *           REFERENCES. Combine by using a ',', which needs to be encoded as part of a URI using URLEncoding: %2C
     * @param catalog
     *           catalog from which get products
     * @param version
     *           version of catalog
     * @param timestamp
     *           time in ISO-8601 format
     * @return {@link ProductDataList}
     */
    @Secured("ROLE_TRUSTED_CLIENT")
    @RequestMapping(value = "/export/incremental", method = RequestMethod.GET)
    @ResponseBody
    public ProductDataList exportProducts(
            @RequestParam(required = false, defaultValue = DEFAULT_PAGE_VALUE) final int currentPage,
            @RequestParam(required = false, defaultValue = MAX_INTEGER) final int pageSize,
            @RequestParam(required = false, defaultValue = BASIC_OPTION) final String options,
            @RequestParam(required = false) final String catalog,
            @RequestParam(required = false) final String version, @RequestParam final String timestamp)
            throws DateParseException {
        final Set<ProductOption> opts = extractOptions(options);
        final Date timestampDate = wsDateFormatter.toDate(timestamp);

        final ProductResultData modifiedProducts = productExportFacade.getOnlyModifiedProductsForOptions(catalog,
                version, timestampDate, opts, currentPage, pageSize);

        return convertResultset(currentPage, pageSize, catalog, version, modifiedProducts);
    }

    /**
     * Web service handler for export product references. Reference type specifies which references to return. If no
     * 'options' query parameter is defined, it will assume BASIC. The options are turned into a Set<ProductOption> and
     * passed on to the facade. Sample Call:
     * http://localhost:9001/rest/v1/{SITE}/products/export/references/{code}?referenceType
     * =CROSSELLING&catalog=hwcatalog&version=Online
     * 
     * @param code
     *           - product code
     * @param referenceType
     *           - reference type according to enum ProductReferenceTypeEnum
     * @param pageSize
     *           - number of Products which will be returned in each page
     * @param options
     *           - a String enumerating the detail level, values are BASIC, PROMOTIONS, STOCK, REVIEW, CLASSIFICATION,
     *           REFERENCES. Combine by using a ',', which needs to be encoded as part of a URI using URLEncoding: %2C
     * @return collection of {@link ProductReferenceData}
     */
    @Secured("ROLE_TRUSTED_CLIENT")
    @RequestMapping(value = "/export/references/{code}", method = RequestMethod.GET)
    @ResponseBody
    public ProductReferencesData exportProductReferences(@PathVariable final String code,
            @RequestParam(required = false, defaultValue = MAX_INTEGER) final int pageSize,
            @RequestParam(required = false, defaultValue = BASIC_OPTION) final String options,
            @RequestParam final String referenceType) throws DateParseException {
        final List<ProductOption> opts = Lists.newArrayList(extractOptions(options));
        final ProductReferenceTypeEnum referenceTypeEnum = ProductReferenceTypeEnum.valueOf(referenceType);

        final List<ProductReferenceData> productReferences = productFacade.getProductReferencesForCode(code,
                referenceTypeEnum, opts, Integer.valueOf(pageSize));

        final ProductReferencesData productReferencesData = new ProductReferencesData();
        productReferencesData.setReferences(productReferences);

        return productReferencesData;
    }

    /**
     * Web service handler for product express update. Returns only elements newer than timestamp. Sample Call:
     * http://localhost:9001/rest/v1/{SITE}/products/expressUpdate<br>
     * This method requires trusted client authentication.<br>
     * Method type : <code>GET</code>.<br>
     * Method is restricted for <code>HTTPS</code> channel.
     * 
     * @param timestamp
     *           - time in ISO-8601 format
     * @param catalog
     *           - the product catalog to return queue for. If not set all products from all catalogs in queue will be
     *           returned. Format: catalogId:catalogVersion
     * @return {@link ProductExpressUpdateElementDataList}
     */
    @Secured("ROLE_TRUSTED_CLIENT")
    @RequestMapping(value = "/expressUpdate", method = RequestMethod.GET)
    @ResponseBody
    public ProductExpressUpdateElementDataList expressUpdate(@RequestParam final String timestamp,
            @RequestParam(required = false) final String catalog) {
        final Date timestampDate = wsDateFormatter.toDate(timestamp);
        final ProductExpressUpdateElementDataList productExpressUpdateDataList = new ProductExpressUpdateElementDataList();
        productExpressUpdateDataList
                .setProductExpressUpdateElements(productExpressUpdateQueue.getItems(timestampDate));
        filterExpressUpdateQueue(productExpressUpdateDataList, validateAndSplitCatalog(catalog));
        return productExpressUpdateDataList;
    }

    private void filterExpressUpdateQueue(final ProductExpressUpdateElementDataList productExpressUpdateDataList,
            final List<String> catalogInfo) {
        if (catalogInfo.size() == 2 && StringUtils.isNotEmpty(catalogInfo.get(CATALOG_ID_POS))
                && StringUtils.isNotEmpty(catalogInfo.get(CATALOG_VERSION_POS))
                && CollectionUtils.isNotEmpty(productExpressUpdateDataList.getProductExpressUpdateElements())) {
            final Iterator<ProductExpressUpdateElementData> dataIterator = productExpressUpdateDataList
                    .getProductExpressUpdateElements().iterator();
            while (dataIterator.hasNext()) {
                final ProductExpressUpdateElementData productExpressUpdateElementData = dataIterator.next();
                if (!catalogInfo.get(CATALOG_ID_POS).equals(productExpressUpdateElementData.getCatalogId())
                        || !catalogInfo.get(CATALOG_VERSION_POS)
                                .equals(productExpressUpdateElementData.getCatalogVersion())) {
                    dataIterator.remove();
                }
            }
        }
    }

    protected List<String> validateAndSplitCatalog(final String catalog) throws IllegalArgumentException {
        final List<String> catalogInfo = new ArrayList<>();
        if (StringUtils.isNotEmpty(catalog)) {
            catalogInfo
                    .addAll(Lists.newArrayList(Splitter.on(':').trimResults().omitEmptyStrings().split(catalog)));
            if (catalogInfo.size() == 2) {
                catalogFacade.getProductCatalogVersionForTheCurrentSite(catalogInfo.get(CATALOG_ID_POS),
                        catalogInfo.get(CATALOG_VERSION_POS), Collections.EMPTY_SET);
            } else if (!catalogInfo.isEmpty()) {
                throw new IllegalArgumentException(
                        "You have to provide both catalog and catalogVersion parameters or none of them.");
            }
        }
        return catalogInfo;
    }

    private ProductDataList convertResultset(final int page, final int pageSize, final String catalog,
            final String version, final ProductResultData modifiedProducts) {
        final ProductDataList result = new ProductDataList();
        result.setProducts(modifiedProducts.getProducts());
        if (pageSize > 0) {
            result.setTotalPageCount(
                    (modifiedProducts.getTotalCount() % pageSize == 0) ? modifiedProducts.getTotalCount() / pageSize
                            : modifiedProducts.getTotalCount() / pageSize + 1);
        }
        result.setCurrentPage(page);
        result.setTotalProductCount(modifiedProducts.getTotalCount());
        result.setCatalog(catalog);
        result.setVersion(version);
        return result;
    }

    protected Set<ProductOption> extractOptions(final String options) {
        final String optionsStrings[] = options.split(YcommercewebservicesConstants.OPTIONS_SEPARATOR);

        final Set<ProductOption> opts = new HashSet<ProductOption>();
        for (final String option : optionsStrings) {
            opts.add(ProductOption.valueOf(option));
        }
        return opts;
    }

    /**
     * Web service handler for the getProductByCode call. If no 'options' query parameter is defined, it will assume
     * BASIC. The options are turned into a Set<ProductOption> and passed on to the facade. Sample Call:
     * http://localhost:9001/rest/v1/{SITE}/products/{CODE}?options=BASIC%2CPROMOTIONS Keep in mind ',' needs to be
     * encoded as %2C
     * 
     * @param code
     *           - the unique code used to identify a product
     * @param options
     *           - a String enumerating the detail level, values are BASIC, PROMOTIONS, STOCK, REVIEW, CLASSIFICATION,
     *           REFERENCES. Combine by using a ',', which needs to be encoded as part of a URI using URLEncoding: %2C
     * @return the ProdcutData DTO which will be marshaled to JSON or XML based on Accept-Header
     */
    @RequestMapping(value = "/{code}", method = RequestMethod.GET)
    @ResponseBody
    public ProductData getProductByCode(@PathVariable final String code,
            @RequestParam(required = false, defaultValue = BASIC_OPTION) final String options) {

        LOG.info("inside productcontroller**********");
        if (LOG.isDebugEnabled()) {
            LOG.debug("getProductByCode: code=" + code + " | options=" + options);
        }

        final Set<ProductOption> opts = extractOptions(options);
        LOG.info("inside productcontroller**********" + opts);

        return productFacade.getProductForCodeAndOptions(code, opts);
        //return productLocationService.getProductForCodeAndOptions(code, opts);

    }

    /**
     * Web service handler for giving the auto complete suggestions as List<String>
     * 
     * @param term
     *           - the term that user inputs for search
     * @param max
     *           - the limit of the suggestions
     * @return the list of auto suggestions
     */
    @RequestMapping(value = "/suggest", method = RequestMethod.GET)
    @ResponseBody
    public SuggestionDataList getSuggestions(@RequestParam(required = true, defaultValue = " ") final String term,
            @RequestParam(required = true, defaultValue = "10") final int max) {
        final List<SuggestionData> suggestions = new ArrayList<SuggestionData>();
        final List<AutocompleteSuggestionData> autoSuggestions;
        if (max < productSearchFacade.getAutocompleteSuggestions(term).size()) {
            autoSuggestions = productSearchFacade.getAutocompleteSuggestions(term).subList(0, max);
        } else {
            autoSuggestions = productSearchFacade.getAutocompleteSuggestions(term);
        }
        for (final AutocompleteSuggestionData autoSuggestion : autoSuggestions) {
            final SuggestionData suggestionData = new SuggestionData();
            suggestionData.setValue(autoSuggestion.getTerm());
            suggestions.add(suggestionData);
        }
        final SuggestionDataList suggestionDataList = new SuggestionDataList();
        suggestionDataList.setSuggestions(suggestions);
        return suggestionDataList;
    }

    /**
     * Web service handler for the postReview call. Review will be posted as anonymous principal. Method uses
     * {@link com.acc.populator.HttpRequestReviewDataPopulator} to populate review data from request parameters.
     * <p/>
     * There is no default validation for the posted value!
     * <p/>
     * Request Method: <code>POST<code>
     * Sample Call: http://localhost:9001/rest/v1/{SITE}/products/{CODE}/review
     * Request parameters:
     * <ul>
     * <li>rating (required)</li>
     * <li>headline</li>
     * <li>comment</li>
     * <li>alias</li>
     * </ul>
     * 
     * @param code
     *           - the unique code used to identify a product
     * @param request
     * @return the ReviewData DTO which will be marshaled to JSON or XML based on Accept-Header
     */
    @RequestMapping(value = "/{code}/reviews", method = RequestMethod.POST)
    @ResponseBody
    public ReviewData createReview(@PathVariable final String code, final HttpServletRequest request)
            throws CustomValidationException {
        final ReviewData reviewData = new ReviewData();
        httpRequestReviewDataPopulator.populate(request, reviewData);
        final Errors errors = new BeanPropertyBindingResult(reviewData, "reviewData");

        reviewValidator.validate(reviewData, errors);

        if (errors.hasErrors()) {
            throw new CustomValidationException("Review validation error", errors);
        }

        return productFacade.postReview(code, reviewData);
    }

    /**
     * Web service handler for searching product's stock level sorted by distance from specific location passed by the
     * free-text parameter. Sample Call: http://localhost:9001/rest/v1/{SITE}/products/{CODE}/nearLocation
     * 
     * @param code
     *           - the unique code used to identify a product
     * @param location
     *           - free-text location
     * @return the StoreFinderStockSearchPageData of ProductData objects sorted by distance from location ascending
     */
    @RequestMapping(value = "/{code}/nearLocation", method = RequestMethod.GET)
    @ResponseBody
    public StoreFinderStockSearchPageData<ProductData> searchProductStockByLocation(@PathVariable final String code,
            @RequestParam(required = true) final String location,
            @RequestParam(required = false, defaultValue = DEFAULT_PAGE_VALUE) final int currentPage,
            @RequestParam(required = false, defaultValue = MAX_INTEGER) final int pageSize) {
        if (LOG.isDebugEnabled()) {
            LOG.debug("getProductStockByLocation: code=" + code + " | location=" + location);
        }

        final Set<ProductOption> opts = extractOptions(BASIC_OPTION);

        return this.storeFinderStockFacade.productSearch(location,
                productFacade.getProductForCodeAndOptions(code, opts), createPageableData(currentPage, pageSize));

    }

    /**
     * Web service handler for searching product's stock level sorted by distance from specific location passed by the
     * free-text parameter. Sample Call: http://localhost:9001/rest/v1/{SITE}/products/{CODE}/nearLatLong
     * 
     * @param code
     *           - the unique code used to identify a product
     * @param latitude
     *           - location's latitude
     * @param longitude
     *           - location's longitude
     * @return the StoreFinderStockSearchPageData of ProductData objects sorted by distance from location ascending
     */
    @RequestMapping(value = "/{code}/nearLatLong", method = RequestMethod.GET)
    @ResponseBody
    public StoreFinderStockSearchPageData<ProductData> searchProductStockByLocationGeoCode(
            @PathVariable final String code, @RequestParam(required = true) final Double latitude,
            @RequestParam(required = true) final Double longitude,
            @RequestParam(required = false, defaultValue = DEFAULT_PAGE_VALUE) final int currentPage,
            @RequestParam(required = false, defaultValue = MAX_INTEGER) final int pageSize) {
        if (LOG.isDebugEnabled()) {
            LOG.debug("getProductStockByLocationGeoCode: code=" + code + " | latitude=" + latitude + " | longitude="
                    + longitude);
        }
        final Set<ProductOption> opts = extractOptions(BASIC_OPTION);

        return this.storeFinderStockFacade.productSearch(createGeoPoint(latitude, longitude),
                productFacade.getProductForCodeAndOptions(code, opts), createPageableData(currentPage, pageSize));
    }

    private PageableData createPageableData(final int currentPage, final int pageSize) {
        final PageableData pageable = new PageableData();

        pageable.setCurrentPage(currentPage);
        pageable.setPageSize(pageSize);
        return pageable;
    }

    private GeoPoint createGeoPoint(final Double latitude, final Double longitude) {
        final GeoPoint point = new GeoPoint();
        point.setLatitude(latitude);
        point.setLongitude(longitude);

        return point;
    }

    public void setProductFacade(final ProductFacade productFacade) {
        this.productFacade = productFacade;
    }

    public void setSearchQueryCodec(final SearchQueryCodec<SolrSearchQueryData> searchQueryCodec) {
        this.searchQueryCodec = searchQueryCodec;
    }

    public void setProductExportFacade(final ProductExportFacade productExportFacade) {
        this.productExportFacade = productExportFacade;
    }

    public void setProductSearchFacade(final ProductSearchFacade<ProductData> productSearchFacade) {
        this.productSearchFacade = productSearchFacade;
    }

    public void setWsDateFormatter(final WsDateFormatter wsDateFormatter) {
        this.wsDateFormatter = wsDateFormatter;
    }

    public void setReviewValidator(final ReviewDataValidator reviewValidator) {
        this.reviewValidator = reviewValidator;
    }

    public void setCatalogFacade(final CatalogFacade catalogFacade) {
        this.catalogFacade = catalogFacade;
    }
}