Search code examples
sortingsolrsap-commerce-cloud

Solr Sort Hybris


I have a provider, which indexes stock for product for every unit, this way:

for (Map.Entry<B2BUnitModel, Integer> unit : stockByUnit.entrySet() )
    {
        document.addField(indexedProperty, hasStock(unit.getValue()), unitUid(unit.getKey()));
    }

so this is result after index in solr:

"localStockForUnt_001_boolean": true,
"localStockForUnt_002_boolean": true,

where localStockForUnt is SolrIndexedProperty, 001 and 002 are the units and true or false are the indexed value.

this is the impex to create it:

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`
;$solrIndexedType; localStockForUnt       ;boolean ;            ;    ;    ;    ;     ;   ;myResolver;

so I added it inside the 'sort' called 'relevance' in hmc, this 'sort' just have this field in hmc.

My doubt is, how can I set to it sort my result using for example localStockForUnt_002_boolean? I did set sort in controller manually to test, I did set it to "relevance", but since the provider of field used in relevance (localStockForUnt) index two different information, how can I select which one to use?


Solution

  • Actually what you are trying to do here was already been initiated and used in several cases by Hybris, for example:

    • localized properties like the name, indexed as name_en_string.
    • properties with currency like price is indexed as priceValue_eur_double and also used for Sort.

    For :priceValue_eur_double       |    For : localStockForUnt_001_boolean.
    priceValue is the field's name    |    localStockForUnt is the field's name.
    euris the field qualifier    |    001 is the field qualifier.
    double is the field type    |    boolean is the field type.

    So your case here is not different than these two examples, hence you need just to know how to use what's already exists.

    Actually nothing magical about how these two examples works!

    1. First of all, add new boolean attribute to SolrIndexedPropertyModel let's call it isB2bUnit :
    <!-- add this to your *-items.xml -->
    <itemtype code="SolrIndexedProperty" autocreate="false" generate="false">
        <attributes>
    
            <attribute qualifier="isB2bUnit" type="java.lang.boolean">
                <persistence type="property" />
                <!-- i would prefer to add a default value here : FALSE -->
            </attribute>
    
        </attributes>
    </itemtype>
    
    1. Next you have to add the same boolean attribute in the IndexedProperty dto :
    <!-- add this to your *-beans.xml -->
    <bean class="de.hybris.platform.solrfacetsearch.config.IndexedProperty">
    
        <property name="isB2bUnit" type="boolean"/>     
    
    </bean>
    
    1. Then you need to override DefaultIndexedPropertyPopulator it's the responsible for converting from SolrIndexedProperty to IndexedProperty:
    public class MyIndexedPropertyPopulator extends DefaultIndexedPropertyPopulator {
    
        @Override
        public void populate(SolrIndexedPropertyModel source, IndexedProperty target) throws ConversionException {
    
            super.populate(source, target);
    
            //add this line
            target.setIsB2bUnit(source.getIsB2bUnit());
    
        }
    
    }
    

    Register the propulator into spring.

    <!-- add this to your *-spring.xml -->
    
    <alias name="myIndexedPropertyPopulator" alias="indexedPropertyPopulator" />
    
    <bean id="myIndexedPropertyPopulator" class="com.foo.bar.MyIndexedPropertyPopulator" parent="defaultIndexedPropertyPopulator" />
    
    1. The idea is to hook into this method DefaultFieldNameTranslator.translateFromProperty(...) and force it to add your specific fieldQualifier which is b2bUnit.code to the fieldName if the isB2bUnit of the Indexedproperty is TRUE.

    The original DefaultFieldNameTranslator.translateFromProperty(...) is like this :

    protected String translateFromProperty(SearchQuery searchQuery, IndexedProperty indexedProperty, FieldType fieldType) {
    
        //...
    
        if(qualifierProvider != null && qualifierProvider.canApply(indexedProperty)) {
    
            Qualifier qualifier = qualifierProvider.getCurrentQualifier();
            fieldQualifier = qualifier != null?qualifier.toFieldQualifier():null;
    
        } else if(indexedProperty.isLocalized()) {
    
            fieldQualifier = searchQuery.getLanguage();
    
        } else if(indexedProperty.isCurrency()) {
    
            fieldQualifier = searchQuery.getCurrency();
    
        }
    
        //you have to add your else if here!!!
    
        return this.fieldNameProvider.getFieldName(indexedProperty, fieldQualifier, fieldType);
    
    }
    

    So create MyFieldNameTranslator that extends from DefaultFieldNameTranslator and override translateFromProperty(...).

    Note: SomeB2bUnitService this service is not real but it should be able to return the current b2bUnit.

    public class MyFieldNameTranslator extends DefaultFieldNameTranslator {
    
        //To be injected!!
        private SomeB2bUnitService someB2bUnitService;
    
        @Override
        protected String translateFromProperty(SearchQuery searchQuery, IndexedProperty indexedProperty, FieldType fieldType) {
    
            //...
    
            //...
    
            else if(indexedProperty.getIsB2bUnit()) {
    
                fieldQualifier = someB2bUnitService.getCurrentB2bUnit().getCode();
    
            }
    
            return this.fieldNameProvider.getFieldName(indexedProperty, fieldQualifier, fieldType);
    
        }
    
    }
    

    Register the Translator into Spring :

    <!-- add this to your *-spring.xml -->
    
    <alias name="myfieldNameTranslator" alias="fieldNameTranslator" />
    
    <bean id="myfieldNameTranslator" class="com.foo.bar.MyFieldNameTranslator" parent="defaultfieldNameTranslator">
    
        <property name="someB2bUnitService" ref="someB2bUnitService" />
    
    </bean>
    
    1. Edit : now all what you have to do is to set isB2bUnit to true for localStockForUnt:
    INSERT_UPDATE SolrIndexedProperty;solrIndexedType(identifier)[unique=true] ;name[unique=true] ;type(code) ;isB2bUnit
    ;$solrIndexedType ;localStockForUnt  ;boolean  ;true
    

    Note : that some classes and beans may have been changed between Hybris versions but the concept will remains the same.