/*
* #%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;
import net.sf.ehcache.Cache;
import net.sf.ehcache.CacheManager;
import net.sf.ehcache.Element;
import org.apache.commons.lang.StringUtils;
import org.broadleafcommerce.common.exception.ServiceException;
import org.broadleafcommerce.core.catalog.domain.Category;
import org.broadleafcommerce.core.catalog.domain.Product;
import org.broadleafcommerce.core.catalog.service.CatalogService;
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.SearchFacet;
import org.broadleafcommerce.core.search.domain.SearchFacetDTO;
import org.broadleafcommerce.core.search.domain.SearchFacetRange;
import org.broadleafcommerce.core.search.domain.SearchFacetResultDTO;
import org.springframework.stereotype.Service;
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;
@Service("blSearchService")
public class DatabaseSearchServiceImpl implements SearchService {
@Resource(name = "blCatalogService")
protected CatalogService catalogService;
@Resource(name = "blSearchFacetDao")
protected SearchFacetDao searchFacetDao;
@Resource(name = "blFieldDao")
protected FieldDao fieldDao;
protected static String CACHE_NAME = "blStandardElements";
protected static String CACHE_KEY_PREFIX = "facet:";
protected Cache cache = CacheManager.getInstance().getCache(CACHE_NAME);
@Override
public ProductSearchResult findExplicitProductsByCategory(Category category, ProductSearchCriteria searchCriteria) throws ServiceException {
throw new UnsupportedOperationException("See findProductsByCategory or use the SolrSearchService implementation");
}
@Override
public ProductSearchResult findProductsByCategoryAndQuery(Category category, String query, ProductSearchCriteria searchCriteria) throws ServiceException {
throw new UnsupportedOperationException("This operation is only supported by the SolrSearchService by default");
}
@Override
public ProductSearchResult findProductsByCategory(Category category, ProductSearchCriteria searchCriteria) {
ProductSearchResult result = new ProductSearchResult();
setQualifiedKeys(searchCriteria);
List<Product> products = catalogService.findFilteredActiveProductsByCategory(category, searchCriteria);
List<SearchFacetDTO> facets = getCategoryFacets(category);
setActiveFacets(facets, searchCriteria);
result.setProducts(products);
result.setFacets(facets);
result.setTotalResults(products.size());
result.setPage(1);
result.setPageSize(products.size());
return result;
}
@Override
public ProductSearchResult findProductsByQuery(String query, ProductSearchCriteria searchCriteria) {
ProductSearchResult result = new ProductSearchResult();
setQualifiedKeys(searchCriteria);
List<Product> products = catalogService.findFilteredActiveProductsByQuery(query, searchCriteria);
List<SearchFacetDTO> facets = getSearchFacets();
setActiveFacets(facets, searchCriteria);
result.setProducts(products);
result.setFacets(facets);
result.setTotalResults(products.size());
result.setPage(1);
result.setPageSize(products.size());
return result;
}
@Override
@SuppressWarnings("unchecked")
public List<SearchFacetDTO> getSearchFacets() {
List<SearchFacetDTO> facets = null;
String cacheKey = CACHE_KEY_PREFIX + "blc-search";
Element element = cache.get(cacheKey);
if (element != null) {
facets = (List<SearchFacetDTO>) element.getValue();
}
if (facets == null) {
facets = buildSearchFacetDtos(searchFacetDao.readAllSearchFacets());
element = new Element(cacheKey, facets);
cache.put(element);
}
return facets;
}
@Override
@SuppressWarnings("unchecked")
public List<SearchFacetDTO> getCategoryFacets(Category category) {
List<SearchFacetDTO> facets = null;
String cacheKey = CACHE_KEY_PREFIX + "category:" + category.getId();
Element element = cache.get(cacheKey);
if (element != null) {
facets = (List<SearchFacetDTO>) element.getValue();
}
if (facets == null) {
List<CategorySearchFacet> categorySearchFacets = category.getCumulativeSearchFacets();
List<SearchFacet> searchFacets = new ArrayList<SearchFacet>();
for (CategorySearchFacet categorySearchFacet : categorySearchFacets) {
searchFacets.add(categorySearchFacet.getSearchFacet());
}
facets = buildSearchFacetDtos(searchFacets);
element = new Element(cacheKey, facets);
cache.put(element);
}
return facets;
}
/**
* Perform any necessary conversion of the key to be used by the search service
* @param criteria
*/
protected void setQualifiedKeys(ProductSearchCriteria criteria) {
// Convert the filter criteria url keys
Map<String, String[]> convertedFilterCriteria = new HashMap<String, String[]>();
for (Entry<String, String[]> entry : criteria.getFilterCriteria().entrySet()) {
Field field = fieldDao.readFieldByAbbreviation(entry.getKey());
if (field != null) {
String qualifiedFieldName = getDatabaseQualifiedFieldName(field.getQualifiedFieldName());
convertedFilterCriteria.put(qualifiedFieldName, entry.getValue());
}
}
criteria.setFilterCriteria(convertedFilterCriteria);
// Convert the sort criteria url keys
if (StringUtils.isNotBlank(criteria.getSortQuery())) {
StringBuilder convertedSortQuery = new StringBuilder();
for (String sortQuery : criteria.getSortQuery().split(",")) {
String[] sort = sortQuery.split(" ");
if (sort.length == 2) {
String key = sort[0];
Field field = fieldDao.readFieldByAbbreviation(key);
String qualifiedFieldName = getDatabaseQualifiedFieldName(field.getQualifiedFieldName());
if (convertedSortQuery.length() > 0) {
convertedSortQuery.append(",");
}
convertedSortQuery.append(qualifiedFieldName).append(" ").append(sort[1]);
}
}
criteria.setSortQuery(convertedSortQuery.toString());
}
}
/**
* From the Field's qualifiedName, build out the qualified name to be used by the ProductDao
* to find the requested products.
*
* @param qualifiedFieldName
* @return the database qualified name
*/
protected String getDatabaseQualifiedFieldName(String qualifiedFieldName) {
if (qualifiedFieldName.contains("productAttributes")) {
return qualifiedFieldName.replace("product.", "");
} else if (qualifiedFieldName.contains("defaultSku")) {
return qualifiedFieldName.replace("product.", "");
} else {
return qualifiedFieldName;
}
}
protected void setActiveFacets(List<SearchFacetDTO> facets, ProductSearchCriteria searchCriteria) {
for (SearchFacetDTO facet : facets) {
String qualifiedFieldName = getDatabaseQualifiedFieldName(facet.getFacet().getField().getQualifiedFieldName());
for (Entry<String, String[]> entry : searchCriteria.getFilterCriteria().entrySet()) {
if (qualifiedFieldName.equals(entry.getKey())) {
facet.setActive(true);
}
}
}
}
/**
* Create the wrapper DTO around the SearchFacet
* @param categoryFacets
* @return the wrapper DTO
*/
protected List<SearchFacetDTO> buildSearchFacetDtos(List<SearchFacet> categoryFacets) {
List<SearchFacetDTO> facets = new ArrayList<SearchFacetDTO>();
for (SearchFacet facet : categoryFacets) {
SearchFacetDTO dto = new SearchFacetDTO();
dto.setFacet(facet);
dto.setShowQuantity(false);
dto.setFacetValues(getFacetValues(facet));
dto.setActive(false);
facets.add(dto);
}
return facets;
}
protected List<SearchFacetResultDTO> getFacetValues(SearchFacet facet) {
if (facet.getSearchFacetRanges().size() > 0) {
return getRangeFacetValues(facet);
} else {
return getMatchFacetValues(facet);
}
}
protected List<SearchFacetResultDTO> getRangeFacetValues(SearchFacet facet) {
List<SearchFacetResultDTO> results = new ArrayList<SearchFacetResultDTO>();
List<SearchFacetRange> ranges = facet.getSearchFacetRanges();
Collections.sort(ranges, new Comparator<SearchFacetRange>() {
public int compare(SearchFacetRange o1, SearchFacetRange o2) {
return o1.getMinValue().compareTo(o2.getMinValue());
}
});
for (SearchFacetRange range : ranges) {
SearchFacetResultDTO dto = new SearchFacetResultDTO();
dto.setMinValue(range.getMinValue());
dto.setMaxValue(range.getMaxValue());
dto.setFacet(facet);
results.add(dto);
}
return results;
}
protected List<SearchFacetResultDTO> getMatchFacetValues(SearchFacet facet) {
List<SearchFacetResultDTO> results = new ArrayList<SearchFacetResultDTO>();
String qualifiedFieldName = facet.getField().getQualifiedFieldName();
qualifiedFieldName = getDatabaseQualifiedFieldName(qualifiedFieldName);
List<String> values = searchFacetDao.readDistinctValuesForField(qualifiedFieldName, String.class);
Collections.sort(values);
for (String value : values) {
SearchFacetResultDTO dto = new SearchFacetResultDTO();
dto.setValue(value);
dto.setFacet(facet);
results.add(dto);
}
return results;
}
@Override
public void rebuildIndex() {
throw new UnsupportedOperationException("Indexes are not supported by this implementation");
}
}