Adding Category
itemtype for indexing doesn’t require any specific actions, except adding corresponding changes to solr.impex
files. But in case of two different itemtypes in one SolrFacetSearchConfig
it is not enough just to change impexes you also need to adopt java source code.
Keep in mind that different SolrIndexType
in one SolrFacetSearchConfig
would end up with having two different solr cores on solr server, and you would need to adjust OOTB code to properly select, for which SolrIndexType
(solr core) search is executed. This is useful, when it is required to reuse solr configurations SolrFacetSearchConfig
between solr indexes without duplicating configurations.
For example, simple impex, which creates in one solr index two index types Product and Category with indexing fields - itemtype, code and name:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
|
$productCatalog = blogProductCatalog
$indexCatalogVersion = Online
$catalogVersions = catalogVersions(catalog(id), version);
$serverConfigName = blogSolrServerConfig
$indexConfigName = blogSolrIndexConfig
$searchConfigName = blogPageSize
$productFacetSearchConfigName = blogIndex
$categoryFacetSearchConfigName = blogCategoryIndex
$facetSearchConfigDescription = blog Solr Index
$searchIndexNamePrefix = blog
$solrProductIndexedType = blogProductType
$solrCategoryIndexedType = blogCategoryType
$indexBaseSite = basesite
$indexLanguages = en
$indexCurrencies = GBP
INSERT_UPDATE SolrIndexedType; identifier[unique = true]; type(code); variant; sorts(&sortRefID)
; $solrProductIndexedType ; Product ; false ; sortRef1, sortRef2, sortRef3, sortRef4, sortRef5, sortRef6, sortRef7
; $solrCategoryIndexedType ; Category ; false ; sortRefC6, sortRefC7, sortRefC8, sortRefC9
INSERT_UPDATE SolrFacetSearchConfig; name[unique = true] ; description ; indexNamePrefix ; languages(isocode); currencies(isocode); solrServerConfig(name); solrSearchConfig(description); solrIndexConfig(name); solrIndexedTypes(identifier) ; enabledLanguageFallbackMechanism; $catalogVersions
; $productFacetSearchConfigName ; $facetSearchConfigDescription ; $searchIndexNamePrefix ; $indexLanguages ; $indexCurrencies ; $serverConfigName ; $searchConfigName ; $indexConfigName ; $solrProductIndexedType, $solrCategoryIndexedType ; true ; $productCatalog:$indexCatalogVersion
INSERT_UPDATE SolrIndexedProperty; solrIndexedType(identifier)[unique = true]; name[unique = true]; type(code); sortableType(code); currency[default = false]; localized[default = false]; multiValue[default = false]; fieldValueProvider; valueProviderParameter
; $solrProductIndexedType ; itemtype ; string ; ; ; ; ;
; $solrProductIndexedType ; code ; string ; int ; ; ; ;
; $solrProductIndexedType ; name ; text ; sortabletext ; ; true ; ;
INSERT_UPDATE SolrIndexedProperty; solrIndexedType(identifier)[unique = true]; name[unique = true]; type(code); sortableType(code); currency[default = false]; localized[default = false]; multiValue[default = false]; useForSpellchecking[default = false]; useForAutocomplete[default = false]; fieldValueProvider; valueProviderParameter
; $solrCategoryIndexedType ; itemtype ; string ; ; ; ; ; ; ;
; $solrCategoryIndexedType ; code ; string ; ; ; ; ; false ; false ;
; $solrCategoryIndexedType ; name ; string ; ; ; true ; ; false ; false ;
|
OOTB hybris can’t search into one index different itemtypes, for example Product
and Category
. Root of the problem is in getIndexedType
method of SearchSolrQueryPopulator
, which takes the first available index itemtype from solr search config:
1
2
3
4
5
6
7
8
9
10
11
12
13
|
protected IndexedType getIndexedType(final FacetSearchConfig config) {
final IndexConfig indexConfig = config.getIndexConfig();
// Strategy for working out which of the available indexed types to use
final Collection<IndexedType> indexedTypes = indexConfig.getIndexedTypes().values();
if (indexedTypes != null && !indexedTypes.isEmpty()) {
// When there are multiple - select the first
return indexedTypes.iterator().next();
}
// No indexed types
return null;
}
|
To fix such behaviour must be extend SolrSearchQueryData
with indexTypeIdentifier
attribute and must be override populate
method of SearchSolrQueryPopulator
.
Extending data object:
1
2
3
4
5
6
|
<bean class="de.hybris.platform.commerceservices.search.solrfacetsearch.data.SolrSearchQueryData">
<!-- Property to define index type for type aware search. Default is used product. -->
<property name="indexTypeIdentifier" type="String"/>
</bean>
|
To override populate
method new BlogTypeAwareSearchSolrQueryPopulator
class would be created and commerceSearchSolrQueryPopulator
bean would be overriden with instance of new class.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
|
package com.blog.core.search.solrfacetsearch.populators;
import com.blog.core.search.solrfacetsearch.services.BlogIndexTypeService;
import de.hybris.platform.commerceservices.search.solrfacetsearch.data.SearchQueryPageableData;
import de.hybris.platform.commerceservices.search.solrfacetsearch.data.SolrSearchQueryData;
import de.hybris.platform.commerceservices.search.solrfacetsearch.data.SolrSearchRequest;
import de.hybris.platform.commerceservices.search.solrfacetsearch.populators.SearchSolrQueryPopulator;
import de.hybris.platform.solrfacetsearch.config.FacetSearchConfig;
import de.hybris.platform.solrfacetsearch.config.IndexedType;
import de.hybris.platform.solrfacetsearch.search.SearchQuery;
import org.apache.commons.lang.StringUtils;
import javax.annotation.Resource;
import static com.blog.core.constants.BlogCoreConstants.PRODUCT_INDEX_TYPE_QUALIFIER;
public class BlogTypeAwareSearchSolrQueryPopulator<INDEXED_PROPERTY_TYPE, INDEXED_TYPE_SORT_TYPE> extends SearchSolrQueryPopulator<INDEXED_PROPERTY_TYPE, INDEXED_TYPE_SORT_TYPE> {
@Resource
private BlogIndexTypeService blogIndexTypeService;
@Override
public void populate(
final SearchQueryPageableData<SolrSearchQueryData> source,
final SolrSearchRequest<FacetSearchConfig, IndexedType, INDEXED_PROPERTY_TYPE, SearchQuery, INDEXED_TYPE_SORT_TYPE> target) {
super.populate(source, target);
setTypeAwareIndexType(source, target);
}
private void setTypeAwareIndexType(SearchQueryPageableData<SolrSearchQueryData> source, SolrSearchRequest<FacetSearchConfig, IndexedType, INDEXED_PROPERTY_TYPE, SearchQuery, INDEXED_TYPE_SORT_TYPE> target) {
String indexTypeQualifier = PRODUCT_INDEX_TYPE_QUALIFIER;
// Set index type qualifier from passed data
if (StringUtils.isNotBlank(source.getSearchQueryData().getIndexTypeIdentifier())) {
indexTypeQualifier = source.getSearchQueryData().getIndexTypeIdentifier();
}
// Reset index type
if (target.getIndexedType() != null && !isProperIndexType(target, indexTypeQualifier)) {
IndexedType properIndexType = blogIndexTypeService.getIndexedType(target.getFacetSearchConfig(), indexTypeQualifier);
if (properIndexType != null) {
// We can only search one core so select the indexed type
target.setIndexedType(properIndexType);
// Create the solr search query for the config and type (this sets-up the default page size and sort order)
final SearchQuery searchQuery = createSearchQuery(target.getFacetSearchConfig(), target.getIndexedType(), source.getSearchQueryData().getSearchQueryContext(), source
.getSearchQueryData().getFreeTextSearch());
searchQuery.setCatalogVersions(target.getCatalogVersions());
searchQuery.setCurrency(getCommonI18NService().getCurrentCurrency().getIsocode());
searchQuery.setLanguage(getCommonI18NService().getCurrentLanguage().getIsocode());
target.setSearchQuery(searchQuery);
}
}
}
private boolean isProperIndexType(SolrSearchRequest<FacetSearchConfig, IndexedType, INDEXED_PROPERTY_TYPE, SearchQuery, INDEXED_TYPE_SORT_TYPE> target, String indexTypeQualifier) {
return target.getIndexedType().getIdentifier().toLowerCase().contains(indexTypeQualifier);
}
}
|
1
2
3
4
5
|
<alias name="blogTypeAwareSearchSolrQueryPopulator" alias="commerceSearchSolrQueryPopulator"/>
<bean id="blogTypeAwareSearchSolrQueryPopulator"
class="com.blog.core.search.solrfacetsearch.populators.BlogTypeAwareSearchSolrQueryPopulator"
parent="defaultCommerceSearchSolrQueryPopulator"/>
|
Methods for resolving current index would be put in separate BlogIndexTypeService
class, which would be reused for facades and services.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
|
package com.blog.core.search.solrfacetsearch.services;
import de.hybris.platform.solrfacetsearch.config.FacetSearchConfig;
import de.hybris.platform.solrfacetsearch.config.IndexConfig;
import de.hybris.platform.solrfacetsearch.config.IndexedType;
import de.hybris.platform.solrfacetsearch.model.config.SolrFacetSearchConfigModel;
import de.hybris.platform.solrfacetsearch.model.config.SolrIndexedTypeModel;
import org.jetbrains.annotations.Nullable;
import java.util.Collection;
import java.util.List;
import static com.blog.core.constants.BlogCoreConstants.PRODUCT_INDEX_TYPE_QUALIFIER;
public class BlogIndexTypeService {
// FOR JALO LAYER
@Nullable
public IndexedType getDefaultIndexedType(FacetSearchConfig config) {
return getIndexedType(config, PRODUCT_INDEX_TYPE_QUALIFIER);
}
@Nullable
public IndexedType getIndexedType(FacetSearchConfig config, String indexTypeIdentifier) {
final IndexConfig indexConfig = config.getIndexConfig();
// Strategy for working out which of the available indexed types to use
final Collection<IndexedType> indexedTypes = indexConfig.getIndexedTypes().values();
for (IndexedType indexedType : indexedTypes) {
if (isProperIndexType(indexedType.getIdentifier(), indexTypeIdentifier)) {
return indexedType;
}
}
// No indexed types
return null;
}
// FOR SERVICE LAYER
@Nullable
public SolrIndexedTypeModel getDefaultIndexedType(SolrFacetSearchConfigModel config) {
return getIndexedType(config, PRODUCT_INDEX_TYPE_QUALIFIER);
}
@Nullable
public SolrIndexedTypeModel getIndexedType(SolrFacetSearchConfigModel config, String indexTypeIdentifier) {
List<SolrIndexedTypeModel> indexedTypes = config.getSolrIndexedTypes();
// Strategy for working out which of the available indexed types to use
for (SolrIndexedTypeModel indexedType : indexedTypes) {
if (isProperIndexType(indexedType.getIdentifier(), indexTypeIdentifier)) {
return indexedType;
}
}
// No indexed types
return null;
}
// Check method
private boolean isProperIndexType(String indexIdentifier, String properIndexType) {
return indexIdentifier.toLowerCase().contains(properIndexType);
}
}
|
OOTB hybris provides default mechanizm to search only Product itemtypes. Such possibility is provided by DefaultSolrProductSearchService
and DefaultSolrProductSearchFacade
.
After implementing of type aware solr index search, could be implemented services and facades for solr searhc of Category
itemtype.
For basic usage it will be enough to implement textSearch
and searchAgain
methods:
1
2
3
4
5
6
7
8
9
10
11
12
|
package com.blog.core.search.solrfacetsearch.solrcategorysearch;
import de.hybris.platform.commerceservices.search.facetdata.CategorySearchPageData;
import de.hybris.platform.commerceservices.search.pagedata.PageableData;
public interface BlogSolrCategorySearchService<STATE, ITEM, RESULT extends CategorySearchPageData<STATE, ITEM>> {
RESULT textSearch(String text, PageableData pageableData);
RESULT searchAgain(STATE searchQueryData, PageableData pageableData);
}
|
Implementation of this methods are similar to DefaultSolrProductSearchService
the difference is in explicit set of Category
index type into SolrSearchQueryData:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
|
package com.blog.core.search.solrfacetsearch.solrcategorysearch.impl;
import com.blog.core.search.solrfacetsearch.solrcategorysearch.BlogSolrCategorySearchService;
import de.hybris.platform.commerceservices.search.facetdata.CategorySearchPageData;
import de.hybris.platform.commerceservices.search.pagedata.PageableData;
import de.hybris.platform.commerceservices.search.solrfacetsearch.data.SearchQueryPageableData;
import de.hybris.platform.commerceservices.search.solrfacetsearch.data.SolrSearchQueryData;
import de.hybris.platform.commerceservices.search.solrfacetsearch.data.SolrSearchQueryTermData;
import de.hybris.platform.commerceservices.search.solrfacetsearch.data.SolrSearchRequest;
import de.hybris.platform.commerceservices.search.solrfacetsearch.data.SolrSearchResponse;
import org.springframework.core.convert.converter.Converter;
import java.util.Collections;
import static com.blog.core.constants.BlogCoreConstants.CATEGORY_INDEX_TYPE_QUALIFIER;
import static de.hybris.platform.servicelayer.util.ServicesUtil.validateParameterNotNull;
/**
* Created by clutcher on 16.01.17.
* Solr category search service
*/
public class BlogSolrCategorySearchServiceImpl<ITEM> implements BlogSolrCategorySearchService<SolrSearchQueryData, ITEM, CategorySearchPageData<SolrSearchQueryData, ITEM>> {
private Converter<SearchQueryPageableData<SolrSearchQueryData>, SolrSearchRequest> searchQueryPageableConverter;
private Converter<SolrSearchRequest, SolrSearchResponse> searchRequestConverter;
private Converter<SolrSearchResponse, CategorySearchPageData<SolrSearchQueryData, ITEM>> searchResponseConverter;
@Override
public CategorySearchPageData<SolrSearchQueryData, ITEM> textSearch(String text, PageableData pageableData) {
final SolrSearchQueryData searchQueryData = new SolrSearchQueryData();
searchQueryData.setFreeTextSearch(text);
searchQueryData.setFilterTerms(Collections.<SolrSearchQueryTermData>emptyList());
return doSearch(searchQueryData, pageableData);
}
@Override
public CategorySearchPageData<SolrSearchQueryData, ITEM> searchAgain(SolrSearchQueryData searchQueryData, PageableData pageableData) {
return doSearch(searchQueryData, pageableData);
}
private CategorySearchPageData<SolrSearchQueryData, ITEM> doSearch(
final SolrSearchQueryData searchQueryData, final PageableData pageableData) {
validateParameterNotNull(searchQueryData, "SearchQueryData cannot be null");
searchQueryData.setIndexTypeIdentifier(CATEGORY_INDEX_TYPE_QUALIFIER);
// Create the SearchQueryPageableData that contains our parameters
final SearchQueryPageableData<SolrSearchQueryData> searchQueryPageableData = buildSearchQueryPageableData(searchQueryData,
pageableData);
// Build up the search request
final SolrSearchRequest solrSearchRequest = getSearchQueryPageableConverter().convert(searchQueryPageableData);
// Execute the search
final SolrSearchResponse solrSearchResponse = getSearchRequestConverter().convert(solrSearchRequest);
// Convert the response
return getSearchResponseConverter().convert(solrSearchResponse);
}
private SearchQueryPageableData<SolrSearchQueryData> buildSearchQueryPageableData(final SolrSearchQueryData searchQueryData,
final PageableData pageableData) {
final SearchQueryPageableData<SolrSearchQueryData> searchQueryPageableData = new SearchQueryPageableData<>();
searchQueryPageableData.setSearchQueryData(searchQueryData);
searchQueryPageableData.setPageableData(pageableData);
return searchQueryPageableData;
}
public Converter<SearchQueryPageableData<SolrSearchQueryData>, SolrSearchRequest> getSearchQueryPageableConverter() {
return searchQueryPageableConverter;
}
public void setSearchQueryPageableConverter(Converter<SearchQueryPageableData<SolrSearchQueryData>, SolrSearchRequest> searchQueryPageableConverter) {
this.searchQueryPageableConverter = searchQueryPageableConverter;
}
public Converter<SolrSearchRequest, SolrSearchResponse> getSearchRequestConverter() {
return searchRequestConverter;
}
public void setSearchRequestConverter(Converter<SolrSearchRequest, SolrSearchResponse> searchRequestConverter) {
this.searchRequestConverter = searchRequestConverter;
}
public Converter<SolrSearchResponse, CategorySearchPageData<SolrSearchQueryData, ITEM>> getSearchResponseConverter() {
return searchResponseConverter;
}
public void setSearchResponseConverter(Converter<SolrSearchResponse, CategorySearchPageData<SolrSearchQueryData, ITEM>> searchResponseConverter) {
this.searchResponseConverter = searchResponseConverter;
}
}
|
And finally facades could be implemented and used on storefront:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
package com.blog.facades.search.solrcategorysearch;
import de.hybris.platform.commercefacades.product.data.CategoryData;
import de.hybris.platform.commercefacades.search.data.SearchStateData;
import de.hybris.platform.commerceservices.search.facetdata.CategorySearchPageData;
import de.hybris.platform.commerceservices.search.pagedata.PageableData;
public interface BlogSolrCategorySearchFacade<ITEM extends CategoryData> {
CategorySearchPageData<SearchStateData, ITEM> textSearch(String text);
CategorySearchPageData<SearchStateData, ITEM> textSearch(SearchStateData searchState, PageableData pageableData);
}
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
|
package com.blog.facades.search.solrcategorysearch.impl;
import com.blog.core.search.solrfacetsearch.solrcategorysearch.BlogSolrCategorySearchService;
import com.blog.facades.search.solrcategorysearch.BlogSolrCategorySearchFacade;
import de.hybris.platform.commercefacades.product.data.CategoryData;
import de.hybris.platform.commercefacades.search.data.SearchQueryData;
import de.hybris.platform.commercefacades.search.data.SearchStateData;
import de.hybris.platform.commerceservices.search.facetdata.CategorySearchPageData;
import de.hybris.platform.commerceservices.search.pagedata.PageableData;
import de.hybris.platform.commerceservices.search.resultdata.SearchResultValueData;
import de.hybris.platform.commerceservices.search.solrfacetsearch.data.SolrSearchQueryData;
import de.hybris.platform.commerceservices.threadcontext.ThreadContextService;
import de.hybris.platform.servicelayer.dto.converter.Converter;
import org.springframework.util.Assert;
import javax.annotation.Resource;
public class BlogSolrCategorySearchFacadeImpl<ITEM extends CategoryData> implements BlogSolrCategorySearchFacade<ITEM> {
@Resource
private ThreadContextService threadContextService;
@Resource
private BlogSolrCategorySearchService<SolrSearchQueryData, SearchResultValueData, CategorySearchPageData<SolrSearchQueryData, SearchResultValueData>> categorySearchService;
@Resource
private Converter<CategorySearchPageData<SolrSearchQueryData, SearchResultValueData>, CategorySearchPageData<SearchStateData, ITEM>> categorySearchPageConverter;
@Resource(name = "solrSearchQueryDecoder")
private Converter<SearchQueryData, SolrSearchQueryData> searchQueryDecoder;
@Override
public CategorySearchPageData<SearchStateData, ITEM> textSearch(String text) {
return threadContextService.executeInContext(
new ThreadContextService.Executor<CategorySearchPageData<SearchStateData, ITEM>, ThreadContextService.Nothing>() {
@Override
public CategorySearchPageData<SearchStateData, ITEM> execute() {
return categorySearchPageConverter.convert(categorySearchService.textSearch(text, null));
}
});
}
@Override
public CategorySearchPageData<SearchStateData, ITEM> textSearch(SearchStateData searchState, PageableData pageableData) {
Assert.notNull(searchState, "SearchStateData must not be null.");
return threadContextService.executeInContext(
new ThreadContextService.Executor<CategorySearchPageData<SearchStateData, ITEM>, ThreadContextService.Nothing>() {
@Override
public CategorySearchPageData<SearchStateData, ITEM> execute() {
return categorySearchPageConverter.convert(
categorySearchService.searchAgain(decodeState(searchState), pageableData));
}
});
}
private SolrSearchQueryData decodeState(final SearchStateData searchState) {
return searchQueryDecoder.convert(searchState.getQuery());
}
}
|
Happy searching!