How to add a localized property to the Solr index

Hybris does a good job by hiding the details of the Solr engine not only to the customer but also to the developer. There is only one difference in handling localized properties stored in the Solr index compared to the non-localized ones.

Prerequisites

Understanding of how non-localized properties work. The details are here:

Customizing Product Search Page

Introduction

In this example we have the SEOName localized field which we want to store in the Solr index and use it in a facade in the frontend to calculate the URL of the product.

Solr Value Provider

For each localized indexed property in Hybris, Solr creates a field in the Solr document with the name of it, the code of the locales of the languages set in the Solr Configuration in Hybris and the type of the field. Here is the result of a query:

{
  "responseHeader": {
    "status": 0,
    "QTime": 0,
    "params": {
      "indent": "true",
      "q": "*:*",
      "_": "1418289746648",
      "wt": "json"
    }
  },
  "response": {
    "numFound": 358,
    "start": 0,
    "docs": [
      {
        "id": "electronicsProductCatalog/Staged/1978440_green",
        "pk": 8796101738497,
        "catalogId": "electronicsProductCatalog",
        "catalogVersion": "Staged",
        "summary_text_ja": "DSC-H20, Green, 10.1 Megapixels, 10x Optical Zoom, 3.0\"\" LCD",
        "summary_text_en": "DSC-H20, Green, 10.1 Megapixels, 10x Optical Zoom, 3.0\"\" LCD",
        "summary_text_de": "DSC-H20, Green, 10.1 Megapixels, 10x Optical Zoom, 3.0\"\" LCD",
        "summary_text_zh": "DSC-H20, Green, 10.1 Megapixels, 10x Optical Zoom, 3.0\"\" LCD",
        "img-30Wx30H_string": "/medias/?context=bWFzdGVyfGltYWdlc3w4NzR8aW1hZ2UvanBlZ3xpbWFnZXMvaDNhL2g1My84Nzk2Mjg0MTI1MjE0LmpwZ3xjYzE2ZDE0MDhmYzQ4MzExMjMxYWJjMmE5OWRhMzA2NWJhYjJjZTg5ZWUwMTg0MDFhNTE2ZWIyODQ1NGQ5YTk4",
        "seoName_ja_string": "dsc-h20-green",
        "seoName_en_string": "dsc-h20-green",
        "seoName_de_string": "dsc-h20-green",
        "seoName_zh_string": "dsc-h20-green",
(...)

As in the case of a non-localized attribute we have to create a new indexed property. The only difference is that we set the flag localized to true:

# Import the Solr configuration for the Electronics store
#
$solrIndexedType=electronicsProductType

# Non-facet properties
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; seoName                ;string ;            ;    ;true;    ;    ;    ;seoNameValueProvider;

Now we need to write an implementation of the value provider. The most important step is to get the name of the Solr field using the language coming from the Solr configuration:

package org.areco.ecommerce.solrsearchtraining.providers;
 
import de.hybris.platform.commerceservices.i18n.CommerceCommonI18NService;
import de.hybris.platform.core.model.c2l.LanguageModel;
import de.hybris.platform.core.model.product.ProductModel;
import de.hybris.platform.solrfacetsearch.config.IndexConfig;
import de.hybris.platform.solrfacetsearch.config.IndexedProperty;
import de.hybris.platform.solrfacetsearch.config.exceptions.FieldValueProviderException;
import de.hybris.platform.solrfacetsearch.provider.FieldNameProvider;
import de.hybris.platform.solrfacetsearch.provider.FieldValue;
import de.hybris.platform.solrfacetsearch.provider.FieldValueProvider;
import de.hybris.platform.solrfacetsearch.provider.impl.AbstractPropertyFieldValueProvider;
 
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Locale;
 
import org.springframework.beans.factory.annotation.Required;
 
public class SeoNameValueProvider extends AbstractPropertyFieldValueProvider implements FieldValueProvider, Serializable
{
  private FieldNameProvider fieldNameProvider;
 
  private CommerceCommonI18NService commerceCommonI18NService;
 
  @Override
  public Collection<FieldValue> getFieldValues(final IndexConfig indexConfig, final IndexedProperty indexedProperty,
      final Object model) throws FieldValueProviderException
  {
    if (model instanceof ProductModel)
    {
      final ProductModel product = (ProductModel) model;
 
      final Collection<FieldValue> fieldValues = new ArrayList<FieldValue>();
 
      if (indexedProperty.isLocalized())
      {
        final Collection<LanguageModel> languages = indexConfig.getLanguages();
        for (final LanguageModel language : languages)
        {
          fieldValues.addAll(createFieldValue(product, language, indexedProperty));
        }
        return fieldValues;
      }
      else
      {
        throw new FieldValueProviderException("The SEO name must be localized");
      }
    }
    else
    {
      throw new FieldValueProviderException("The SEO name can only be calculate from a product");
    }
  }
 
  protected List<FieldValue> createFieldValue(final ProductModel product, final LanguageModel language,
      final IndexedProperty indexedProperty)
  {
    final List<FieldValue> fieldValues = new ArrayList<FieldValue>();
 
    //The  current site must be set in the session.
    final Locale currentLocale = this.getCommerceCommonI18NService().getLocaleForLanguage(language);
    final String seoName = translateToSEO(product.getName(currentLocale));
    if (seoName != null)
    {
      addFieldValues(fieldValues, indexedProperty, language, seoName);
    }
 
    return fieldValues;
  }
 
  private String translateToSEO(final String pProductName)
  {
    if (pProductName == null) {
      return null;
    }
    //Simple test conversion.
    return pProductName.toLowerCase().replaceAll("\\s","-");
  }
 
  protected void addFieldValues(final List<FieldValue> fieldValues, final IndexedProperty indexedProperty,
      final LanguageModel language, final Object value)
  {
    final Collection<String> fieldNames = getFieldNameProvider().getFieldNames(indexedProperty,
        language == null ? null : language.getIsocode());
    for (final String fieldName : fieldNames)
    {
      fieldValues.add(new FieldValue(fieldName, value));
    }
  }
 
  protected FieldNameProvider getFieldNameProvider()
  {
    return fieldNameProvider;
  }
 
 
  @Required
  public void setFieldNameProvider(final FieldNameProvider fieldNameProvider)
  {
    this.fieldNameProvider = fieldNameProvider;
  }
 
  @Required
  public CommerceCommonI18NService getCommerceCommonI18NService()
  {
    return commerceCommonI18NService;
  }
 
  public void setCommerceCommonI18NService(final CommerceCommonI18NService pCommerceCommonI18NService)
  {
    commerceCommonI18NService = pCommerceCommonI18NService;
  }
}

After adding the value provider to the spring context, importing the Impex Script and running full rebuild of the Solr Index, the new field should be in the Solr documents.

Populator

To make the new localized field available to the frontend or facades you have to write a populator:

package org.areco.ecommerce.solrsearchtraining.populators;
 
import de.hybris.platform.commercefacades.product.data.ProductData;
import de.hybris.platform.commerceservices.search.resultdata.SearchResultValueData;
import de.hybris.platform.converters.Populator;
import de.hybris.platform.servicelayer.dto.converter.ConversionException;
 
public class ProductSeoNamePopulator implements Populator<SearchResultValueData, ProductData>
{
 
  /**
   * Populate the target instance with values from the source instance.
   *
   * @param source
   *     the source object
   * @param target
   *     the target to fill
   * @throws de.hybris.platform.servicelayer.dto.converter.ConversionException
   *     if an error occurs
   */
  @Override public void populate(final SearchResultValueData source, final ProductData target) throws ConversionException
  {
    target.setSeoName((String) source.getValues().get("seoName"));
  }
}

No special handling of the language is required. Hybris already returns the value for the current language.

The above code assumes that you have defined a new field for the product data in your customextension-beans.xml file:

    <bean class="de.hybris.platform.commercefacades.product.data.ProductData">
        <property name="seoName" type="java.lang.String" />
    </bean>

The last step is to used this localized indexed property in at least one facade or in a view in the frontend.

–Based on Hybris 5.3

Discussion

Enter your comment. Wiki syntax is allowed: