How to use same solr index for different index types

There are cases, when it is required to use same solr index to index/search different itemtypes. For example, it is required to have in search box autocompletion different types, like Product and Media. Another common use case would be display on search result page search results of different itemtypes mixed together (for example, Product, Media, custom Resource itemtype).

OOTB SAP Commerce (Hybris) provides only partial support for that. It is possible to attach different itemtypes to same SolrFacetSearchConfig, but it would be still using different solr cores and would require two different search requests to solr. Below for example implementation would be used two itemtypes Resource and Media for indexing in same solr query.

Add support of multiple itemtypes (SolrIndexedType) for SolrFacetSearchConfig

First of all we would need to build infrastructure from article How to search different itemtypes with one SolrFacetSearchConfig. This would allow to attach multiple itemtypes to same SolrFacetSearchConfig using intermediate SolrIndexedType and be able to programmatically filter out results by itemtype (return only Media results).

Use same solr core for different SolrIndexedType

Each SolrIndexedType would create own Solr Core by default. To reuse same Solr Core by all SolrIndexedTypes, we need to specify same indexName attribute on SolrIndexedType, so DefaultIndexNameResolver would resolve same Solr Core for different SolrIndexedType.

1
2
3
INSERT_UPDATE SolrIndexedType; identifier[unique = true]; type(code); indexName
                             ; resourceIndexedType      ; Resource  ; Resource
                             ; mediaIndexedType         ; Media     ; Resource

Adjust indexing process to work with multiple SolrIndexedType

Main entry point for indexing is OOTB DefaultIndexerService. Main purpose of the service is to create IndexerStrategy and pass control to it. IndexerStrategy is designed to create IndexerWorkers and Thread Pool for execution of workers. Also IndexerStrategy creates IndexerContext, which is needed for proper IndexerWorkers creation. IndexerContext is created by IndexerContextFactory.

In default implementation of IndexerContextFactory exists hooking mechanizm, which executes IndexerListener/ExtendedIndexerListener in different stages of indexing process. OOTB IndexerOperationListener is used to clean up Solr Core before two phase full solr reindex, and unfortunately OOTB implementation would clean up Solr Core per each SolrIndexedType, even if they are for same Solr Indexes.

Custom implementation MultipleIndexedTypeIndexerOperationListener would be used to replace IndexerOperationListener to execute clean up only for first SolrIndexedType. Due to OOTB IndexConfig#getIndexedTypes uses LinkedHashMap implementation, we can run clean up only when current SolrIndexedType is same as first SolrIndexedType in the map. Index activation and switching in afterIndex method would be executed after all IndexedTypes would be processed.

MultipleIndexedTypeOptimizeModeListener custom implementation would replace OptimizeModeListener to execute solr index optimization only after all IndexedTypes would be processed.

Both implementations are using SolrMultiIndexedTypesUtils, code for which is provided at bottom of page in Appendix section.

 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.indexer.listeners;

import com.blog.core.utils.SolrMultiIndexedTypesUtils;
import de.hybris.platform.solrfacetsearch.config.FacetSearchConfig;
import de.hybris.platform.solrfacetsearch.config.IndexConfig;
import de.hybris.platform.solrfacetsearch.config.IndexOperation;
import de.hybris.platform.solrfacetsearch.config.IndexedType;
import de.hybris.platform.solrfacetsearch.enums.IndexMode;
import de.hybris.platform.solrfacetsearch.indexer.IndexerContext;
import de.hybris.platform.solrfacetsearch.indexer.exceptions.IndexerException;
import de.hybris.platform.solrfacetsearch.indexer.listeners.IndexerOperationListener;
import de.hybris.platform.solrfacetsearch.model.SolrIndexModel;
import de.hybris.platform.solrfacetsearch.solr.Index;
import de.hybris.platform.solrfacetsearch.solr.SolrSearchProvider;
import de.hybris.platform.solrfacetsearch.solr.exceptions.SolrServiceException;

public class MultipleIndexedTypeIndexerOperationListener extends IndexerOperationListener {

    public void afterPrepareContext(final IndexerContext context) throws IndexerException {
        try {
            FacetSearchConfig facetSearchConfig = context.getFacetSearchConfig();
            IndexedType indexedType = context.getIndexedType();
            IndexConfig indexConfig = facetSearchConfig.getIndexConfig();
            SolrSearchProvider solrSearchProvider = getSolrSearchProviderFactory().getSearchProvider(facetSearchConfig, indexedType);
            IndexOperation indexOperation = context.getIndexOperation();
            if (indexOperation == IndexOperation.FULL) {
                String newQualifier = "default";
                if (indexConfig.getIndexMode() == IndexMode.TWO_PHASE) {
                    newQualifier = this.resolveStagedQualifier(context.getIndex());
                }

                Index newIndex = solrSearchProvider.resolveIndex(facetSearchConfig, indexedType, newQualifier);
                context.setIndex(newIndex);
            }

            Index index = context.getIndex();
            SolrIndexModel solrIndex = getSolrIndexService().getOrCreateIndex(facetSearchConfig.getName(), indexedType.getIdentifier(), index.getQualifier());
            getSolrIndexOperationService().startOperation(solrIndex, context.getIndexOperationId(), indexOperation, context.isExternalIndexOperation());
            // ! Resolving when to delete all data from index differs from parent class implementation.
            // ! For multiple index types case we should clean up solr index only for first index type.
            // ! Otherwise, final index would contain data only for last index type.
            if (indexOperation == IndexOperation.FULL && SolrMultiIndexedTypesUtils.isFirstIteration(context)) {
                solrSearchProvider.createIndex(context.getIndex());
                solrSearchProvider.exportConfig(context.getIndex());
                if (indexConfig.getIndexMode() == IndexMode.TWO_PHASE) {
                    solrSearchProvider.deleteAllDocuments(index);
                }
            }

        } catch (SolrServiceException ex) {
            throw new IndexerException(ex);
        }
    }

    @Override
    public void afterIndex(IndexerContext context) throws IndexerException {
        // ! Resolving when activate and switch index differs from parent class implementation.
        if (SolrMultiIndexedTypesUtils.isLastIteration(context)) {
            super.afterIndex(context);
        }
    }
}

During indexing process SAP Commerce creates instances of this SolrIndexModel in DB to know in which Solr Core index requests should go and in which Solr Core search requests should go. Mainly needed for two-phase indexing. OOTB implementation has mandatory attribute indexType on SolrIndexModel, which binds one Solr Core to one IndexedType. For MultiTypeIndex index we want to use few IndexTypes for same Solr Core, so a new itemtype MultiTypeSolrIndex must be created to redeclare indexedType attribute and make it optional.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18

<itemtype code="MultiTypeSolrIndex" extends="SolrIndex">
    <attributes>
        <attribute qualifier="indexedType" type="SolrIndexedType" redeclare="true">
            <persistence type="property"/>
            <modifiers optional="true"/>
        </attribute>
        <attribute qualifier="indexName" type="java.lang.String">
            <persistence type="property"/>
            <modifiers optional="true"/>
        </attribute>
    </attributes>
    <indexes>
        <index name="solrIndexByName">
            <key attribute="indexName"/>
        </index>
    </indexes>
</itemtype>

MultiTypeSolrIndexService overrides OOTB DefaultSolrIndexService implementation to create instance of MultiTypeSolrIndexModel in case current SolrFacetSearchConfigModel is MultiTypeIndex. For similar purpose should be created MultiTypeSolrIndexDao, which overrides DefaultSolrIndexDao, to support search in DB for MultiTypeSolrIndexModel. Both implementation uses SolrMultiIndexedTypesUtils to check if index is index with multiple itemtypes. Code for that class is at the bottom of page in Appendix section.

 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
package com.blog.core.search.solrfacetsearch.solr.impl;

import com.blog.core.model.MultiTypeSolrIndexModel;
import com.blog.core.utils.SolrMultiIndexedTypesUtils;
import de.hybris.platform.servicelayer.exceptions.ModelSavingException;
import de.hybris.platform.solrfacetsearch.model.SolrIndexModel;
import de.hybris.platform.solrfacetsearch.model.config.SolrFacetSearchConfigModel;
import de.hybris.platform.solrfacetsearch.model.config.SolrIndexedTypeModel;
import de.hybris.platform.solrfacetsearch.solr.exceptions.SolrServiceException;
import de.hybris.platform.solrfacetsearch.solr.impl.DefaultSolrIndexService;

public class MultiTypeSolrIndexService extends DefaultSolrIndexService {

    @Override
    protected SolrIndexModel createIndex(SolrFacetSearchConfigModel facetSearchConfig, SolrIndexedTypeModel indexedType, String qualifier) throws SolrServiceException {
        String indexName = indexedType.getIndexName();
        return SolrMultiIndexedTypesUtils.isMultiTypeIndexType(facetSearchConfig, indexedType)
                ? createMultiTypeSolrIndex(facetSearchConfig, qualifier, indexName)
                : super.createIndex(facetSearchConfig, indexedType, qualifier);
    }


    private MultiTypeSolrIndexModel createMultiTypeSolrIndex(final SolrFacetSearchConfigModel facetSearchConfig, final String qualifier,
                                                             final String indexName) throws SolrServiceException {
        MultiTypeSolrIndexModel indexModel = getModelService().create(MultiTypeSolrIndexModel.class);
        indexModel.setFacetSearchConfig(facetSearchConfig);
        indexModel.setIndexName(indexName);
        indexModel.setQualifier(qualifier);

        try {
            getModelService().save(indexModel);
        } catch (ModelSavingException exception) {
            throw new SolrServiceException(exception);
        }

        return indexModel;
    }
}
 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
94
package com.blog.core.search.solrfacetsearch.daos.impl;

import com.blog.core.model.MultiTypeSolrIndexModel;
import com.blog.core.utils.SolrMultiIndexedTypesUtils;
import de.hybris.platform.servicelayer.search.SearchResult;
import de.hybris.platform.servicelayer.util.ServicesUtil;
import de.hybris.platform.solrfacetsearch.daos.impl.DefaultSolrIndexDao;
import de.hybris.platform.solrfacetsearch.model.SolrIndexModel;
import de.hybris.platform.solrfacetsearch.model.config.SolrFacetSearchConfigModel;
import de.hybris.platform.solrfacetsearch.model.config.SolrIndexedTypeModel;

import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

public class MultiTypeSolrIndexDao extends DefaultSolrIndexDao {

    private static final String MULTI_TYPE_INDEX_CONFIG_QUERY = "SELECT {" + MultiTypeSolrIndexModel.PK + "} FROM {" + MultiTypeSolrIndexModel._TYPECODE + "}" +
            " " +
            " WHERE {" + MultiTypeSolrIndexModel.FACETSEARCHCONFIG + "}=?" + MultiTypeSolrIndexModel.FACETSEARCHCONFIG +
            " AND {" + MultiTypeSolrIndexModel.INDEXNAME + "}=?" + MultiTypeSolrIndexModel.INDEXNAME;

    private static final String MULTI_TYPE_INDEX_CONFIG_ACTIVE_QUERY = MULTI_TYPE_INDEX_CONFIG_QUERY + " AND {" + MultiTypeSolrIndexModel.ACTIVE + "}=?"
            + MultiTypeSolrIndexModel.ACTIVE;

    private static final String MULTI_TYPE_INDEX_CONFIG_WITH_QUALIFIER_QUERY = MULTI_TYPE_INDEX_CONFIG_QUERY
            + " AND {" + MultiTypeSolrIndexModel.QUALIFIER + "}=?" + MultiTypeSolrIndexModel.QUALIFIER;

    @Override
    public SolrIndexModel findIndexByConfigAndTypeAndQualifier(final SolrFacetSearchConfigModel facetSearchConfig, final SolrIndexedTypeModel indexedType,
                                                               final String qualifier) {
        if (SolrMultiIndexedTypesUtils.isMultiTypeIndexType(facetSearchConfig, indexedType)) {
            final Map<String, Object> queryParams = createQueryParams(facetSearchConfig, indexedType.getIndexName(), qualifier, null);
            final Collection<SolrIndexModel> result = doSearch(MULTI_TYPE_INDEX_CONFIG_WITH_QUALIFIER_QUERY, queryParams);
            return validateAndReturnSingleResult(queryParams, result);
        }

        return super.findIndexByConfigAndTypeAndQualifier(facetSearchConfig, indexedType, qualifier);


    }

    @Override
    public SolrIndexModel findActiveIndexByConfigAndType(final SolrFacetSearchConfigModel facetSearchConfig, final SolrIndexedTypeModel indexedType) {
        if (SolrMultiIndexedTypesUtils.isMultiTypeIndexType(facetSearchConfig, indexedType)) {
            final Map<String, Object> queryParams = createQueryParams(facetSearchConfig, indexedType.getIndexName(), null, true);
            final Collection<SolrIndexModel> result = doSearch(MULTI_TYPE_INDEX_CONFIG_ACTIVE_QUERY, queryParams);
            return validateAndReturnSingleResult(queryParams, result);
        }

        return super.findActiveIndexByConfigAndType(facetSearchConfig, indexedType);
    }

    @Override
    public List<SolrIndexModel> findIndexesByConfigAndType(final SolrFacetSearchConfigModel facetSearchConfig, final SolrIndexedTypeModel indexedType) {
        if (SolrMultiIndexedTypesUtils.isMultiTypeIndexType(facetSearchConfig, indexedType)) {
            final Map<String, Object> queryParams = createQueryParams(facetSearchConfig, indexedType.getIndexName(), null, null);
            return doSearch(MULTI_TYPE_INDEX_CONFIG_QUERY, queryParams);
        }

        return super.findIndexesByConfigAndType(facetSearchConfig, indexedType);

    }

    private Map<String, Object> createQueryParams(final SolrFacetSearchConfigModel facetSearchConfig, final String indexedName,
                                                  final String qualifier, final Boolean active) {
        Map<String, Object> queryParams = new HashMap<>();

        queryParams.put(MultiTypeSolrIndexModel.FACETSEARCHCONFIG, facetSearchConfig);
        queryParams.put(MultiTypeSolrIndexModel.INDEXNAME, indexedName);

        if (qualifier != null) {
            queryParams.put(MultiTypeSolrIndexModel.QUALIFIER, qualifier);
        }

        if (active != null) {
            queryParams.put(MultiTypeSolrIndexModel.ACTIVE, active);
        }

        return queryParams;
    }

    private List<SolrIndexModel> doSearch(final String query, final Map<String, Object> queryParams) {
        final SearchResult<SolrIndexModel> search = getFlexibleSearchService().search(query, queryParams);
        return search.getResult();
    }

    private SolrIndexModel validateAndReturnSingleResult(final Map<String, Object> queryParams, final Collection<SolrIndexModel> result) {
        ServicesUtil.validateIfSingleResult(result, "Index not found: " + queryParams, "More than one index was found: " + queryParams);
        return result.iterator().next();
    }

}

Adjust solr search process to work with multiple SolrIndexedType

Solr search query creation

OOTB Solr searching is using SolrIndexedType to resolve SolrSearchQueryTemplate and corresponding SolrSearchQueryProperty to build solr search query. So for SolrFacetSearchConfig with multiple SolrIndexedType during search should still be defined one SolrIndexedType, which would be used to create solr search query, even if Solr Core contains different itemtypes in index.

By default, in SearchSolrQueryPopulator#getIndexedType is used first SolrIndexedType from the list. It means that we would always crate search query using search templates and search properties from first SolrIndexedType (in our example it is Resource itemtype) for searching both Resource and Media. That behavior would not allow to create boosts or filters for Media specific attributes. To bypass that limitation custom MultipleSolrIndexedTypesSearchSolrQueryPopulator should be implemented to create SearchQuery for all SolrIndexedTypes and merge results into one SearchQuery.

In article How to search different itemtypes with one SolrFacetSearchConfig is used name BlogTypeAwareSearchSolrQueryPopulator, so it should be renamed to MultipleSolrIndexedTypesSearchSolrQueryPopulator. To be able to fall back to default behaviour (in other words to filter results by one itemtype) can be used indexTypeIdentifier attribute from article. MultipleSolrIndexedTypesSearchSolrQueryPopulator implementation in addittion to SolrMultiIndexedTypesUtils also uses MultiIndexTypeSearchQueryBuilder, which is provided at the bottom of page in Appendix section.

 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
94
95
96
package com.blog.core.search.solrfacetsearch.populators;

import com.blog.core.search.solrfacetsearch.solr.strategies.impl.FacetSearchConfigStrategy;
import com.blog.core.utils.SolrMultiIndexedTypesUtils;
import de.hybris.platform.catalog.model.CatalogVersionModel;
import de.hybris.platform.commerceservices.enums.SearchQueryContext;
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.SolrSearchRequest;
import de.hybris.platform.commerceservices.search.solrfacetsearch.populators.SearchSolrQueryPopulator;
import de.hybris.platform.commerceservices.search.solrfacetsearch.strategies.exceptions.NoValidSolrConfigException;
import de.hybris.platform.servicelayer.dto.converter.ConversionException;
import de.hybris.platform.solrfacetsearch.config.FacetSearchConfig;
import de.hybris.platform.solrfacetsearch.config.IndexedType;
import de.hybris.platform.solrfacetsearch.config.exceptions.FacetConfigServiceException;
import de.hybris.platform.solrfacetsearch.search.SearchQuery;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.StringUtils;

import javax.annotation.Resource;
import java.util.ArrayList;
import java.util.Collection;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class MultipleSolrIndexedTypesSearchSolrQueryPopulator<INDEXED_PROPERTY_TYPE, INDEXED_TYPE_SORT_TYPE> extends SearchSolrQueryPopulator<INDEXED_PROPERTY_TYPE, INDEXED_TYPE_SORT_TYPE> {
    private static final Logger LOG = LoggerFactory.getLogger(MultipleSolrIndexedTypesSearchSolrQueryPopulator.class);

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

    @Override
    protected SearchQuery createSearchQuery(final FacetSearchConfig facetSearchConfig, final IndexedType indexedType,
                                            final SearchQueryContext searchQueryContext, final String freeTextSearch) {
        boolean isMultiTypeIndex = SolrMultiIndexedTypesUtils.isMultiTypeIndex(facetSearchConfig);
        if (!isMultiTypeIndex) {
            return super.createSearchQuery(facetSearchConfig, indexedType, searchQueryContext, freeTextSearch);
        }

        MultiIndexTypeSearchQueryBuilder builder = MultiIndexTypeSearchQueryBuilder.create(facetSearchConfig, indexedType);
        for (IndexedType solrIndexType : facetSearchConfig.getIndexConfig().getIndexedTypes().values()) {
            builder.add(super.createSearchQuery(facetSearchConfig, solrIndexType, searchQueryContext, freeTextSearch));
        }
        return builder.build();

    }

    @Override
    protected SearchQuery createSearchQueryForLegacyMode(FacetSearchConfig facetSearchConfig, IndexedType indexedType, SearchQueryContext searchQueryContext, String freeTextSearch) {
        LOG.warn("Solr legacy mode is not supported for query building. Fallback to regular mode.");
        return this.createSearchQuery(facetSearchConfig, indexedType, searchQueryContext, freeTextSearch);
    }

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

}

SearchQuery creation in createSearchQuery method for none MultiTypeIndex uses OOTB implementation. For MultiTypeIndex is used MultiIndexTypeSearchQueryBuilder, which combines SearchQuery created for each index type into 1 SearchQuery. SearchQuery creation for solr legacy mode is disabled.

Catalog Version Aware Search filter

For each solr search OOTB SAP Commerce automatically adds filter by catalog version if we are searching catalog aware itemtype (similar to flexible search restriction). It is implemented in FacetSearchQueryCatalogVersionsFilterPopulator, but unfortunately OOTB implementation would work only if all SolrIndexedType are catalog aware or if all are not catalog aware. In case there are both catalog aware and catalog unaware SolrIndexedTypes, then catalog unaware items would be filtered out. So MultipleIndexedTypeFacetSearchQueryCatalogVersionsFilterPopulator overrides default implementation to include catalog version filter only for catalog aware SolrIndexedTypes. To create query is used CatalogVersionsRawFilterQueryBuilder.

 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
package com.blog.core.search.solrfacetsearch.search.impl.populators;

import com.blog.core.search.solrfacetsearch.search.impl.CatalogVersionsRawFilterQueryBuilder;
import de.hybris.platform.catalog.CatalogTypeService;
import de.hybris.platform.catalog.model.CatalogVersionModel;
import de.hybris.platform.converters.Populator;
import de.hybris.platform.solrfacetsearch.config.IndexedType;
import de.hybris.platform.solrfacetsearch.search.impl.SearchQueryConverterData;
import org.apache.solr.client.solrj.SolrQuery;

import javax.annotation.Resource;
import java.util.Collection;
import java.util.List;

public class MultipleIndexedTypeFacetSearchQueryCatalogVersionsFilterPopulator implements Populator<SearchQueryConverterData, SolrQuery> {

    @Resource
    private CatalogTypeService catalogTypeService;

    @Override
    public void populate(final SearchQueryConverterData source, final SolrQuery target) {
        // ! This is extended version of OOTB [[FacetSearchQueryCatalogVersionsFilterPopulator]] class!
        List<CatalogVersionModel> catalogVersions = source.getSearchQuery().getCatalogVersions();

        CatalogVersionsRawFilterQueryBuilder filterQueryBuilder = CatalogVersionsRawFilterQueryBuilder.create(catalogVersions);
        for (IndexedType indexedType : getIndexedTypes(source)) {
            filterQueryBuilder.withComposedType(indexedType, catalogTypeService);
        }
        target.addFilterQuery(filterQueryBuilder.build());
    }

    private static Collection<IndexedType> getIndexedTypes(SearchQueryConverterData source) {
        return source.getFacetSearchContext().getFacetSearchConfig().getIndexConfig().getIndexedTypes().values();
    }

}
 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
package com.blog.core.search.solrfacetsearch.search.impl;

import de.hybris.platform.catalog.CatalogTypeService;
import de.hybris.platform.catalog.model.CatalogVersionModel;
import de.hybris.platform.core.model.type.ComposedTypeModel;
import de.hybris.platform.solrfacetsearch.config.IndexedType;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.solr.client.solrj.util.ClientUtils;

import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.StringJoiner;

public class CatalogVersionsRawFilterQueryBuilder {
    private List<CatalogVersionModel> catalogVersions;

    private final List<String> queries = new ArrayList<>();

    private CatalogVersionsRawFilterQueryBuilder(List<CatalogVersionModel> catalogVersions) {
        if (CollectionUtils.isNotEmpty(catalogVersions)) {
            this.catalogVersions = catalogVersions;
        }
    }

    public static CatalogVersionsRawFilterQueryBuilder create(List<CatalogVersionModel> catalogVersions) {
        return new CatalogVersionsRawFilterQueryBuilder(catalogVersions);
    }

    public CatalogVersionsRawFilterQueryBuilder withComposedType(IndexedType indexedType, CatalogTypeService catalogTypeService) {
        String filterQuery = createFilterQuery(catalogTypeService, indexedType.getComposedType());
        queries.add(filterQuery);
        return this;
    }

    public String build() {
        return String.join(" OR ", queries);
    }

    private String createFilterQuery(CatalogTypeService catalogTypeService, ComposedTypeModel composedType) {
        if (catalogTypeService.isCatalogVersionAwareType(composedType)) {
            return createItemTypeFilterQueryWithCatalogVersionFilter(composedType);
        }
        return createItemTypeFilterQuery(composedType);
    }

    private String createItemTypeFilterQuery(ComposedTypeModel composedType) {
        final StringBuilder rawQuery = new StringBuilder("itemtype_string:").append(composedType.getCode());
        final Collection<ComposedTypeModel> allSubTypes = composedType.getAllSubTypes();
        if (CollectionUtils.isNotEmpty(allSubTypes)) {
            for (final ComposedTypeModel subType : allSubTypes) {
                rawQuery.append(" OR ").append("itemtype_string:").append(subType.getCode());
            }
        }
        return rawQuery.toString();
    }

    private String createItemTypeFilterQueryWithCatalogVersionFilter(ComposedTypeModel composedType) {
        final String itemTypeFilterQuery = createItemTypeFilterQuery(composedType);
        if (CollectionUtils.isEmpty(catalogVersions)) {
            return itemTypeFilterQuery;
        }

        final StringJoiner stringJoiner = new StringJoiner(" OR ");
        for (final CatalogVersionModel catalogVersion : catalogVersions) {
            final StringBuilder rawQuery = new StringBuilder();
            rawQuery.insert(0, "((")
                    .append(itemTypeFilterQuery)
                    .append(") AND catalogId:\"").append(ClientUtils.escapeQueryChars(catalogVersion.getCatalog().getId())).append('"')
                    .append(" AND catalogVersion:\"").append(ClientUtils.escapeQueryChars(catalogVersion.getVersion())).append('"')
                    .append("  )");
            stringJoiner.add(rawQuery);
        }

        return stringJoiner.toString();
    }

}

Sorting, filtering/facets

Searching also includes sorting and filtering with facets. OOTB SAP Commerce converts and populates only indexed properties defined for IndexedType, due to multiple IndexedType could now be defined all places should be able to resolve indexed properties from all IndexedTypes.

Resolving of indexed properties for MultiTypeIndex is written in SolrMultiIndexedTypesUtils (provided below in Appendix section) and is used in:

  • MultipleTypeFacetSearchContextFactory to override OOTB DefaultFacetSearchContextFactory to support sorting for MultiTypeIndex.
  • MultipleTypesSearchFiltersPopulator and MultipleTypesSearchResponseFacetsPopulator to override OOTB classes to support filtering and facets for MultiTypeIndex.
 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
package com.blog.core.search.solrfacetsearch.context.impl;

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.config.IndexedTypeSort;
import de.hybris.platform.solrfacetsearch.config.SearchConfig;
import de.hybris.platform.solrfacetsearch.search.SearchQuery;
import de.hybris.platform.solrfacetsearch.search.context.impl.DefaultFacetSearchContext;
import de.hybris.platform.solrfacetsearch.search.context.impl.DefaultFacetSearchContextFactory;

import java.util.function.Function;

import org.apache.commons.collections4.CollectionUtils;

import java.util.ArrayList;
import java.util.Collection;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.stream.Collectors;

public class MultipleTypeFacetSearchContextFactory extends DefaultFacetSearchContextFactory {

    public DefaultFacetSearchContext createContext(final FacetSearchConfig facetSearchConfig, final IndexedType indexedType, final SearchQuery searchQuery) {
        final DefaultFacetSearchContext context = super.createContext(facetSearchConfig, indexedType, searchQuery);
        context.setAvailableNamedSorts(getAvailableNamedSorts(facetSearchConfig));
        return context;
    }

    protected List<IndexedTypeSort> getAvailableNamedSorts(final FacetSearchConfig facetSearchConfig) {
        IndexConfig indexConfig = facetSearchConfig.getIndexConfig();
        SearchConfig searchConfig = facetSearchConfig.getSearchConfig();

        Map<String, IndexedTypeSort> sortsFromAllIndexedTypes = getSortFromAllIndexedTypes(indexConfig);
        Collection<String> defaultOrderOfSorts = searchConfig.getDefaultSortOrder();

        Set<IndexedTypeSort> sortsInDefaultOrder = createsSortedSetWithSortsInDefaultSortOrder(defaultOrderOfSorts, sortsFromAllIndexedTypes);
        sortsInDefaultOrder.addAll(sortsFromAllIndexedTypes.values());

        return new ArrayList<>(sortsInDefaultOrder);
    }

    private Map<String, IndexedTypeSort> getSortFromAllIndexedTypes(IndexConfig indexConfig) {
        return indexConfig.getIndexedTypes().values().stream()
                .map(IndexedType::getSorts)
                .flatMap(Collection::stream)
                .collect(Collectors.toMap(IndexedTypeSort::getCode, Function.identity(), (it, it2) -> it));
    }

    private Set<IndexedTypeSort> createsSortedSetWithSortsInDefaultSortOrder(Collection<String> defaultSortOrder, Map<String, IndexedTypeSort> uniqueSorts) {
        return CollectionUtils.emptyIfNull(defaultSortOrder).stream()
                .map(uniqueSorts::get)
                .filter(Objects::nonNull)
                .collect(Collectors.toCollection(LinkedHashSet::new));
    }
}
 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
package com.blog.core.search.solrfacetsearch.populators;

import com.blog.core.utils.SolrMultiIndexedTypesUtils;
import de.hybris.platform.commerceservices.search.solrfacetsearch.data.IndexedPropertyValueData;
import de.hybris.platform.commerceservices.search.solrfacetsearch.data.SearchQueryPageableData;
import de.hybris.platform.commerceservices.search.solrfacetsearch.data.SolrSearchFilterQueryData;
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.populators.SearchFiltersPopulator;
import de.hybris.platform.solrfacetsearch.config.FacetSearchConfig;
import de.hybris.platform.solrfacetsearch.config.IndexConfig;
import de.hybris.platform.solrfacetsearch.config.IndexedProperty;
import de.hybris.platform.solrfacetsearch.config.IndexedType;
import de.hybris.platform.solrfacetsearch.search.SearchQuery;
import org.apache.commons.collections4.CollectionUtils;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

public class MultipleTypesSearchFiltersPopulator<FACET_SEARCH_CONFIG_TYPE, INDEXED_TYPE_SORT_TYPE>
        extends SearchFiltersPopulator<FACET_SEARCH_CONFIG_TYPE, INDEXED_TYPE_SORT_TYPE> {

    @Override
    public void populate(SearchQueryPageableData<SolrSearchQueryData> source, SolrSearchRequest<FACET_SEARCH_CONFIG_TYPE, IndexedType, IndexedProperty,
            SearchQuery, INDEXED_TYPE_SORT_TYPE> target) {
        // Convert the facet filters into IndexedPropertyValueData
        final List<IndexedPropertyValueData<IndexedProperty>> indexedPropertyValues = new ArrayList<>();
        final Map<String, SolrSearchFilterQueryData> filterQueriesMap = new HashMap<>();
        final List<SolrSearchQueryTermData> terms = target.getSearchQueryData().getFilterTerms();
        final FacetSearchConfig facetSearchConfig = (FacetSearchConfig) target.getFacetSearchConfig();
        final IndexConfig indexConfig = facetSearchConfig.getIndexConfig();

        // ! Indexed properties resolving differs from parent class implementation.
        final Map<String, IndexedProperty> indexedProperties = SolrMultiIndexedTypesUtils.getIndexedProperties(indexConfig);

        if (terms != null && !terms.isEmpty()) {
            for (final SolrSearchQueryTermData term : terms) {
                final IndexedProperty indexedProperty = indexedProperties.get(term.getKey());
                if (indexedProperty != null) {
                    final IndexedPropertyValueData<IndexedProperty> indexedPropertyValue = new IndexedPropertyValueData<>();
                    indexedPropertyValue.setIndexedProperty(indexedProperty);
                    indexedPropertyValue.setValue(term.getValue());
                    indexedPropertyValues.add(indexedPropertyValue);
                }
            }
        }
        target.setIndexedPropertyValues(indexedPropertyValues);

        populateFilterQueries(target.getSearchQueryData(), filterQueriesMap);

        // Add the facet filters
        for (final IndexedPropertyValueData<IndexedProperty> indexedPropertyValue : target.getIndexedPropertyValues()) {
            target.getSearchQuery().addFacetValue(indexedPropertyValue.getIndexedProperty().getName(),
                    indexedPropertyValue.getValue());
        }

        // Add category restriction
        if (target.getSearchQueryData().getCategoryCode() != null) {
            // allCategories field indexes all the separate category hierarchies
            target.getSearchQuery().addFilterQuery("allCategories", target.getSearchQueryData().getCategoryCode());
        }

        // Add filter queries
        final List<SolrSearchFilterQueryData> filterQueries = target.getSearchQueryData().getFilterQueries();

        if (CollectionUtils.isNotEmpty(filterQueries)) {
            for (final SolrSearchFilterQueryData solrSearchFilterQuery : filterQueries) {
                target.getSearchQuery().addFilterQuery(convertFilterQuery(solrSearchFilterQuery));
            }
        }
    }
}
 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
package com.blog.facades.search.populators;

import com.blog.core.utils.SolrMultiIndexedTypesUtils;
import de.hybris.platform.commerceservices.search.facetdata.FacetData;
import de.hybris.platform.commerceservices.search.facetdata.FacetValueData;
import de.hybris.platform.commerceservices.search.solrfacetsearch.data.SolrSearchQueryData;
import de.hybris.platform.commerceservices.search.solrfacetsearch.populators.SearchResponseFacetsPopulator;
import de.hybris.platform.solrfacetsearch.config.FacetSearchConfig;
import de.hybris.platform.solrfacetsearch.config.IndexedProperty;
import de.hybris.platform.solrfacetsearch.config.IndexedType;
import de.hybris.platform.solrfacetsearch.search.Facet;
import de.hybris.platform.solrfacetsearch.search.FacetValue;
import de.hybris.platform.solrfacetsearch.search.SearchQuery;
import de.hybris.platform.solrfacetsearch.search.SearchResult;
import de.hybris.platform.solrfacetsearch.search.impl.SolrSearchResult;
import org.springframework.util.CollectionUtils;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;

public class MultipleTypesSearchResponseFacetsPopulator<FACET_SEARCH_CONFIG_TYPE, INDEXED_TYPE_TYPE, INDEXED_PROPERTY_TYPE, INDEXED_TYPE_SORT_TYPE, ITEM>
        extends
        SearchResponseFacetsPopulator<FACET_SEARCH_CONFIG_TYPE, INDEXED_TYPE_TYPE, INDEXED_PROPERTY_TYPE, INDEXED_TYPE_SORT_TYPE, ITEM> {


    protected List<FacetData<SolrSearchQueryData>> buildFacets(final SearchResult solrSearchResult,
                                                               final SolrSearchQueryData searchQueryData, final IndexedType indexedType) {
        final List<Facet> solrSearchResultFacets = solrSearchResult.getFacets();
        final List<FacetData<SolrSearchQueryData>> result = new ArrayList<>(solrSearchResultFacets.size());

        // ! Indexed properties resolving differs from parent class implementation.
        final Map<String, IndexedProperty> indexedProperties = resolveIndexedProperties(solrSearchResult, indexedType);

        for (final Facet facet : solrSearchResultFacets) {
            final IndexedProperty indexedProperty = indexedProperties.get(facet.getName());
            if (indexedProperty != null) {
                // Ignore any facets with a priority less than or equal to 0 as they are for internal use only
                final FacetData<SolrSearchQueryData> facetData = createFacetData();
                facetData.setCode(facet.getName());
                facetData.setCategory(indexedProperty.isCategoryField());
                final String displayName = indexedProperty.getDisplayName();
                facetData.setName(displayName == null ? facet.getName() : displayName);
                facetData.setMultiSelect(facet.isMultiselect());
                facetData.setPriority(facet.getPriority());
                facetData.setVisible(indexedProperty.isVisible());

                buildFacetValues(facetData, facet, indexedProperty, solrSearchResult, searchQueryData);

                // Only add the facet if there are values
                if (facetData.getValues() != null && !facetData.getValues().isEmpty()) {
                    result.add(facetData);
                }
            }
        }

        return result;
    }

    private Map<String, IndexedProperty> resolveIndexedProperties(final SearchResult solrSearchResult, final IndexedType indexedType) {
        Map<String, IndexedProperty> result;
        if (solrSearchResult instanceof SolrSearchResult searchResult) {
            final SearchQuery searchQuery = searchResult.getSearchQuery();
            final FacetSearchConfig facetSearchConfig = searchQuery.getFacetSearchConfig();

            result = SolrMultiIndexedTypesUtils.getIndexedProperties(facetSearchConfig.getIndexConfig());
        } else {
            result = indexedType.getIndexedProperties();
        }
        return result;
    }

}

Similar to previous implementation would be used SolrMultiIndexedTypesUtils to resolve indexed properties for adjustments in:

  • MultipleIndexedTypeFieldNameTranslator to override OOTB class to support field name resolving for MultiTypeIndex.
  • MultipleTypeSolrAsSearchProfileCalculationListener to override OOTB class to support Adaptive Search configurations for indexed properties.
  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
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
package com.blog.core.search.solrfacetsearch.search.impl;

import de.hybris.platform.solrfacetsearch.config.IndexedProperty;
import de.hybris.platform.solrfacetsearch.config.IndexedType;
import de.hybris.platform.solrfacetsearch.constants.SolrfacetsearchConstants;
import de.hybris.platform.solrfacetsearch.provider.FieldNameProvider;
import de.hybris.platform.solrfacetsearch.provider.IndexedTypeFieldsValuesProvider;
import de.hybris.platform.solrfacetsearch.search.FieldNameTranslator;
import de.hybris.platform.solrfacetsearch.search.SearchQuery;
import de.hybris.platform.solrfacetsearch.search.context.FacetSearchContext;
import de.hybris.platform.solrfacetsearch.search.impl.DefaultFieldNameTranslator;

import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;

public class MultipleIndexedTypeFieldNameTranslator extends DefaultFieldNameTranslator {

    @Override
    public String translate(final SearchQuery searchQuery, final String field, final FieldNameProvider.FieldType fieldType) {
        String translatedField = SolrfacetsearchConstants.INTERNAL_FIELD_MAPPING.get(field);
        if (translatedField == null) {
            // ! Indexed types resolving differs from parent class implementation.
            final Map<String, IndexedType> indexedTypes = searchQuery.getFacetSearchConfig().getIndexConfig().getIndexedTypes();
            final Optional<IndexedProperty> indexedProperty = indexedTypes.values().stream()
                    .map(IndexedType::getIndexedProperties)
                    .map(stringIndexedPropertyMap -> stringIndexedPropertyMap.get(field))
                    .filter(Objects::nonNull)
                    .findFirst();

            if (indexedProperty.isPresent()) {
                translatedField = translateFromProperty(searchQuery, indexedProperty.get(), fieldType);
            } else {
                translatedField = translateFromType(searchQuery, field);
            }

            if (Objects.isNull(translatedField)) {
                translatedField = field;
            }
        }
        return translatedField;
    }

    @Override
    public FieldNameTranslator.FieldInfosMapping getFieldInfos(FacetSearchContext searchContext) {
        DefaultFieldInfosMapping fieldInfosMapping = (DefaultFieldInfosMapping) searchContext.getAttributes().get("solrfacetsearch.fieldInfos");
        if (fieldInfosMapping == null) {
            Map<String, FieldInfo> fieldInfos = new HashMap<>();
            Map<String, FieldNameTranslator.FieldInfo> invertedFieldInfos = new HashMap<>();
            SearchQuery searchQuery = searchContext.getSearchQuery();
            IndexedType indexedType = searchContext.getIndexedType();

            // ! Indexed types resolving differs from parent class implementation.
            final List<IndexedProperty> indexedProperties = searchContext.getFacetSearchConfig().getIndexConfig().getIndexedTypes().values().stream()
                    .map(indexType -> indexType.getIndexedProperties().values())
                    .flatMap(Collection::stream).toList();

            for (IndexedProperty indexedProperty : indexedProperties) {
                String fieldName = indexedProperty.getName();
                String translatedFieldName = this.translateFromProperty(searchQuery, indexedProperty, FieldNameProvider.FieldType.INDEX);
                DefaultFieldInfo fieldInfo = new DefaultFieldInfo();
                fieldInfo.setFieldName(fieldName);
                fieldInfo.setTranslatedFieldName(translatedFieldName);
                fieldInfo.setIndexedProperty(indexedProperty);
                fieldInfos.put(fieldName, fieldInfo);
                invertedFieldInfos.put(translatedFieldName, fieldInfo);
            }

            Object typeValueProvider = this.getTypeValueProvider(indexedType);
            if (typeValueProvider instanceof IndexedTypeFieldsValuesProvider) {
                Map<String, String> fieldNamesMapping = ((IndexedTypeFieldsValuesProvider) typeValueProvider).getFieldNamesMapping();

                for (Map.Entry<String, String> stringStringEntry : fieldNamesMapping.entrySet()) {
                    Map.Entry entry = stringStringEntry;
                    String fieldName = (String) entry.getKey();
                    String translatedFieldName = (String) entry.getValue();
                    DefaultFieldInfo fieldInfo = new DefaultFieldInfo();
                    fieldInfo.setFieldName(fieldName);
                    fieldInfo.setTranslatedFieldName(translatedFieldName);
                    fieldInfos.put(fieldName, fieldInfo);
                    invertedFieldInfos.put(translatedFieldName, fieldInfo);
                }
            }

            fieldInfosMapping = new DefaultFieldInfosMapping();
            fieldInfosMapping.setFieldInfos(fieldInfos);
            fieldInfosMapping.setInvertedFieldInfos(invertedFieldInfos);
            searchContext.getAttributes().put("solrfacetsearch.fieldInfos", fieldInfosMapping);
        }

        return fieldInfosMapping;
    }

    protected static class DefaultFieldInfo implements FieldNameTranslator.FieldInfo {
        private String fieldName;
        private String translatedFieldName;
        private IndexedProperty indexedProperty;

        protected DefaultFieldInfo() {
        }

        public String getFieldName() {
            return this.fieldName;
        }

        public void setFieldName(String fieldName) {
            this.fieldName = fieldName;
        }

        public String getTranslatedFieldName() {
            return this.translatedFieldName;
        }

        public void setTranslatedFieldName(String translatedFieldName) {
            this.translatedFieldName = translatedFieldName;
        }

        public IndexedProperty getIndexedProperty() {
            return this.indexedProperty;
        }

        public void setIndexedProperty(IndexedProperty indexedProperty) {
            this.indexedProperty = indexedProperty;
        }
    }

    protected static class DefaultFieldInfosMapping implements FieldNameTranslator.FieldInfosMapping {
        private Map<String, FieldNameTranslator.FieldInfo> fieldInfos;
        private Map<String, FieldNameTranslator.FieldInfo> invertedFieldInfos;

        protected DefaultFieldInfosMapping() {
        }

        public Map<String, FieldNameTranslator.FieldInfo> getFieldInfos() {
            return this.fieldInfos;
        }

        public void setFieldInfos(Map<String, FieldNameTranslator.FieldInfo> fieldInfos) {
            this.fieldInfos = fieldInfos;
        }

        public Map<String, FieldNameTranslator.FieldInfo> getInvertedFieldInfos() {
            return this.invertedFieldInfos;
        }

        public void setInvertedFieldInfos(Map<String, FieldNameTranslator.FieldInfo> invertedFieldInfos) {
            this.invertedFieldInfos = invertedFieldInfos;
        }
    }
}
 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
package com.blog.core.search.solrfacetsearch.listeners;

import com.blog.core.utils.SolrMultiIndexedTypesUtils;
import de.hybris.platform.adaptivesearch.data.AbstractAsFacetConfiguration;
import de.hybris.platform.adaptivesearch.data.AsConfigurationHolder;
import de.hybris.platform.adaptivesearchsolr.listeners.SolrAsSearchProfileCalculationListener;
import de.hybris.platform.solrfacetsearch.config.FacetSearchConfig;
import de.hybris.platform.solrfacetsearch.config.IndexConfig;
import de.hybris.platform.solrfacetsearch.config.IndexedProperty;
import de.hybris.platform.solrfacetsearch.search.context.FacetSearchContext;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.Map;

public class MultipleTypeSolrAsSearchProfileCalculationListener extends SolrAsSearchProfileCalculationListener {
    private static final Logger LOG = LoggerFactory.getLogger(MultipleTypeSolrAsSearchProfileCalculationListener.class);

    @Override
    protected boolean isValidFacet(final FacetSearchContext facetSearchContext,
                                   final AsConfigurationHolder<? extends AbstractAsFacetConfiguration, AbstractAsFacetConfiguration> facetHolder) {

        final FacetSearchConfig facetSearchConfig = facetSearchContext.getFacetSearchConfig();
        final IndexConfig indexConfig = facetSearchConfig.getIndexConfig();
        final Map<String, IndexedProperty> indexedProperties = SolrMultiIndexedTypesUtils.getIndexedProperties(indexConfig);

        final AbstractAsFacetConfiguration facet = facetHolder.getConfiguration();
        if (!indexedProperties.containsKey(facet.getIndexProperty())) {
            LOG.warn("Facet {} is no longer valid!", facet.getIndexProperty());
            return false;
        }

        return true;
    }
}

Now everything is ready for using one solr core for different itemtypes/SolrIndexedType. Happy searching!

Appendix

 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
package com.blog.core.utils;

import com.google.common.collect.Iterables;
import de.hybris.platform.solrfacetsearch.config.FacetSearchConfig;
import de.hybris.platform.solrfacetsearch.config.IndexConfig;
import de.hybris.platform.solrfacetsearch.config.IndexedProperty;
import de.hybris.platform.solrfacetsearch.config.IndexedType;
import de.hybris.platform.solrfacetsearch.indexer.IndexerContext;
import de.hybris.platform.solrfacetsearch.model.config.SolrFacetSearchConfigModel;
import de.hybris.platform.solrfacetsearch.model.config.SolrIndexedTypeModel;
import org.apache.commons.collections4.MapUtils;
import org.apache.commons.lang3.StringUtils;

import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Set;

public class SolrMultiIndexedTypesUtils {

    private SolrMultiIndexedTypesUtils() {
        // ! Util class must not be initialized!
    }

    public static boolean isFirstIteration(final IndexerContext context) {
        FacetSearchConfig facetSearchConfig = context.getFacetSearchConfig();
        if (!isMultiTypeIndex(facetSearchConfig)) {
            return true;
        }
        final IndexedType indexedType = context.getIndexedType();
        final Collection<IndexedType> indexedTypes = getIndexedTypes(context);
        return indexedTypes.iterator().next().getIdentifier().equals(indexedType.getIdentifier());
    }

    public static boolean isLastIteration(final IndexerContext context) {
        FacetSearchConfig facetSearchConfig = context.getFacetSearchConfig();
        if (!isMultiTypeIndex(facetSearchConfig)) {
            return true;
        }
        final IndexedType indexedType = context.getIndexedType();
        final Collection<IndexedType> indexedTypes = getIndexedTypes(context);

        return Iterables.getLast(indexedTypes).getIdentifier().equals(indexedType.getIdentifier());
    }

    public static boolean isMultiTypeIndexType(final SolrFacetSearchConfigModel facetSearchConfig,
                                               final SolrIndexedTypeModel indexedType) {
        String indexName = indexedType.getIndexName();
        if (StringUtils.isBlank(indexName)) {
            return false;
        }
        return facetSearchConfig.getSolrIndexedTypes().stream()
                .anyMatch(solrIndexedType -> !indexedType.equals(solrIndexedType) && indexName.equals(solrIndexedType.getIndexName()));
    }

    public static boolean isMultiTypeIndex(FacetSearchConfig facetSearchConfig) {
        Set<String> uniqueIndexNameSet = new HashSet<>();
        Collection<IndexedType> indexedTypes = facetSearchConfig.getIndexConfig().getIndexedTypes().values();
        return indexedTypes.stream()
                .anyMatch(indexedType -> existAnotherIndexTypeWithSameIndexName(indexedType, uniqueIndexNameSet));
    }

    private static boolean existAnotherIndexTypeWithSameIndexName(IndexedType indexedType, Set<String> uniqueIndexNamedPerIndexType) {
        if (indexedType.getIndexName() == null) {
            return false;
        }
        return !uniqueIndexNamedPerIndexType.add(indexedType.getIndexName());
    }

    public static Map<String, IndexedProperty> getIndexedProperties(final IndexConfig indexConfig) {
        final HashMap<String, IndexedProperty> indexedProperties = new HashMap<>();

        indexConfig.getIndexedTypes().values().stream()
                .map(IndexedType::getIndexedProperties)
                .filter(MapUtils::isNotEmpty)
                .forEach(indexedProperties::putAll);

        return indexedProperties;
    }

    private static Collection<IndexedType> getIndexedTypes(final IndexerContext context) {
        final FacetSearchConfig facetSearchConfig = context.getFacetSearchConfig();
        final Map<String, IndexedType> indexedTypes = facetSearchConfig.getIndexConfig().getIndexedTypes();
        if (indexedTypes instanceof LinkedHashMap<String, IndexedType>) {
            return indexedTypes.values();
        } else {
            throw new RuntimeException("Internal implementation has changed. We need an ordered map for our logic!");
        }
    }
}
  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
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
package com.blog.core.search.solrfacetsearch.populators;

import de.hybris.platform.solrfacetsearch.config.FacetSearchConfig;
import de.hybris.platform.solrfacetsearch.config.IndexedType;
import de.hybris.platform.solrfacetsearch.search.SearchQuery;

import java.util.ArrayList;

import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.collections4.MapUtils;

import java.util.List;
import java.util.Map;
import java.util.Objects;

public class MultiIndexTypeSearchQueryBuilder {

    private final MultipleItemTypeSearchQuery mergingSearchQuery;
    private boolean isNonListAttributesPopulated = false;

    private MultiIndexTypeSearchQueryBuilder(MultipleItemTypeSearchQuery mergingSearchQuery) {
        this.mergingSearchQuery = mergingSearchQuery;
    }

    public static MultiIndexTypeSearchQueryBuilder create(final FacetSearchConfig facetSearchConfig, final IndexedType indexedType) {
        MultipleItemTypeSearchQuery mergingSearchQuery = new MultipleItemTypeSearchQuery(facetSearchConfig, indexedType);
        return new MultiIndexTypeSearchQueryBuilder(mergingSearchQuery);
    }

    public MultiIndexTypeSearchQueryBuilder add(SearchQuery searchQuery) {
        if (!isNonListAttributesPopulated) {
            mergingSearchQuery.setNonListAttributes(searchQuery);
            isNonListAttributesPopulated = true;
        }

        mergingSearchQuery.mergeOtherQuery(searchQuery);
        return this;
    }

    public SearchQuery build() {
        return mergingSearchQuery;
    }

    private static class MultipleItemTypeSearchQuery extends SearchQuery {

        public MultipleItemTypeSearchQuery(final FacetSearchConfig facetSearchConfig, final IndexedType indexedType) {
            super(facetSearchConfig, indexedType);
        }

        public void setNonListAttributes(SearchQuery searchQuery) {
            this.setLanguage(searchQuery.getLanguage());
            this.setCurrency(searchQuery.getCurrency());
            this.setOffset(searchQuery.getOffset());
            this.setPageSize(searchQuery.getPageSize());
            this.setDefaultOperator(searchQuery.getDefaultOperator());
            this.setFreeTextQueryBuilder(searchQuery.getFreeTextQueryBuilder());
            this.setUserQuery(searchQuery.getUserQuery());
            this.setGroupFacets(searchQuery.isGroupFacets());
            this.setEnableSpellcheck(searchQuery.isEnableSpellcheck());
            this.setQueryParser(searchQuery.getQueryParser());
            this.setNamedSort(searchQuery.getNamedSort());
        }

        public void mergeOtherQuery(final SearchQuery mergingSearchQuery) {
            this.setCatalogVersions(addAllNewArrayList(this.getCatalogVersions(), mergingSearchQuery.getCatalogVersions()));
            this.setKeywords(addAllNewArrayList(this.getKeywords(), mergingSearchQuery.getKeywords()));
            addAll(this.getFreeTextQueryBuilderParameters(), mergingSearchQuery.getFreeTextQueryBuilderParameters());
            addAll(this.getQueryContexts(), mergingSearchQuery.getQueryContexts());
            addAll(this.getQueries(), mergingSearchQuery.getQueries());
            addAll(this.getFreeTextQueries(), mergingSearchQuery.getFreeTextQueries());
            addAll(this.getFreeTextFuzzyQueries(), mergingSearchQuery.getFreeTextFuzzyQueries());
            addAll(this.getFreeTextWildcardQueries(), mergingSearchQuery.getFreeTextWildcardQueries());
            addAll(this.getFreeTextPhraseQueries(), mergingSearchQuery.getFreeTextPhraseQueries());
            addAll(this.getRawQueries(), mergingSearchQuery.getRawQueries());
            addAll(this.getFilterQueries(), mergingSearchQuery.getFilterQueries());
            addAll(this.getFilterRawQueries(), mergingSearchQuery.getFilterRawQueries());
            addAll(this.getGroupCommands(), mergingSearchQuery.getGroupCommands());
            addAll(this.getSorts(), mergingSearchQuery.getSorts());
            addAll(this.getFields(), mergingSearchQuery.getFields());
            addAll(this.getHighlightingFields(), mergingSearchQuery.getHighlightingFields());
            addAll(this.getFacets(), mergingSearchQuery.getFacets());
            addAll(this.getFacetValues(), mergingSearchQuery.getFacetValues());
            addAll(this.getBoosts(), mergingSearchQuery.getBoosts());
            addAll(this.getPromotedItems(), mergingSearchQuery.getPromotedItems());
            addAll(this.getExcludedItems(), mergingSearchQuery.getExcludedItems());
            addAll(this.getRawParams(), mergingSearchQuery.getRawParams());
            addAll(this.getBreadcrumbs(), mergingSearchQuery.getBreadcrumbs());
            addAll(this.getCoupledFields(), mergingSearchQuery.getCoupledFields());
            addAll(this.getBoostFields(), mergingSearchQuery.getBoostFields());
        }

        private <Q> List<Q> addAllNewArrayList(final List<Q> sourceList, final List<Q> targetList) {
            List<Q> collection = null;
            if (CollectionUtils.isEmpty(sourceList) && CollectionUtils.isNotEmpty(targetList)) {
                collection = targetList;
            } else if (CollectionUtils.isNotEmpty(sourceList) && CollectionUtils.isEmpty(targetList)) {
                collection = sourceList;
            } else if (CollectionUtils.isNotEmpty(sourceList) && CollectionUtils.isNotEmpty(targetList)) {
                collection = new ArrayList<>();
                CollectionUtils.addAll(collection, sourceList);
                CollectionUtils.addAll(collection, targetList);
            }
            return collection;
        }

        private <Q, Z> void addAll(final Map<Q, Z> sourceMap, final Map<Q, Z> targetMap) {
            if (Objects.nonNull(sourceMap) && MapUtils.isNotEmpty(targetMap)) {
                sourceMap.putAll(targetMap);
            }
        }

        private <Q> void addAll(final List<Q> sourceList, final List<Q> targetList) {
            if (Objects.nonNull(sourceList) && CollectionUtils.isNotEmpty(targetList)) {
                sourceList.addAll(targetList);
            }
        }
    }


}
comments powered by Disqus