Page 1 of 1

How to Configure Solr to Search for Other Entities

Posted: Tue Jun 10, 2014 6:12 am
by pokemon007
BLC Solr search extension seems product centric. I tried to configure search for other entity types such as deal/promotion, but couldn't get it to work. Here is what I've done:

1. Add necessary records for the new entity to following search related tables:
1) BLC_FIELD;
2) BLC_FIELD_SEARCH_TYPES
3) BLC_SEARCH_FACET
4) BLC_SEARCH_FACET_RANGE
2. Extend SearchFacetDao to support read facets by entity type

3. Extend SolrSearchService and add following methods
1) getSearchFacets(String entityType)
2) findDealByQuery
3) Other necessary internal methods

4. Extend BroadleafSearchController to support search for different entity types

All above code changes take effect, but the search doesn't work. The problem is in SolrSearchServiceImpl.setFacetResults. FacetField facet.getValues is null and for (Count value : facet.getValues()) doesn't check for it:

Code: Select all

   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);
             }
          }
       }


Am I missing anything? Do the records in BLC_FIELD control what Solr will index?

Thank you!

-Charlie

Re: How to Configure Solr to Search for Other Entities

Posted: Tue Jun 10, 2014 10:27 am
by phillipuniverse
I would imagine you will need to override the SolrIndexService as well; currently it only indexes products.

What you described looks like a bug could you open a bug in GitHub for it? Sorry your other one fell by the wayside; I just commented on it with a potential resolution.

Re: How to Configure Solr to Search for Other Entities

Posted: Thu Jun 12, 2014 9:07 pm
by RapidTransit
PM me if you want to to take a look at my Repo I've done a lot of Solr customization, Search on Skus and search based on arbitrary (Not predefined) range values, type-ahead auto complete, filtering based on Sku values and Product values, even client-side filtering with back button support Filtering 25 Products with 3 Skus Each, one facet takes under 80ms, to filter, second takes less than 20-50ms

You'll see what has to be modified, to add a custom entity.

Edit : Although 3.2 added in Sku support :cry:

Re: How to Configure Solr to Search for Other Entities

Posted: Sun Aug 31, 2014 2:13 am
by anirban.212
RapidTransit wrote:PM me if you want to to take a look at my Repo I've done a lot of Solr customization, Search on Skus and search based on arbitrary (Not predefined) range values, type-ahead auto complete, filtering based on Sku values and Product values, even client-side filtering with back button support Filtering 25 Products with 3 Skus Each, one facet takes under 80ms, to filter, second takes less than 20-50ms

You'll see what has to be modified, to add a custom entity.

Edit : Although 3.2 added in Sku support :cry:


Hi,
We are working on solr customization. Appreciate if you could share some pointers in this forum for the solr customization.

Re: How to Configure Solr to Search for Other Entities

Posted: Sun Sep 14, 2014 2:06 pm
by RapidTransit
I'm still polishing it but heres some of my stuff:

Code: Select all

package com.mycompany.core.search.dao;

import com.mycompany.core.search.domain.FieldExtension;
import com.mycompany.core.search.domain.FieldExtensionImpl;
import org.broadleafcommerce.core.search.dao.FieldDaoImpl;
import org.broadleafcommerce.core.search.domain.*;

import javax.persistence.TypedQuery;
import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.CriteriaQuery;
import javax.persistence.criteria.Root;
import java.util.HashMap;
import java.util.List;
import java.util.Map;


public class FieldDaoExtensionImpl extends FieldDaoImpl implements FieldDaoExtension {

    @Override
    public List<Field> readAllSkuFields() {
        CriteriaBuilder builder = em.getCriteriaBuilder();
        CriteriaQuery<Field> criteria = builder.createQuery(Field.class);
        Root<FieldImpl> root = criteria.from(FieldImpl.class);
        criteria.select(root);
        criteria.where(
                builder.equal(root.get("entityType").as(String.class), FieldEntity.SKU.getType())
        );
        TypedQuery<Field> query = em.createQuery(criteria);
        return query.getResultList();
    }


    @Override
    public List<Field> readAllSkuRangeFields() {
        CriteriaBuilder builder = em.getCriteriaBuilder();
        CriteriaQuery<Field> criteria = builder.createQuery(Field.class);
        Root<FieldImpl> root = criteria.from(FieldImpl.class);
        criteria.select(root);
        criteria.where(
                builder.equal(root.get("isAdHocRange").as(Boolean.class), true),
                builder.equal(root.get("entityType").as(String.class), FieldEntity.SKU.getType())
        );
        TypedQuery<Field> query = em.createQuery(criteria);
        return query.getResultList();
    }

    @Override
    public List<Field> readAllProductRangeFields() {
        CriteriaBuilder builder = em.getCriteriaBuilder();
        CriteriaQuery<Field> criteria = builder.createQuery(Field.class);
        Root<FieldImpl> root = criteria.from(FieldImpl.class);
        criteria.select(root);
        criteria.where(
                builder.equal(root.get("isAdHocRange").as(Boolean.class), true),
                builder.equal(root.get("entityType").as(String.class), FieldEntity.PRODUCT.getType())
        );
        TypedQuery<Field> query = em.createQuery(criteria);
        return query.getResultList();
    }

    @Override
    public List<Field> readAllRangeFields(){
        CriteriaBuilder builder = em.getCriteriaBuilder();
        CriteriaQuery<Field> criteria = builder.createQuery(Field.class);
        Root<FieldExtensionImpl> root = criteria.from(FieldExtensionImpl.class);
        criteria.select(root);
        criteria.where(
                builder.equal(root.get("isAdHocRange").as(Boolean.class), true)
        );
        TypedQuery<Field> query = em.createQuery(criteria);
        return query.getResultList();
    }

    @Override
    public Map<String, Field> generateRangeFacets(List<Field> fields){
        Map<String, Field> composedFields = new HashMap<String, Field>(fields.size() * 2);
        for(Field field : fields){
            if(field.getClass().isAssignableFrom(FieldExtensionImpl.class)){
                FieldExtension fieldExtension = (FieldExtension) field;
                String minField = fieldExtension.getAbbreviation() + fieldExtension.getMinPostfix();
                String maxField = fieldExtension.getAbbreviation() + fieldExtension.getMaxPostfix();
                composedFields.put(minField, field);
                composedFields.put(maxField, field);
            }
        }
        return composedFields;
    }


}


Code: Select all

package com.mycompany.core.search.domain;

import org.broadleafcommerce.core.search.domain.FieldImpl;

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Table;
import javax.persistence.Transient;
import java.io.Serializable;
import java.math.BigDecimal;

@Entity
@Table(name = "PSS_FIELD_EXTENSION")
public class FieldExtensionImpl extends FieldImpl implements FieldExtension, Serializable {

    private static final long serialVersionUID = 1L;

    @Column(name = "AD_HOC_RANGE")
    protected Boolean isAdHocRange = false;

    @Column(name = "MIN_POST_FIX")
    protected String minPostfix;

    @Column(name = "MAX_POST_FIX")
    protected String maxPostfix;

    @Column(name = "INCREMENT")
    protected BigDecimal increment;

    @Column(name="SORTABLE")
    protected Boolean isSortable = false;

    @Transient
    protected Integer minValue;

    @Transient
    protected Integer maxValue;

    @Transient
    protected BigDecimal requestMin;

    @Transient
    protected BigDecimal requestMax;

    @Override
    public Boolean getIsAdHocRange() {
        return isAdHocRange;
    }

    @Override
    public void setIsAdHocRange(Boolean isAdHocRange) {
        this.isAdHocRange = isAdHocRange;
    }

    @Override
    public String getMinPostfix() {
        return minPostfix;
    }

    @Override
    public void setMinPostfix(String minPostfix) {
        this.minPostfix = minPostfix;
    }

    @Override
    public String getMaxPostfix() {
        return maxPostfix;
    }

    @Override
    public void setMaxPostfix(String maxPostfix) {
        this.maxPostfix = maxPostfix;
    }

    @Override
    public BigDecimal getIncrement() {
        return increment != null ? increment : new BigDecimal(1);
    }

    @Override
    public void setIncrement(BigDecimal increment) {
        this.increment = increment;
    }

    @Override
    public String getFormattedName() {
        return getAbbreviation() + "_" + getFacetFieldType().getType();
    }

    @Override
    public String getFormattedMinName() {
        return getAbbreviation() + getMinPostfix();
    }

    @Override
    public String getFormattedMaxName() {
        return getAbbreviation() + getMaxPostfix();
    }

    @Override
    public Integer getMinValue() {
        return minValue;
    }

    @Override
    public void setMinValue(Integer minValue) {
        this.minValue = minValue;
    }

    @Override
    public Integer getMaxValue() {
        return maxValue;
    }

    @Override
    public void setMaxValue(Integer maxValue) {
        this.maxValue = maxValue;
    }

    @Override
    public BigDecimal getRequestMin() {
        return requestMin != null ? requestMin : new BigDecimal(minValue);
    }

    @Override
    public void setRequestMin(BigDecimal requestMin) {
        this.requestMin = requestMin;
    }

    @Override
    public BigDecimal getRequestMax() {
        return requestMax != null ? requestMax : new BigDecimal(maxValue);
    }

    @Override
    public void setRequestMax(BigDecimal requestMax) {
        this.requestMax = requestMax;
    }

    @Override
    public Boolean getIsSortable() {
        return isSortable;
    }

    @Override
    public void setIsSortable(Boolean isSortable) {
        this.isSortable = isSortable;
    }

    @Override
    public String toJsonString() {
        final StringBuilder sb = new StringBuilder(getAbbreviation()).append(":{");
        sb.append("abbreviation:'").append(getAbbreviation()).append('\'');
        sb.append(", isAdHocRange:").append(isAdHocRange);
        sb.append(", entityType:'").append(entityType).append('\'');
        sb.append(", formattedName:'").append(getFormattedName()).append('\'');
        if(isAdHocRange) {
            sb.append(", rangeInfo: {");
            sb.append("increment:").append(getIncrement());
            sb.append(", minValue:").append(minValue);
            sb.append(", maxValue:").append(maxValue);
            sb.append(", requestMin:").append(getRequestMin());
            sb.append(", requestMax:").append(getRequestMax());
            sb.append(", formattedMinName:'").append(getFormattedMinName()).append('\'');
            sb.append(", formattedMaxName:'").append(getFormattedMaxName()).append('\'');
            sb.append("}");
        }
        sb.append('}');
        return sb.toString();
    }

}


Code: Select all

package com.mycompany.core.solr;

import com.mycompany.core.search.dao.FieldDaoExtension;
import jodd.bean.BeanUtil;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.solr.client.solrj.SolrServer;
import org.apache.solr.client.solrj.SolrServerException;
import org.apache.solr.common.SolrInputDocument;
import org.broadleafcommerce.common.exception.ServiceException;
import org.broadleafcommerce.common.locale.domain.Locale;
import org.broadleafcommerce.common.util.BLCCollectionUtils;
import org.broadleafcommerce.common.util.StopWatch;
import org.broadleafcommerce.common.util.TransactionUtils;
import org.broadleafcommerce.common.util.TypedTransformer;
import org.broadleafcommerce.core.catalog.domain.Product;
import org.broadleafcommerce.core.catalog.domain.Sku;
import org.broadleafcommerce.core.search.dao.CatalogStructure;
import org.broadleafcommerce.core.search.domain.Field;
import org.broadleafcommerce.core.search.service.solr.SolrContext;
import org.broadleafcommerce.core.search.service.solr.SolrIndexCachedOperation;
import org.broadleafcommerce.core.search.service.solr.SolrIndexServiceImpl;
import org.springframework.transaction.TransactionDefinition;
import org.springframework.transaction.TransactionStatus;

import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;


public class SolrIndexServiceExtensionImpl  extends SolrIndexServiceImpl{
    private static final Log LOG = LogFactory.getLog(SolrIndexServiceExtensionImpl.class);




    @Override
    public void buildIncrementalIndex(int page, int pageSize, boolean useReindexServer) throws ServiceException {

        TransactionStatus status = TransactionUtils.createTransaction("readProducts",
                TransactionDefinition.PROPAGATION_REQUIRED, transactionManager, true);
        if (LOG.isDebugEnabled()) {
            LOG.debug(String.format("Building index - page: [%s], pageSize: [%s]", page, pageSize));
        }
        boolean cacheOperationManaged = false;
        StopWatch s = new StopWatch();
        try {
            Collection<SolrInputDocument> documents = new ArrayList<SolrInputDocument>();
            Collection<SolrInputDocument> skuDocuments = new ArrayList<SolrInputDocument>();

            CatalogStructure cache = SolrIndexCachedOperation.getCache();
            if (cache != null) {
                cacheOperationManaged = true;
            } else {
                cache = new CatalogStructure();
                SolrIndexCachedOperation.setCache(cache);
            }
            List<Product> products = readAllActiveProducts(page, pageSize);
            List<Field> fields = ((FieldDaoExtension) fieldDao).readAllProductFields();
            List<Long> productIds = BLCCollectionUtils.collectList(products, new TypedTransformer<Long>() {
                @Override
                public Long transform(Object input) {
                    return ((Product) input).getId();
                }
            });
            solrIndexDao.populateProductCatalogStructure(productIds, SolrIndexCachedOperation.getCache());
            List<Locale> locales = getAllLocales();


            for (Product product : products) {
                List<Sku> skus = product.getAdditionalSkus();
                for(Sku sku : skus){
                    skuDocuments.add(buildSkuDocument(sku));
                }

                SolrInputDocument doc = buildDocument(product, fields, locales);
                //If someone overrides the buildDocument method and determines that they don't want a product
                //indexed, then they can return null. If the document is null it does not get added to
                //to the index.
                if (doc != null) {
                    documents.add(doc);
                }
            }


            logDocuments(documents);

            if (!CollectionUtils.isEmpty(documents)) {
                SolrServer server = useReindexServer ? SolrContext.getReindexServer() : SolrContext.getServer();

                server.add(documents);
                server.add(skuDocuments);
                server.commit();
            }
            TransactionUtils.finalizeTransaction(status, transactionManager, false);
        } catch (SolrServerException e) {
            TransactionUtils.finalizeTransaction(status, transactionManager, true);
            throw new ServiceException("Could not rebuild index", e);
        } catch (IOException e) {
            TransactionUtils.finalizeTransaction(status, transactionManager, true);
            throw new ServiceException("Could not rebuild index", e);
        } catch (RuntimeException e) {
            TransactionUtils.finalizeTransaction(status, transactionManager, true);
            throw e;
        }

        if (LOG.isDebugEnabled()) {
            LOG.debug(String.format("Built index - page: [%s], pageSize: [%s] in [%s]", page, pageSize, s.toLapString()));
        }
    }

    @Override
     protected void attachBasicDocumentFields(Product product, SolrInputDocument document) {
         document.addField("isSku", false);
         super.attachBasicDocumentFields(product, document);
     }


    protected SolrInputDocument buildSkuDocument(Sku sku){
        SolrInputDocument skuDocument = new SolrInputDocument();
        attachBasicDocumentFields(sku, skuDocument );

        skuDocument.addField("isSku", true);
        skuDocument.addField("productId", sku.getProduct().getId());
        List<Field> skuFields = fieldDao.readAllSkuFields();
        for(Field f : skuFields){
            String fieldName = shs.getPropertyNameForFieldFacet(f);
            String propertyName = f.getPropertyName();
            Object  propertyValue = BeanUtil.getPropertySilently(sku, propertyName);
            skuDocument.addField(fieldName, propertyValue);
        }
        return skuDocument;
    }


}


Code: Select all

package com.mycompany.core.solr;


import com.mycompany.core.catalog.dao.ProductDaoExtension;
import com.mycompany.core.search.dao.FieldDaoExtension;
import com.mycompany.core.search.domain.*;
import jodd.util.StringBand;
import jodd.util.StringUtil;
import jodd.util.collection.LongArrayList;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.commons.validator.routines.BigDecimalValidator;
import org.apache.commons.validator.routines.DoubleValidator;
import org.apache.solr.client.solrj.SolrQuery;
import org.apache.solr.client.solrj.SolrServer;
import org.apache.solr.client.solrj.SolrServerException;
import org.apache.solr.client.solrj.response.QueryResponse;
import org.apache.solr.common.SolrDocument;
import org.broadleafcommerce.common.exception.ServiceException;
import org.broadleafcommerce.core.catalog.domain.Category;
import org.broadleafcommerce.core.catalog.domain.Product;
import org.broadleafcommerce.core.search.domain.*;
import org.broadleafcommerce.core.search.service.solr.SolrContext;
import org.broadleafcommerce.core.search.service.solr.SolrSearchServiceImpl;
import org.xml.sax.SAXException;

import javax.xml.parsers.ParserConfigurationException;
import java.io.IOException;
import java.math.BigDecimal;
import java.net.URLDecoder;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.stream.Collector;
import java.util.stream.Collectors;
import java.util.stream.LongStream;


public class SolrSearchServiceExtensionImpl extends SolrSearchServiceImpl{

    private static final Log LOG = LogFactory.getLog(SolrSearchServiceExtensionImpl.class);

    public SolrSearchServiceExtensionImpl(String solrServer) throws IOException, ParserConfigurationException, SAXException {
        super(solrServer);
    }

    public SolrSearchServiceExtensionImpl(SolrServer solrServer) {
        super(solrServer);
    }

    public SolrSearchServiceExtensionImpl(String solrServer, String reindexServer) throws IOException, ParserConfigurationException, SAXException {
        super(solrServer, reindexServer);
    }

    public SolrSearchServiceExtensionImpl(SolrServer solrServer, SolrServer reindexServer) {
        super(solrServer, reindexServer);
    }


    protected LongArrayList getProductIds(SolrQuery solrQuery){
        return getIds(solrQuery, shs.getProductIdFieldName());
    }

    protected LongArrayList getSkuIds(SolrQuery solrQuery){
        return getIds(solrQuery, shs.getSkuIdFieldName());
    }

    protected LongArrayList getIds(SolrQuery solrQuery, final String fieldName){
        List<SolrDocument> solrDocs = getSolrDocuments(solrQuery);
        final LongArrayList idList = new LongArrayList(solrDocs.size());
        solrDocs.stream().forEachOrdered(d -> idList.add((Long) d.getFieldValue(fieldName)));
        return idList;
    }

    private List<SolrDocument> getSolrDocuments(SolrQuery solrQuery) {
        QueryResponse response;
        List<SolrDocument> solrDocs = null;
        try {
            response = SolrContext.getServer().query(solrQuery);
            solrDocs = getResponseDocuments(response);
        } catch (SolrServerException e) {
            e.printStackTrace();
        }
        return solrDocs;
    }

    protected SolrQuery getNewSolrQuery(String qualifiedSolrQuery, boolean isSku, SearchCriteria searchCriteria, String... filterQueries){
        SolrQuery solrQuery = new SolrQuery(qualifiedSolrQuery);
        if(isSku){
            solrQuery.set("isSku", true);
        }else{
            solrQuery.set("isSku", false);
            solrQuery.setFields(shs.getProductIdFieldName());
        }
        solrQuery.setRows(searchCriteria.getPageSize()).setStart((searchCriteria.getPage() - 1) * searchCriteria.getPageSize());
        solrQuery.addFilterQuery(new StringBand(5).append(shs.getNamespaceFieldName()).append(":(").append('"').append(shs.getCurrentNamespace()).append('"').append(")").toString());
        solrQuery.set("defType", "edismax");
        if (filterQueries != null) {
            solrQuery.setFilterQueries(filterQueries);
        }
        return solrQuery;
    }



    protected LongArrayList findSkus(String qualifiedSolrQuery, List<SearchFacetDTO> facets, SearchCriteria searchCriteria, String defaultSort, Category category, String... filterQueries) throws ServiceException {
        Map<String, SearchFacetDTO> namedFacetMap = getNamedFacetMap(facets, searchCriteria);
        searchCriteria.setPageSize(searchCriteria.getPageSize() * 4);
        SolrQuery solrQuery = getNewSolrQuery(qualifiedSolrQuery, true, searchCriteria, filterQueries);
        attachActiveSkuFilters(solrQuery, namedFacetMap, searchCriteria);
        return getSkuIds(solrQuery);
    }



    protected LongArrayList productOrdering(String qualifiedSolrQuery, SearchCriteria searchCriteria, String defaultSort, Category category, String... filterQueries){
        SolrQuery productOrder = getNewSolrQuery(qualifiedSolrQuery, false, searchCriteria, filterQueries);
        attachSortClause(productOrder, searchCriteria, defaultSort);
        return getProductIds(productOrder);
    }

    protected SearchResult findProducts(String qualifiedSolrQuery, List<SearchFacetDTO> facets, SearchCriteria searchCriteria, String defaultSort, Category category, String... filterQueries) throws ServiceException {
        Map<String, SearchFacetDTO> namedFacetMap = getNamedFacetMap(facets, searchCriteria);

        SolrQuery solrQuery = getNewSolrQuery(qualifiedSolrQuery, false, searchCriteria, filterQueries);
        solrQuery.set("qf", buildQueryFieldsString());
        LongArrayList productOrder = productOrdering(qualifiedSolrQuery, searchCriteria, defaultSort, category,  filterQueries);
        attachActiveFilters(solrQuery, namedFacetMap, searchCriteria);
        attachFacets(solrQuery, namedFacetMap);
        QueryResponse stats = getStatQuery(qualifiedSolrQuery, filterQueries);
        extensionManager.getProxy().modifySolrQuery(solrQuery, qualifiedSolrQuery, facets, searchCriteria, defaultSort);

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

        } catch (SolrServerException e) {
            throw new ServiceException("Could not perform search", e);
        }

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

        // Get the products

        LongArrayList skus = findSkus(qualifiedSolrQuery, facets, searchCriteria, defaultSort, category, filterQueries);
        List<Product> products = getProducts(responseDocuments,  productOrder, skus, searchCriteria, category);
        SearchResult result = new SearchResult();
        facets.stream().forEach(f -> rangeFacet(stats, f, (FieldExtension) f.getFacet().getField()));

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

    private void rangeFacet(QueryResponse stats, SearchFacetDTO facet, FieldExtension fieldExtension) {
        if(fieldExtension.getIsAdHocRange() != null && fieldExtension.getIsAdHocRange()) {
            if (stats.getFieldStatsInfo().containsKey(fieldExtension.getFormattedName())) {
                Integer validateMin = validateMin(stats, fieldExtension);
                Integer validateMax = validateMax(stats, fieldExtension);
                if(validateMin != null ) ((FieldExtension) facet.getFacet().getField()).setMinValue(validateMin);
                if(validateMax != null) ((FieldExtension) facet.getFacet().getField()).setMaxValue(validateMax);
            }
        }
    }

    private Integer validateMin(QueryResponse stats, FieldExtension fieldExtension) {
        return ((Double) Math.floor(DoubleValidator.getInstance().validate(stats.getFieldStatsInfo().get(fieldExtension.getFormattedName()).getMin().toString()))).intValue();
    }

    private Integer validateMax(QueryResponse stats, FieldExtension fieldExtension) {
        return ((Double) Math.floor(DoubleValidator.getInstance().validate(stats.getFieldStatsInfo().get(fieldExtension.getFormattedName()).getMax().toString()))).intValue();
    }


    private QueryResponse getStatQuery(String qualifiedSolrQuery, String[] filterQueries) throws ServiceException {
        SolrQuery statQuery = new SolrQuery().setQuery(qualifiedSolrQuery);
        statQuery.setParam("stats", true);
        List<Field> rangeFields = ((FieldDaoExtension) fieldDao).readAllRangeFields();
        String[] statFields = new String[rangeFields.size()];
        for(int i = 0; i <  rangeFields.size(); i++){
            statFields[i] = ((FieldExtension) rangeFields.get(i)).getFormattedName();
        }
        statQuery.add("stats.field", statFields);
        statQuery.setFilterQueries(filterQueries);
        QueryResponse stats;
        try {
            stats = SolrContext.getServer().query(statQuery);
        }
        catch (SolrServerException e) {
            throw new ServiceException("Could not perform search", e);
        }
        return stats;
    }



    public SearchResult findProductsByCategoryAndQuery(Category category, String query,
                                                       SearchCriteria SearchCriteria) throws ServiceException {
        List<SearchFacetDTO> facets = getSearchFacets();

        String catFq = shs.getCategoryFieldName() + ":" + shs.getCategoryId(category.getId());
        query = "(" + sanitizeQuery(query) + ")";

        return findProducts(query, facets, SearchCriteria, null, category, catFq);
    }
    public SearchResult findProductsByCategory(Category category, SearchCriteria SearchCriteria)
            throws ServiceException {
        List<SearchFacetDTO> facets = getCategoryFacets(category);
        String query = shs.getCategoryFieldName() + ":" + shs.getCategoryId(category.getId());
        return findProducts("*:*", facets, SearchCriteria, shs.getCategorySortFieldName(category) + " asc", category, query);
    }

    protected List<Product> getProducts(List<SolrDocument> responseDocuments, LongArrayList productOrder, LongArrayList skus,SearchCriteria SearchCriteria, Category category) {
        final LongArrayList productIds = new LongArrayList(responseDocuments.size());
        for (SolrDocument doc : responseDocuments) {
            productIds.add((long) doc.getFieldValue(shs.getProductIdFieldName()));
        }
        return ((ProductDaoExtension) productDao).readProductsByCategoryAndFilter(productIds, productOrder, skus, SearchCriteria, category);
    }

    public SearchResult _findProductsByQuery(String query, SearchCriteria SearchCriteria)
            throws ServiceException {
        List<SearchFacetDTO> facets = getSearchFacets();
        query = "(" + sanitizeQuery(query) + ")";
        return findProducts(query, facets, SearchCriteria, null, null, null);
    }

    protected void attachActiveFilters(SolrQuery query, Map<String, SearchFacetDTO> namedFacetMap,  SearchCriteria searchCriteria) {
        List<String> queryParams = new ArrayList<String>();
        for(Map.Entry<String, String[]> param : searchCriteria.getFilterCriteria().entrySet()){
            if(param.getValue() != null){
                for (Map.Entry<String, SearchFacetDTO> dtoEntry : namedFacetMap.entrySet()) {

                    getFacet(searchCriteria, queryParams, param, dtoEntry);

                }
            }
        }
        queryString(query, queryParams);
    }

    private void queryString(SolrQuery query, List<String> queryParams) {
        if(queryParams.size() > 0) {
            String solrTag = getSolrFieldTag(shs.getGlobalFacetTagField(), "tag");
            query.addFilterQuery(new StringBand(solrTag).append("(").append(StringUtil.join(queryParams, " OR ")).append( ")").toString());
        }
    }

    protected void attachActiveSkuFilters(SolrQuery query, Map<String, SearchFacetDTO> namedFacetMap,  SearchCriteria searchCriteria) {
        List<String> queryParams = new ArrayList<String>();
        for(Map.Entry<String, String[]> param : searchCriteria.getFilterCriteria().entrySet()){
            if(param.getValue() != null){
                for (Map.Entry<String, SearchFacetDTO> dtoEntry : namedFacetMap.entrySet()) {
                    if(dtoEntry.getValue().getFacet().getField().getEntityType().getType().equals("SKU")){
                        getFacet(searchCriteria, queryParams, param, dtoEntry);
                    }
                }
            }
        }
        queryString(query, queryParams);
    }

    private void getFacet(SearchCriteria searchCriteria, List<String> queryParams, Map.Entry<String, String[]> param, Map.Entry<String, SearchFacetDTO> dtoEntry) {
        FieldExtension fieldExtension = (FieldExtension) dtoEntry.getValue().getFacet().getField();
        if(fieldExtension.getAbbreviation().equals(param.getKey())){
            for(String p : param.getValue()){
                queryParams.add(fieldExtension.getFormattedName()+ ":\"" + p + "\"");
            }
            dtoEntry.getValue().setActive(true);
        } else  if(fieldExtension.getIsAdHocRange()){
            String facetName = fieldExtension.getFormattedMinName();
            BigDecimal start = BigDecimalValidator.getInstance().validate(param.getValue()[0]);
            BigDecimal end = null;
            if(facetName.equals(param.getKey()) && start != null){
                if(searchCriteria.getFilterCriteria().containsKey(fieldExtension.getFormattedMaxName())){
                    end = BigDecimalValidator.getInstance().validate(searchCriteria.getFilterCriteria().get(fieldExtension.getFormattedMaxName())[0]);
                }
                ((FieldExtension) dtoEntry.getValue().getFacet().getField()).setRequestMin(start);
                queryParams.add(new StringBand(5).append(fieldExtension.getFormattedName()).append(":[").append(start.toString()).append(" TO ").append(end != null ? end.toString() : "*").append("]").toString());
            }

            dtoEntry.getValue().setActive(true);
        }
    }

}

This is my Javascript (Well it's Typescript) I think there are some unused imports on it

Code: Select all

///<reference path="../../defs/dot.d.ts" />
///<reference path="../../defs/lodash.d.ts" />
///<reference path="../../defs/jquery.d.ts" />
///<reference path="../../defs/pubsub.d.ts" />
///<reference path="../../defs/history.d.ts" />
///<reference path="../../defs/jqrangeslider.d.ts" />
///<require path="../../jquery/jquery.rangeSlider.js" />
///<reference path="../App.ts" />
///<reference path="Product.ts" />
///<reference path="Facet.ts" />
///<reference path="RangeFacet.ts" />


class FacetFilter{
    private disableHistoryEvent = true;
    private selectorCache=[];
    private history;
    private rangeFacet: Facet[]= [];
    private products;
    private activeProductFacets = {};
    private activeProductRangeFacets = {};
    private activeSkuFacets = {};
    private activeSkuRangeFacets = {};
    private isHistory = false;
    private checkboxes = [];
    private facets : Facet[];
    private maxAmount = {};
    private csrfFilter;
    private facetElement;
    private title;
    private activeSort=[];
    private prodList = document.getElementById('prod-list');

    public constructor(products, facets : Facet[], csrfFilter){
        this.csrfFilter = csrfFilter;
        this.history = History;
        this.products = products;
        this.facets = facets;
        this.title = document.title;
        this.facetElement =  document.getElementById("facet-filter");
        if(History.store != null){
            this.disableHistoryEvent = false;
            this.isHistory = true;
        }
        this.removeApplyButton();
        this.setProductSelectorCache();
        this.setInputs();
        this.setActiveRangeFacets();
        this.setActiveFacets();
        this.setStateChange();
        this.setSort();
    }

    public setProductSelectorCache(){
        for(var i = 0; i < this.products.length; i++){
            var skus = [];
            for(var j = 0; j < this.products[i].skus.length; j++){
                skus[this.products[i].skus[j].skuId] = document.getElementById("sku-" + this.products[i].skus[j].skuId);
            }
            this.selectorCache[this.products[i].productId] = {product: <HTMLLIElement> document.getElementById("prod-" + this.products[i].productId), skus: skus}
        }
    }

    public removeApplyButton(){
        this.facetElement.removeChild(this.facetElement.getElementsByTagName('button')[0]);
    }

    public setInputs(){
        var  inputs = <HTMLInputElement[]> <any> this.facetElement.getElementsByTagName("input");
        for(var i = 0, length = inputs.length; i < length; i++) {
            inputs[i].addEventListener('click', this.eventHandler.bind(this));
            if (inputs[i].type == "checkbox") {
                if (typeof this.maxAmount[inputs[i].name] === 'undefined') {
                    this.maxAmount[inputs[i].name] = 1;
                }
                this.checkboxes.push(inputs[i]);
                this.maxAmount[inputs[i].name]++;
            }
        }

        var self = this;
        var j = 0;
        for(var name in this.facets){
            if(this.facets[name].isAdHocRange) {
                this.rangeFacet[j] = this.facets[name];
                var facet = this.rangeFacet[j];
                var range = document.getElementById(facet.abbreviation);
                range.innerHTML = "";
                this.rangeFacet[j].slider = $(range);
                var dec = 0;
                if (this.rangeFacet[j].rangeInfo.increment < 1) {
                    dec = 1;
                }
                var toolTip = $.Link({
                    target: '-tooltip-',
                    format: {
                        decimals: dec
                    },
                    method: function (value) {
                        $(this).html(
                                '<span class="s-tooltip">' + value + '</span>'
                        );
                    }
                });
                this.rangeFacet[j].slider.noUiSlider({start: [facet.rangeInfo.requestMin, facet.rangeInfo.requestMax], range: {'min': [facet.rangeInfo.minValue], 'max': [facet.rangeInfo.maxValue]}, step: facet.rangeInfo.increment, serialization: {
                    lower: [ toolTip ],
                    upper: [ toolTip ]
                }});
                this.rangeFacet[j].slider.on("change", self.eventHandler.bind(self));
                j++;
            }
        }

    }
    public setSort(){
        var self = this;
        var sortAnchors = document.getElementById('sorting').getElementsByTagName('a');
        for(var i = 0, length = sortAnchors.length; i < length; i++){
            sortAnchors[i].addEventListener('click', function(e){
                e.preventDefault();
                self.isHistory = false;
                var href = (<HTMLAnchorElement> e.currentTarget).getAttribute('href');
                var sortParams = href.match(/(?:sort\=).+?((?=&)|$)/)[0].replace('sort=', '').split('+');
                self.activeSort = sortParams;
                self.sort(sortParams[0], sortParams[1]);
                if(!self.isHistory){
                    self.setUrl();
                }
                self.disableHistoryEvent = false;
            });
        }
    }

    public sort(property, order){
        var map = this.map(property);

        if(typeof map[0].value === 'number'){
            if(order == 'asc'){
                map.sort(function(a, b){
                    return a.value > b.value ? 1 : -1;
                });
            }else if(order == 'desc'){
                map.sort(function(a,b){
                    return a.value < b.value ? 1 : -1;
                });
            }
        }else {
            if (order == 'asc') {
                map.sort(function (a, b) {
                    return a.value.toLowerCase() > b.value.toLowerCase() ? 1 : -1;
                });
            } else if (order == 'desc') {
                map.sort(function (a, b) {
                    return a.value.toLowerCase() < b.value.toLowerCase() ? 1 : -1;
                });
            }
        }
        var result = this.result(map);
        this.appendDocument(result);
    }


    public result(map){
        var result=[];
        for (var i=0, mapLength = map.length; i < mapLength; i++) {
            result.push(this.products[map[i].index]);
        }
        return result;
    }


    public map(property){
        var map = [];
        for(var i = 0, length = this.products.length; i < length; i++){
            map.push({
                index: i,
                value: this.products[i][property]
            })
        }
        return map;
    }

    public appendDocument(result){

        this.prodList.innerHTML = "";
        for(var j = 0, length = result.length; j < length; j++){
            this.prodList.appendChild(this.selectorCache[result[j].productId].product);
        }
    }



    public eventHandler(){
        console.time("eventHandler");
        this.isHistory = false;
        this.setActiveRangeFacets();
        this.setActiveFacets();
        this.filter();
        this.disableHistoryEvent = false;
        console.timeEnd("eventHandler");
    }

    public historyEventHandler(){
        this.setActiveRangeFacets();
        this.setActiveFacets();
        this.filter();

    }

    public filter(){
        var productsLength = this.products.length;

        for(var i = 0; i < productsLength; i++){
            var filtered = false;
            if(this.activeProductFacets != null){
                filtered = this.productFilter(this.products[i], this.activeProductFacets);
            }
            if(!filtered && this.activeProductRangeFacets != null){
                filtered = this.productRangeFilter(this.products[i], this.activeProductRangeFacets);
            }
            if(!filtered && (this.activeSkuFacets != null || this.activeSkuRangeFacets != null)){
                filtered = this.skuFilter(this.products[i].skus, this.selectorCache[this.products[i].productId].skus);
                if(filtered){
                    this.selectorCache[this.products[i].productId].product.className = "hidden";
                }else{
                    this.selectorCache[this.products[i].productId].product.className = "";
                }
            }
            if(this.activeSkuFacets == null && this.activeSkuRangeFacets == null){
                for(var skuElement in this.selectorCache[this.products[i].productId].skus){
                    skuElement.className = "";
                }
            }
        }
        if(!this.isHistory){
            this.setUrl();
        }
    }

    public skuFilter(skus, sel){
        var length = skus.length
        var inc = 0;
        for(var i = 0; i < length; i++){
            var filtered = false;
            if(this.activeSkuFacets != null){
                filtered = this.skuFacetFilter(skus[i], this.activeSkuFacets, sel)
            }
            if(!filtered && this.activeSkuRangeFacets != null){
                filtered = this.skuRangeFacetFilter(skus[i], this.activeSkuRangeFacets, sel[skus[i].skuId])
            }
            if(filtered){
                inc++;
            }
        }
        return inc === skus.length;

    }

    public skuRangeFacetFilter(sku, facets, sel){
        for(var facet in facets){
            if(typeof sku[facet] !== 'undefined'){
                if (facets[facet][0] > sku[facet] || facets[facet][1] < sku[facet]) {
                    sel.className = "hidden";
                    return true;
                }
            }
        }
        sel.className = "";
        return false;
    }

    public skuFacetFilter(sku, facets, sel){
        for(var facet in facets){
            if(typeof sku[facet] !== 'undefined'){
                if(sku[facet] instanceof Array){
                    if (!this.intersect(sku[facet], facets[facet])) {
                        sel.className = "hidden";
                        return true;
                    }
                }
            }
        }
        sel.className =  "";
        return false;
    }

    public productFilter(product, facets){
        for(var facet in facets){
            if(typeof product[facet] !== 'undefined'){
                if( Array.isArray(product[facet])){
                    if (!this.intersect(product[facet], facets[facet])) {
                        this.selectorCache[product.productId].product.className = "hidden";
                        return true;
                    }
                }
                else{
                    if (!this.indexof(facets[facet], product[facet])) {
                        this.selectorCache[product.productId].product.className = "hidden";
                        return true;
                    }
                }
            }
        }
        this.selectorCache[product.productId].product.className = "";
        return false;
    }

    public indexof(array, value) {
    var index = -1,
        length = array ? array.length : 0;

    while (++index < length) {
        if (array[index] === value) {
            return true;
        }
    }
    return false;
}
    public intersect(product, facet){
        var length = facet.length;
        for(var i = 0; i < length; i++){
            if(product.indexOf(facet[i]) > -1){
                return true;
            }
        }
        return false;
    }

    public productRangeFilter(product, facets){
        for(var facet in facets){
            if(typeof product[facet] !== 'undefined'){
                if (facets[facet][0] > product[facet] || facets[facet][1] < product[facet]) {
                    this.selectorCache[product.productId].product.className = "hidden";
                    return true;
                }
            }
        }
        this.selectorCache[product.productId].product.className = "";
        return false;
    }



    public setUrl(){
        var url = $(this.facetElement).find('input:checked').serialize();

        for(var i = 0; i < this.rangeFacet.length; i++){
            var input : Facet =  this.rangeFacet[i];
            var val = input.slider.val();
            if(input.rangeInfo.increment >= 1){
                val[0] = +val[0];
                val[1] = +val[1];
            }
            if(val[0] != input.rangeInfo.minValue || val[1] != input.rangeInfo.maxValue){
                if(url.length > 0){
                    url += "&" + input.rangeInfo.formattedMinName + '=' + val[0] + '&' + input.rangeInfo.formattedMaxName + '=' + val[1] ;
                }else {
                    url += input.rangeInfo.formattedMinName + '=' + val[0]  + '&' + input.rangeInfo.formattedMaxName + '=' + val[1] ;
                }
            }
        }
        if(this.activeSort.length > 1){
            if(url.length > 0){
                url += '&sort=' + this.activeSort[0] + '+' + this.activeSort[1];
            }else{
                url += 'sort=' + this.activeSort[0] + '+' + this.activeSort[1];
            }
        }
        this.setState(url.length > 0 ? '?' + url : this.history.getShortUrl(window.location.href).split('?')[0]);
    }

    public setState(url){
        this.disableHistoryEvent = true;
        this.history.pushState(
            {
                id: this.history.getCurrentIndex(),
                activeProductFacets: this.activeProductFacets,
                activeProductRangeFacets: this.activeProductRangeFacets,
                activeSkuFacets: this.activeSkuFacets,
                activeSkuRangeFacets: this.activeSkuRangeFacets,
                activeSort: this.activeSort
            }, this.title, url);
        console.log(this.history.getState());
    }

    public backInputs(data){
        if(data.activeProductFacets != null ||  data.activeSkuFacets != null) {

            for (var i = 0, length = this.checkboxes.length; i < length; i++) {
                var input = <HTMLInputElement> this.checkboxes[i];
                if (typeof data.activeProductFacets[input.name] !== 'undefined') {
                    input.checked = this.indexof(data.activeProductFacets[input.name], input.value) ? true : false
                } else if (data.activeSkuFacets != null && typeof data.activeSkuFacets[input.name] !== 'undefined') {
                    input.checked = this.indexof(data.activeSkuFacets[input.name], input.value) ? true : false
                } else {
                    input.checked = false;
                }
            }
        }else {
            for (var i = 0, length = this.checkboxes.length; i < length; i++) {
                this.checkboxes[i].checked = false;
            }
        }

        for(var j = 0, length = this.rangeFacet.length; j < length; j++){
            if(data.activeProductRangeFacets != null && typeof data.activeProductRangeFacets[this.rangeFacet[j].abbreviation] !== 'undefined'){
                this.rangeFacet[j].slider.val([data.activeProductRangeFacets[this.rangeFacet[j].abbreviation][0], data.activeProductRangeFacets[this.rangeFacet[j].abbreviation][1]]);
            }else if(data.activeSkuRangeFacets != null && typeof data.activeSkuRangeFacets[this.rangeFacet[j].abbreviation] !== 'undefined'){
                this.rangeFacet[j].slider.val([data.activeSkuRangeFacets[this.rangeFacet[j].abbreviation][0], data.activeSkuRangeFacets[this.rangeFacet[j].abbreviation][1]]);
            }else{
                this.rangeFacet[j].slider.val([this.rangeFacet[j].rangeInfo.minValue, this.rangeFacet[j].rangeInfo.maxValue]);
            }
        }
        this.activeSort = data.activeSort;
        console.log(data);
        if(data.activeSort!= null){

            this.sort(this.activeSort[0], this.activeSort[1]);
        }else{
            this.sort("name", "asc");
        }
        this.isHistory = true;
        this.historyEventHandler();
    }

    public setStateChange(){
        var self = this;
        this.history.Adapter.bind(window, "statechange", function(){
            if(!self.disableHistoryEvent) {
                self.isHistory = false;
                self.backInputs(self.history.getState().data);
            }
        });

    }

    public setActiveFacets(){
        var activeFacets = {};
        var activeSkuFacets = {};
        var facets = this.checkboxes;
        for(var i = 0; i < facets.length; i++) {
            if (facets[i].checked) {
                var input = <HTMLInputElement> facets[i];
                if (this.facets[input.name].entityType == 'PRODUCT') {
                    if (typeof activeFacets[input.name] === 'undefined') {
                        activeFacets[input.name] = [];
                    }
                    var inputValue;
                    if (input.value == "true") {
                        inputValue = true;
                    } else if (input.value == "false") {
                        inputValue = false;
                    } else {
                        inputValue = input.value;
                    }
                    activeFacets[input.name].push(inputValue);
                } else{
                    if (typeof activeSkuFacets[input.name] === 'undefined') {
                        activeSkuFacets[input.name] = [];
                    }
                    var inputValue;
                    if (input.value == "true") {
                        inputValue = true;
                    } else if (input.value == "false") {
                        inputValue = false;
                    } else {
                        inputValue = input.value;
                    }
                    activeSkuFacets[input.name].push(inputValue);
                    this.activeSkuFacets = activeSkuFacets;
                }
            }
        }

        this.activeProductFacets = activeFacets;
    }


    public setActiveRangeFacets(){
        if(this.rangeFacet != null) {
            this.activeProductRangeFacets = {};
            this.activeSkuRangeFacets = {};
            var rangeLength = this.rangeFacet.length;
            for (var i = 0; i < rangeLength; i++) {
                var facet = this.rangeFacet[i];
                var range = facet.slider.val();
                if (+range[0] != facet.rangeInfo.minValue || +range[1] != facet.rangeInfo.maxValue) {
                    if(facet.entityType == 'PRODUCT') {
                        this.activeProductRangeFacets[facet.abbreviation] = [+range[0], +range[1], true];
                    }else{
                        this.activeSkuRangeFacets[facet.abbreviation] = [+range[0], +range[1], false];
                    }
                }

            }
        }
    }

}

Re: How to Configure Solr to Search for Other Entities

Posted: Tue Sep 23, 2014 12:31 am
by nanix84
Hi rapidTransit

I'm currently looking into your implementation.
Please let us know on how to use it.
I have overridden the implementations already.
What are the database additions needed to use this.
Just site an example how you use this.

I will be using this to index property_name:
products.productOptions.type = color and display facet productOptionValues "red" ,"black" and "silver"

Thanks a lot