Package org.broadleafcommerce.core.search.service.solr

Source Code of org.broadleafcommerce.core.search.service.solr.SolrSearchServiceImpl

/*
* #%L
* BroadleafCommerce Framework
* %%
* Copyright (C) 2009 - 2013 Broadleaf Commerce
* %%
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
*       http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* #L%
*/
package org.broadleafcommerce.core.search.service.solr;

import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.collections.MapUtils;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.solr.client.solrj.SolrQuery;
import org.apache.solr.client.solrj.SolrQuery.ORDER;
import org.apache.solr.client.solrj.SolrServer;
import org.apache.solr.client.solrj.SolrServerException;
import org.apache.solr.client.solrj.embedded.EmbeddedSolrServer;
import org.apache.solr.client.solrj.response.FacetField;
import org.apache.solr.client.solrj.response.FacetField.Count;
import org.apache.solr.client.solrj.response.Group;
import org.apache.solr.client.solrj.response.GroupCommand;
import org.apache.solr.client.solrj.response.GroupResponse;
import org.apache.solr.client.solrj.response.QueryResponse;
import org.apache.solr.common.SolrDocument;
import org.apache.solr.core.CoreContainer;
import org.broadleafcommerce.common.exception.ServiceException;
import org.broadleafcommerce.common.locale.domain.Locale;
import org.broadleafcommerce.common.util.BLCMapUtils;
import org.broadleafcommerce.common.util.TypedClosure;
import org.broadleafcommerce.common.web.BroadleafRequestContext;
import org.broadleafcommerce.core.catalog.dao.ProductDao;
import org.broadleafcommerce.core.catalog.domain.Category;
import org.broadleafcommerce.core.catalog.domain.Product;
import org.broadleafcommerce.core.search.dao.FieldDao;
import org.broadleafcommerce.core.search.dao.SearchFacetDao;
import org.broadleafcommerce.core.search.domain.CategorySearchFacet;
import org.broadleafcommerce.core.search.domain.Field;
import org.broadleafcommerce.core.search.domain.ProductSearchCriteria;
import org.broadleafcommerce.core.search.domain.ProductSearchResult;
import org.broadleafcommerce.core.search.domain.RequiredFacet;
import org.broadleafcommerce.core.search.domain.SearchFacet;
import org.broadleafcommerce.core.search.domain.SearchFacetDTO;
import org.broadleafcommerce.core.search.domain.SearchFacetRange;
import org.broadleafcommerce.core.search.domain.SearchFacetResultDTO;
import org.broadleafcommerce.core.search.domain.solr.FieldType;
import org.broadleafcommerce.core.search.service.SearchService;
import org.springframework.beans.factory.DisposableBean;
import org.xml.sax.SAXException;

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileOutputStream;
import java.io.FileReader;
import java.io.IOException;
import java.io.InputStream;
import java.math.BigDecimal;
import java.net.URLDecoder;
import java.util.ArrayList;
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.xml.parsers.ParserConfigurationException;

/**
* An implementation of SearchService that uses Solr.
*
* Note that prior to 2.2.0, this class used to contain all of the logic for interaction with Solr. Since 2.2.0, this class
* has been refactored and parts of it have been split into the other classes you can find in this package.
*
* @author Andre Azzolini (apazzolini)
*/
public class SolrSearchServiceImpl implements SearchService, DisposableBean {
    private static final Log LOG = LogFactory.getLog(SolrSearchServiceImpl.class);

    @Resource(name = "blProductDao")
    protected ProductDao productDao;

    @Resource(name = "blFieldDao")
    protected FieldDao fieldDao;

    @Resource(name = "blSearchFacetDao")
    protected SearchFacetDao searchFacetDao;

    @Resource(name = "blSolrHelperService")
    protected SolrHelperService shs;

    @Resource(name = "blSolrIndexService")
    protected SolrIndexService solrIndexService;

    @Resource(name = "blSolrSearchServiceExtensionManager")
    protected SolrSearchServiceExtensionManager extensionManager;

    public SolrSearchServiceImpl(String solrServer) throws IOException, ParserConfigurationException, SAXException {
        if ("solrhome".equals(solrServer)) {

            final String baseTempPath = System.getProperty("java.io.tmpdir");

            File tempDir = new File(baseTempPath + File.separator + System.getProperty("user.name") + File.separator + "solrhome");
            if (System.getProperty("tmpdir.solrhome") != null) {
                //allow for an override of tmpdir
                tempDir = new File(System.getProperty("tmpdir.solrhome"));
            }
            if (!tempDir.exists()) {
                tempDir.mkdirs();
            }

            solrServer = tempDir.getAbsolutePath();
        }
       
        File solrXml = new File(new File(solrServer), "solr.xml");
        if (!solrXml.exists()) {
            copyConfigToSolrHome(this.getClass().getResourceAsStream("/solr-default.xml"), solrXml);
        }

        LOG.debug(String.format("Using [%s] as solrhome", solrServer));
        LOG.debug(String.format("Using [%s] as solr.xml", solrXml.getAbsoluteFile()));
       
        if (LOG.isTraceEnabled()) {
            LOG.trace("Contents of solr.xml:");
            BufferedReader br = null;
            try {
                br = new BufferedReader(new FileReader(solrXml));
                String line;
                while ((line = br.readLine()) != null) {
                    LOG.trace(line);
                }
            } finally {
                if (br != null) {
                    try {
                        br.close();
                    } catch (Throwable e) {
                        //do nothing
                    }
                }
            }
            LOG.trace("Done printing solr.xml");
        }

        CoreContainer coreContainer = CoreContainer.createAndLoad(solrServer, solrXml);
        EmbeddedSolrServer primaryServer = new EmbeddedSolrServer(coreContainer, SolrContext.PRIMARY);
        EmbeddedSolrServer reindexServer = new EmbeddedSolrServer(coreContainer, SolrContext.REINDEX);

        SolrContext.setPrimaryServer(primaryServer);
        SolrContext.setReindexServer(reindexServer);
        //NOTE: There is no reason to set the admin server here as the SolrContext will return the primary server
        //if the admin server is not set...
    }

    public void copyConfigToSolrHome(InputStream configIs, File destFile) throws IOException {
        BufferedInputStream bis = null;
        BufferedOutputStream bos = null;
        try {
            bis = new BufferedInputStream(configIs);
            bos = new BufferedOutputStream(new FileOutputStream(destFile, false));
            boolean eof = false;
            while (!eof) {
                int temp = bis.read();
                if (temp == -1) {
                    eof = true;
                } else {
                    bos.write(temp);
                }
            }
            bos.flush();
        } finally {
            if (bis != null) {
                try {
                    bis.close();
                } catch (Throwable e) {
                    //do nothing
                }
            }
            if (bos != null) {
                try {
                    bos.close();
                } catch (Throwable e) {
                    //do nothing
                }
            }
        }
    }

    public SolrSearchServiceImpl(SolrServer solrServer) {
        SolrContext.setPrimaryServer(solrServer);
    }

    /**
     * This constructor serves to mimic the one which takes in one {@link SolrServer} argument.
     * By having this and then simply disregarding the second parameter, we can more easily support 2-core
     * Solr configurations that use embedded/standalone per environment.
     *
     * @param solrServer
     * @param reindexServer
     * @throws SAXException
     * @throws ParserConfigurationException
     * @throws IOException
     */
    public SolrSearchServiceImpl(String solrServer, String reindexServer)
            throws IOException, ParserConfigurationException, SAXException {
        this(solrServer);
    }

    /**
     * This constructor serves to mimic the one which takes in one {@link SolrServer} argument.
     * By having this and then simply disregarding the second and third parameters, we can more easily support 2-core
     * Solr configurations that use embedded/standalone per environment, along with an admin server.
     *
     * @param solrServer
     * @param reindexServer
     * @throws SAXException
     * @throws ParserConfigurationException
     * @throws IOException
     */
    public SolrSearchServiceImpl(String solrServer, String reindexServer, String adminServer)
            throws IOException, ParserConfigurationException, SAXException {
        this(solrServer);
    }

    public SolrSearchServiceImpl(SolrServer solrServer, SolrServer reindexServer) {
        SolrContext.setPrimaryServer(solrServer);
        SolrContext.setReindexServer(reindexServer);
    }

    public SolrSearchServiceImpl(SolrServer solrServer, SolrServer reindexServer, SolrServer adminServer) {
        SolrContext.setPrimaryServer(solrServer);
        SolrContext.setReindexServer(reindexServer);
        SolrContext.setAdminServer(adminServer);
    }

    @Override
    public void rebuildIndex() throws ServiceException, IOException {
        solrIndexService.rebuildIndex();
    }

    @Override
    public void destroy() throws Exception {
        if (SolrContext.getServer() instanceof EmbeddedSolrServer) {
            ((EmbeddedSolrServer) SolrContext.getServer()).shutdown();
        }
    }

    @Override
    public ProductSearchResult findExplicitProductsByCategory(Category category, ProductSearchCriteria searchCriteria)
            throws ServiceException {
        List<SearchFacetDTO> facets = getCategoryFacets(category);
        String query = shs.getExplicitCategoryFieldName() + ":" + shs.getCategoryId(category.getId());
        return findProducts("*:*", facets, searchCriteria, shs.getCategorySortFieldName(category) + " asc", query);
    }

    @Override
    public ProductSearchResult findProductsByCategory(Category category, ProductSearchCriteria searchCriteria)
            throws ServiceException {
        List<SearchFacetDTO> facets = getCategoryFacets(category);
        String query = shs.getCategoryFieldName() + ":" + shs.getCategoryId(category.getId());
        return findProducts("*:*", facets, searchCriteria, shs.getCategorySortFieldName(category) + " asc", query);
    }

    @Override
    public ProductSearchResult findProductsByQuery(String query, ProductSearchCriteria searchCriteria)
            throws ServiceException {
        List<SearchFacetDTO> facets = getSearchFacets();
        query = "(" + sanitizeQuery(query) + ")";
        return findProducts(query, facets, searchCriteria, null);
    }

    @Override
    public ProductSearchResult findProductsByCategoryAndQuery(Category category, String query,
            ProductSearchCriteria searchCriteria) throws ServiceException {
        List<SearchFacetDTO> facets = getSearchFacets();

        String catFq = shs.getCategoryFieldName() + ":" + shs.getCategoryId(category.getId());
        query = "(" + sanitizeQuery(query) + ")";
       
        return findProducts(query, facets, searchCriteria, null, catFq);
    }

    public String getLocalePrefix() {
        if (BroadleafRequestContext.getBroadleafRequestContext() != null) {
            Locale locale = BroadleafRequestContext.getBroadleafRequestContext().getLocale();
            if (locale != null) {
                return locale.getLocaleCode() + "_";
            }
        }
        return "";
    }

    protected String buildQueryFieldsString() {
        StringBuilder queryBuilder = new StringBuilder();
        List<Field> fields = fieldDao.readAllProductFields();
        for (Field currentField : fields) {
            if (currentField.getSearchable()) {
                appendFieldToQuery(queryBuilder, currentField);
            }
        }
        return queryBuilder.toString();
    }

    protected void appendFieldToQuery(StringBuilder queryBuilder, Field currentField) {
        List<FieldType> searchableFieldTypes = shs.getSearchableFieldTypes(currentField);
        for (FieldType currentType : searchableFieldTypes) {
            queryBuilder.append(shs.getPropertyNameForFieldSearchable(currentField, currentType)).append(" ");
        }
    }

    /**
     * @deprecated in favor of the other findProducts() method
     */
    protected ProductSearchResult findProducts(String qualifiedSolrQuery, List<SearchFacetDTO> facets,
            ProductSearchCriteria searchCriteria, String defaultSort) throws ServiceException {
        return findProducts(qualifiedSolrQuery, facets, searchCriteria, defaultSort, null);
    }

    /**
     * Given a qualified solr query string (such as "category:2002"), actually performs a solr search. It will
     * take into considering the search criteria to build out facets / pagination / sorting.
     *
     * @param qualifiedSolrQuery
     * @param facets
     * @param searchCriteria
     * @return the ProductSearchResult of the search
     * @throws ServiceException
     */
    protected ProductSearchResult findProducts(String qualifiedSolrQuery, List<SearchFacetDTO> facets,
            ProductSearchCriteria searchCriteria, String defaultSort, String... filterQueries) throws ServiceException {
        Map<String, SearchFacetDTO> namedFacetMap = getNamedFacetMap(facets, searchCriteria);

        // Build the basic query
        SolrQuery solrQuery = new SolrQuery()
                .setQuery(qualifiedSolrQuery)
                .setFields(shs.getProductIdFieldName())
                .setRows(searchCriteria.getPageSize())
                .setStart((searchCriteria.getPage() - 1) * searchCriteria.getPageSize());
        if (filterQueries != null) {
            solrQuery.setFilterQueries(filterQueries);
        }
        solrQuery.addFilterQuery(shs.getNamespaceFieldName() + ":(\"" + shs.getCurrentNamespace() + "\")");
        solrQuery.set("defType", "edismax");
        solrQuery.set("qf", buildQueryFieldsString());

        // Attach additional restrictions
        attachSortClause(solrQuery, searchCriteria, defaultSort);
        attachActiveFacetFilters(solrQuery, namedFacetMap, searchCriteria);
        attachFacets(solrQuery, namedFacetMap);
       
        modifySolrQuery(solrQuery, qualifiedSolrQuery, facets, searchCriteria, defaultSort);
       
        extensionManager.getProxy().modifySolrQuery(solrQuery, qualifiedSolrQuery, facets,
                searchCriteria, defaultSort);

        if (LOG.isTraceEnabled()) {
            try {
                LOG.trace(URLDecoder.decode(solrQuery.toString(), "UTF-8"));
            } catch (Exception e) {
                LOG.trace("Couldn't UTF-8 URL Decode: " + solrQuery.toString());
            }
        }

        // Query solr
        QueryResponse response;
        List<SolrDocument> responseDocuments;
        int numResults = 0;
        try {
            response = SolrContext.getServer().query(solrQuery);
            responseDocuments = getResponseDocuments(response);
            numResults = (int) response.getResults().getNumFound();

            if (LOG.isTraceEnabled()) {
                LOG.trace(response.toString());

                for (SolrDocument doc : responseDocuments) {
                    LOG.trace(doc);
                }
            }
        } catch (SolrServerException e) {
            throw new ServiceException("Could not perform search", e);
        }

        // Get the facets
        setFacetResults(namedFacetMap, response);
        sortFacetResults(namedFacetMap);

        // Get the products
        List<Product> products = getProducts(responseDocuments);

        ProductSearchResult result = new ProductSearchResult();
        result.setFacets(facets);
        result.setProducts(products);
        setPagingAttributes(result, numResults, searchCriteria);
        return result;
    }

    /**
     * Provides a hook point for implementations to modify all SolrQueries before they're executed.
     * Modules should leverage the extension manager method of the same name,
     * {@link SolrSearchServiceExtensionHandler#modifySolrQuery(SolrQuery, String, List, ProductSearchCriteria, String)}
     *
     * @param query
     * @param qualifiedSolrQuery
     * @param facets
     * @param searchCriteria
     * @param defaultSort
     */
    protected void modifySolrQuery(SolrQuery query, String qualifiedSolrQuery,
            List<SearchFacetDTO> facets, ProductSearchCriteria searchCriteria, String defaultSort) {
    }
   
    protected List<SolrDocument> getResponseDocuments(QueryResponse response) {
        List<SolrDocument> docs;
       
        if (response.getGroupResponse() == null) {
            docs = response.getResults();
        } else {
            docs = new ArrayList<SolrDocument>();
            GroupResponse gr = response.getGroupResponse();
            for (GroupCommand gc : gr.getValues()) {
                for (Group g : gc.getValues()) {
                    for (SolrDocument d : g.getResult()) {
                        docs.add(d);
                    }
                }
            }
        }
       
        return docs;
    }

    @Override
    public List<SearchFacetDTO> getSearchFacets() {
        return buildSearchFacetDTOs(searchFacetDao.readAllSearchFacets());
    }

    @Override
    public List<SearchFacetDTO> getCategoryFacets(Category category) {
        List<CategorySearchFacet> categorySearchFacets = category.getCumulativeSearchFacets();

        List<SearchFacet> searchFacets = new ArrayList<SearchFacet>();
        for (CategorySearchFacet categorySearchFacet : categorySearchFacets) {
            searchFacets.add(categorySearchFacet.getSearchFacet());
        }

        return buildSearchFacetDTOs(searchFacets);
    }

    /**
     * Sets up the sorting criteria. This will support sorting by multiple fields at a time
     *
     * @param query
     * @param searchCriteria
     */
    protected void attachSortClause(SolrQuery query, ProductSearchCriteria searchCriteria, String defaultSort) {
        Map<String, String> solrFieldKeyMap = getSolrFieldKeyMap(searchCriteria);

        String sortQuery = searchCriteria.getSortQuery();
        if (StringUtils.isBlank(sortQuery)) {
            sortQuery = defaultSort;
        }

        if (StringUtils.isNotBlank(sortQuery)) {
            String[] sortFields = sortQuery.split(",");
            for (String sortField : sortFields) {
                String field = sortField.split(" ")[0];
                if (solrFieldKeyMap.containsKey(field)) {
                    field = solrFieldKeyMap.get(field);
                }
                ORDER order = "desc".equals(sortField.split(" ")[1]) ? ORDER.desc : ORDER.asc;

                if (field != null) {
                    query.addSortField(field, order);
                }
            }
        }
    }

    /**
     * Restricts the query by adding active facet filters.
     *
     * @param query
     * @param namedFacetMap
     * @param searchCriteria
     */
    protected void attachActiveFacetFilters(SolrQuery query, Map<String, SearchFacetDTO> namedFacetMap,
            ProductSearchCriteria searchCriteria) {
        for (Entry<String, String[]> entry : searchCriteria.getFilterCriteria().entrySet()) {
            String solrKey = null;
            for (Entry<String, SearchFacetDTO> dtoEntry : namedFacetMap.entrySet()) {
                if (dtoEntry.getValue().getFacet().getField().getAbbreviation().equals(entry.getKey())) {
                    solrKey = dtoEntry.getKey();
                    dtoEntry.getValue().setActive(true);
                }
            }

            if (solrKey != null) {
                String solrTag = getSolrFieldTag(shs.getGlobalFacetTagField(), "tag");

                String[] selectedValues = entry.getValue().clone();
                for (int i = 0; i < selectedValues.length; i++) {
                    if (selectedValues[i].contains("range[")) {
                        String rangeValue = selectedValues[i].substring(selectedValues[i].indexOf('[') + 1,
                                selectedValues[i].indexOf(']'));
                        String[] rangeValues = StringUtils.split(rangeValue, ':');
                        if (rangeValues[1].equals("null")) {
                            rangeValues[1] = "*";
                        }
                        selectedValues[i] = solrKey + ":[" + rangeValues[0] + " TO " + rangeValues[1] + "]";
                    } else {
                        selectedValues[i] = solrKey + ":\"" + selectedValues[i] + "\"";
                    }
                }
                String valueString = StringUtils.join(selectedValues, " OR ");

                StringBuilder sb = new StringBuilder();
                sb.append(solrTag).append("(").append(valueString).append(")");

                query.addFilterQuery(sb.toString());
            }
        }
    }

    /**
     * Notifies solr about which facets you want it to determine results and counts for
     *
     * @param query
     * @param namedFacetMap
     */
    protected void attachFacets(SolrQuery query, Map<String, SearchFacetDTO> namedFacetMap) {
        query.setFacet(true);
        for (Entry<String, SearchFacetDTO> entry : namedFacetMap.entrySet()) {
            SearchFacetDTO dto = entry.getValue();
            String facetTagField = entry.getValue().isActive() ? shs.getGlobalFacetTagField() : entry.getKey();

            // Clone the list - we don't want to remove these facets from the DB
            List<SearchFacetRange> facetRanges = new ArrayList<SearchFacetRange>(dto.getFacet().getSearchFacetRanges());

            if (extensionManager != null) {
                extensionManager.getProxy().filterSearchFacetRanges(dto, facetRanges);
            }

            if (facetRanges != null && facetRanges.size() > 0) {
                for (SearchFacetRange range : facetRanges) {
                    query.addFacetQuery(getSolrTaggedFieldString(entry.getKey(), facetTagField, "ex", range));
                }
            } else {
                query.addFacetField(getSolrTaggedFieldString(entry.getKey(), facetTagField, "ex", null));
            }
        }
    }

    /**
     * Builds out the DTOs for facet results from the search. This will then be used by the view layer to
     * display which values are available given the current constraints as well as the count of the values.
     *
     * @param namedFacetMap
     * @param response
     */
    protected void setFacetResults(Map<String, SearchFacetDTO> namedFacetMap, QueryResponse response) {
        if (response.getFacetFields() != null) {
            for (FacetField facet : response.getFacetFields()) {
                String facetFieldName = facet.getName();
                SearchFacetDTO facetDTO = namedFacetMap.get(facetFieldName);

                for (Count value : facet.getValues()) {
                    SearchFacetResultDTO resultDTO = new SearchFacetResultDTO();
                    resultDTO.setFacet(facetDTO.getFacet());
                    resultDTO.setQuantity(new Long(value.getCount()).intValue());
                    resultDTO.setValue(value.getName());
                    facetDTO.getFacetValues().add(resultDTO);
                }
            }
        }

        if (response.getFacetQuery() != null) {
            for (Entry<String, Integer> entry : response.getFacetQuery().entrySet()) {
                String key = entry.getKey();
                String facetFieldName = key.substring(key.indexOf("}") + 1, key.indexOf(':'));
                SearchFacetDTO facetDTO = namedFacetMap.get(facetFieldName);

                String minValue = key.substring(key.indexOf("[") + 1, key.indexOf(" TO"));
                String maxValue = key.substring(key.indexOf(" TO ") + 4, key.indexOf("]"));
                if (maxValue.equals("*")) {
                    maxValue = null;
                }

                SearchFacetResultDTO resultDTO = new SearchFacetResultDTO();
                resultDTO.setFacet(facetDTO.getFacet());
                resultDTO.setQuantity(entry.getValue());
                resultDTO.setMinValue(new BigDecimal(minValue));
                resultDTO.setMaxValue(maxValue == null ? null : new BigDecimal(maxValue));

                facetDTO.getFacetValues().add(resultDTO);
            }
        }
    }

    /**
     * Invoked to sort the facet results. This method will use the natural sorting of the value attribute of the
     * facet (or, if value is null, the minValue of the facet result). Override this method to customize facet
     * sorting for your given needs.
     *
     * @param namedFacetMap
     */
    protected void sortFacetResults(Map<String, SearchFacetDTO> namedFacetMap) {
        for (Entry<String, SearchFacetDTO> entry : namedFacetMap.entrySet()) {
            Collections.sort(entry.getValue().getFacetValues(), new Comparator<SearchFacetResultDTO>() {
                public int compare(SearchFacetResultDTO o1, SearchFacetResultDTO o2) {
                    if (o1.getValue() != null && o2.getValue() != null) {
                        return o1.getValue().compareTo(o2.getValue());
                    } else if (o1.getMinValue() != null && o2.getMinValue() != null) {
                        return o1.getMinValue().compareTo(o2.getMinValue());
                    }
                    return 0; // Don't know how to compare
                }
            });
        }
    }

    /**
     * Sets the total results, the current page, and the page size on the ProductSearchResult. Total results comes
     * from solr, while page and page size are duplicates of the searchCriteria conditions for ease of use.
     *
     * @param result
     * @param response
     * @param searchCriteria
     */
    public void setPagingAttributes(ProductSearchResult result, int numResults, ProductSearchCriteria searchCriteria) {
        result.setTotalResults(numResults);
        result.setPage(searchCriteria.getPage());
        result.setPageSize(searchCriteria.getPageSize());
    }

    /**
     * Given a list of product IDs from solr, this method will look up the IDs via the productDao and build out
     * actual Product instances. It will return a Products that is sorted by the order of the IDs in the passed
     * in list.
     *
     * @param response
     * @return the actual Product instances as a result of the search
     */
    protected List<Product> getProducts(List<SolrDocument> responseDocuments) {
        final List<Long> productIds = new ArrayList<Long>();
        for (SolrDocument doc : responseDocuments) {
            productIds.add((Long) doc.getFieldValue(shs.getProductIdFieldName()));
        }

        List<Product> products = productDao.readProductsByIds(productIds);

        // We have to sort the products list by the order of the productIds list to maintain sortability in the UI
        if (products != null) {
            Collections.sort(products, new Comparator<Product>() {
                public int compare(Product o1, Product o2) {
                    Long o1id = shs.getProductId(o1.getId());
                    Long o2id = shs.getProductId(o2.getId());
                    return new Integer(productIds.indexOf(o1id)).compareTo(productIds.indexOf(o2id));
                }
            });
        }

        return products;
    }

    /**
     * Create the wrapper DTO around the SearchFacet
     *
     * @param searchFacets
     * @return the wrapper DTO
     */
    protected List<SearchFacetDTO> buildSearchFacetDTOs(List<SearchFacet> searchFacets) {
        List<SearchFacetDTO> facets = new ArrayList<SearchFacetDTO>();
        Map<String, String[]> requestParameters = BroadleafRequestContext.getRequestParameterMap();

        for (SearchFacet facet : searchFacets) {
            if (facetIsAvailable(facet, requestParameters)) {
                SearchFacetDTO dto = new SearchFacetDTO();
                dto.setFacet(facet);
                dto.setShowQuantity(true);
                facets.add(dto);
            }
        }

        return facets;
    }

    /**
     * Checks to see if the requiredFacets condition for a given facet is met.
     *
     * @param facet
     * @param request
     * @return whether or not the facet parameter is available
     */
    protected boolean facetIsAvailable(SearchFacet facet, Map<String, String[]> params) {
        // Facets are available by default if they have no requiredFacets
        if (CollectionUtils.isEmpty(facet.getRequiredFacets())) {
            return true;
        }

        // If we have at least one required facet but no active facets, it's impossible for this facet to be available
        if (MapUtils.isEmpty(params)) {
            return false;
        }

        // We must either match all or just one of the required facets depending on the requiresAllDependentFacets flag
        int requiredMatches = facet.getRequiresAllDependentFacets() ? facet.getRequiredFacets().size() : 1;
        int matchesSoFar = 0;

        for (RequiredFacet requiredFacet : facet.getRequiredFacets()) {
            if (requiredMatches == matchesSoFar) {
                return true;
            }

            // Check to see if the required facet has a value in the current request parameters
            for (Entry<String, String[]> entry : params.entrySet()) {
                String key = entry.getKey();
                if (key.equals(requiredFacet.getRequiredFacet().getField().getAbbreviation())) {
                    matchesSoFar++;
                    break;
                }
            }
        }

        return requiredMatches == matchesSoFar;
    }

    /**
     * Perform any necessary query sanitation here. For example, we disallow open and close parentheses, colons, and we also
     * ensure that quotes are actual quotes (") and not the URL encoding (&quot;) so that Solr is able to properly handle
     * the user's intent.
     *
     * @param query
     * @return the sanitized query
     */
    protected String sanitizeQuery(String query) {
        return query.replace("(", "").replace("%28", "")
                .replace(")", "").replace("%29", "")
                .replace(":", "").replace("%3A", "").replace("%3a", "")
                .replace("&quot;", "\""); // Allow quotes in the query for more finely tuned matches
    }

    /**
     * Returns a field string. Given indexField = a and a non-null range, would produce the following String:
     * a:[minVal TO maxVal]
     */
    protected String getSolrFieldString(String indexField, SearchFacetRange range) {
        StringBuilder sb = new StringBuilder();

        sb.append(indexField);

        if (range != null) {
            String minValue = range.getMinValue().toPlainString();
            String maxValue = range.getMaxValue() == null ? "*" : range.getMaxValue().toPlainString();
            sb.append(":[").append(minValue).append(" TO ").append(maxValue).append("]");
        }

        return sb.toString();
    }

    /**
     * Returns a fully composed solr field string. Given indexField = a, tag = ex, and a non-null range,
     * would produce the following String: {!ex=a}a:[minVal TO maxVal]
     */
    protected String getSolrTaggedFieldString(String indexField, String tagField, String tag, SearchFacetRange range) {
        return getSolrFieldTag(tagField, tag) + getSolrFieldString(indexField, range);
    }

    /**
     * Returns a solr field tag. Given indexField = a, tag = ex, would produce the following String:
     * {!ex=a}
     */
    protected String getSolrFieldTag(String tagField, String tag) {
        StringBuilder sb = new StringBuilder();
        if (StringUtils.isNotBlank(tag)) {
            sb.append("{!").append(tag).append("=").append(tagField).append("}");
        }
        return sb.toString();
    }

    /**
     * @param facets
     * @param searchCriteria
     * @return a map of fully qualified solr index field key to the searchFacetDTO object
     */
    protected Map<String, SearchFacetDTO> getNamedFacetMap(List<SearchFacetDTO> facets,
            final ProductSearchCriteria searchCriteria) {
        return BLCMapUtils.keyedMap(facets, new TypedClosure<String, SearchFacetDTO>() {

            public String getKey(SearchFacetDTO facet) {
                return getSolrFieldKey(facet.getFacet().getField(), searchCriteria);
            }
        });
    }

    /**
     * This method will be used to map a field abbreviation to the appropriate solr index field to use. Typically,
     * this default implementation that maps to the facet field type will be sufficient. However, there may be
     * cases where you would want to use a different solr index depending on other currently active facets. In that
     * case, you would associate that mapping here. For example, for the "price" abbreviation, we would generally
     * want to use "defaultSku.retailPrice_td". However, if a secondary facet on item condition is selected (such
     * as "refurbished", we may want to index "price" to "refurbishedSku.retailPrice_td". That mapping occurs here.
     *
     * @param fields
     * @param searchCriteria the searchCriteria in case it is needed to determine the field key
     * @return the solr field index key to use
     */
    protected String getSolrFieldKey(Field field, ProductSearchCriteria searchCriteria) {
        return shs.getPropertyNameForFieldFacet(field);
    }

    /**
     * @param searchCriteria
     * @return a map of abbreviated key to fully qualified solr index field key for all product fields
     */
    protected Map<String, String> getSolrFieldKeyMap(ProductSearchCriteria searchCriteria) {
        List<Field> fields = fieldDao.readAllProductFields();
        Map<String, String> solrFieldKeyMap = new HashMap<String, String>();
        for (Field field : fields) {
            solrFieldKeyMap.put(field.getAbbreviation(), getSolrFieldKey(field, searchCriteria));
        }
        return solrFieldKeyMap;
    }

}
TOP

Related Classes of org.broadleafcommerce.core.search.service.solr.SolrSearchServiceImpl

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.