Value Provider in Solr with example


Let’s see how we can write value provider with example

Requirement:

Display the number of days product will be available online from current date in the product search box and search result.

We have an attribute called offlineDate defined inside the Product model already which specifies when the product will go offline from the site

So for this we need to write some logic like offlineDate – currentDate and get this data when we search the product.

But search result comes from Solr right?
Yes,So we need to index this data now.

But this data is not available in DB directly, we need to write a custom logic to get this data and then we can send it to Solr for indexing.

So for this, we need to write ValueProvider.

Before implementation output will be as below

solr search suggestion output


After implementation output will be as below


solr search suggestion modified output


Let’s see step by step process to achieve the same.


Step 1

We need to create a new class for Value provider

So create a new class called ProductOnlineDaysValueProvider as below

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
package org.training.core.search.solrfacetsearch.provider.impl;
 
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.Date;
import java.util.List;
import java.util.concurrent.TimeUnit;
 
 
public class ProductOnlineDaysValueProvider extends AbstractPropertyFieldValueProvider implements FieldValueProvider, Serializable
{
    private FieldNameProvider fieldNameProvider;
    @SuppressWarnings("deprecation")
    @Override
    public Collection<FieldValue> getFieldValues(final IndexConfig indexConfig, final IndexedProperty indexedProperty,
            final Object obj) throws FieldValueProviderException
    {
        if (obj instanceof ProductModel)
        {
            final ProductModel product = (ProductModel) obj;
            final List<FieldValue> fieldValues = createFieldValue(product, indexConfig, indexedProperty);
            return fieldValues;
        }
        else
        {
            throw new FieldValueProviderException("Cannot get online days for a product model");
        }
    }
 
    @SuppressWarnings("boxing")
    protected List<FieldValue> createFieldValue(final ProductModel product, final IndexConfig indexConfig,
            final IndexedProperty indexedProperty)
    {
        final List<FieldValue> fieldValues = new ArrayList<FieldValue>();
        final Date offlineDate = product.getOfflineDate();
        final Date today = new Date();
        if (offlineDate != null)
        {
            final long diff = offlineDate.getTime() - today.getTime();
            final long noOfDaysOnline = TimeUnit.DAYS.convert(diff, TimeUnit.MILLISECONDS);
            addFieldValues(fieldValues, indexedProperty, null, noOfDaysOnline);
        }
        return fieldValues;
    }
 
 
    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));
        }
    }
 
    public FieldNameProvider getFieldNameProvider()
    {
        return fieldNameProvider;
    }
 
    public void setFieldNameProvider(final FieldNameProvider fieldNameProvider)
    {
        this.fieldNameProvider = fieldNameProvider;
    }
 
}
package org.training.core.search.solrfacetsearch.provider.impl;

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.Date;
import java.util.List;
import java.util.concurrent.TimeUnit;


public class ProductOnlineDaysValueProvider extends AbstractPropertyFieldValueProvider implements FieldValueProvider, Serializable
{
	private FieldNameProvider fieldNameProvider;
	@SuppressWarnings("deprecation")
	@Override
	public Collection<FieldValue> getFieldValues(final IndexConfig indexConfig, final IndexedProperty indexedProperty,
			final Object obj) throws FieldValueProviderException
	{
		if (obj instanceof ProductModel)
		{
			final ProductModel product = (ProductModel) obj;
			final List<FieldValue> fieldValues = createFieldValue(product, indexConfig, indexedProperty);
			return fieldValues;
		}
		else
		{
			throw new FieldValueProviderException("Cannot get online days for a product model");
		}
	}

	@SuppressWarnings("boxing")
	protected List<FieldValue> createFieldValue(final ProductModel product, final IndexConfig indexConfig,
			final IndexedProperty indexedProperty)
	{
		final List<FieldValue> fieldValues = new ArrayList<FieldValue>();
		final Date offlineDate = product.getOfflineDate();
		final Date today = new Date();
		if (offlineDate != null)
		{
			final long diff = offlineDate.getTime() - today.getTime();
			final long noOfDaysOnline = TimeUnit.DAYS.convert(diff, TimeUnit.MILLISECONDS);
			addFieldValues(fieldValues, indexedProperty, null, noOfDaysOnline);
		}
		return fieldValues;
	}


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

	public FieldNameProvider getFieldNameProvider()
	{
		return fieldNameProvider;
	}

	public void setFieldNameProvider(final FieldNameProvider fieldNameProvider)
	{
		this.fieldNameProvider = fieldNameProvider;
	}

}


Step 2

Make the above value provider class as a spring bean

Define it as a bean in the trainingcore-spring.xml as below
\hybris\bin\custom\training\trainingcore\resources\trainingcore-spring.xml

1
2
3
4
5
6
    <!-- value provider for product online days -->
    <bean id="productOnlineDaysValueProvider"
          class="org.training.core.search.solrfacetsearch.provider.impl.ProductOnlineDaysValueProvider"
           parent="abstractPropertyFieldValueProvider">
        <property name="fieldNameProvider" ref="solrFieldNameProvider"/>
    </bean>
	<!-- value provider for product online days -->
	<bean id="productOnlineDaysValueProvider"
	      class="org.training.core.search.solrfacetsearch.provider.impl.ProductOnlineDaysValueProvider"
	       parent="abstractPropertyFieldValueProvider">
		<property name="fieldNameProvider" ref="solrFieldNameProvider"/>
	</bean>


Step 3

Add new attribute onlineDays inside solr.impex file as indexed property
Also specify the value provider for the same as below
\hybris\bin\ext-data\apparelstore\resources\apparelstore\import\coredata\stores\apparel-uk\solr.impex

1
2
3
#Impex for adding new indexing property
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; onlineDays    ;int;            ;    ;    ;    ;    ;    ;productOnlineDaysValueProvider
#Impex for adding new indexing property
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; onlineDays    ;int;            ;    ;    ;    ;    ;    ;productOnlineDaysValueProvider

Here we have specified the indexed property name as onlineDays and the value provider as productOnlineDaysValueProvider
So this value provider productOnlineDaysValueProvider should be same as spring bean id of the value provider defined in the trainingcore-spring.xml
file

Step 4


Add new attribute onlineDays inside ProductData

We need to add below line in ProductData bean definitions

hybris\bin\custom\training\trainingfacades\resources\trainingfacades-beans.xml

1
2
3
4
<bean class="de.hybris.platform.commercefacades.product.data.ProductData">
        <property name="genders" type="java.util.List<org.training.facades.product.data.GenderData>"/>
<property name="onlineDays" type="int"></property>
    </bean>
<bean class="de.hybris.platform.commercefacades.product.data.ProductData">
		<property name="genders" type="java.util.List<org.training.facades.product.data.GenderData>"/>
<property name="onlineDays" type="int"></property>
	</bean>

Do ant all to get this attribute generated in the ProductData class.

Step 5

Populate new attribute from solr result to ProductData

Create a new class by extending SearchResultProductPopulator and Override the populate() method to set this new attribute in the ProductData

We need to add below lines

1
2
//Adding online days to product data
target.setOnlineDays(this.<Integer> getValue(source, "onlineDays").intValue());
//Adding online days to product data
target.setOnlineDays(this.<Integer> getValue(source, "onlineDays").intValue());



getValue() method takes 2 parameters
1)SearchResultValueData : Search result obtained from Solr
2)String: property name specified in solr.impex file

Complete class should be as below

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
package org.training.facades.search.converters.populators;
 
import de.hybris.platform.commercefacades.product.data.ProductData;
import de.hybris.platform.commercefacades.search.converters.populator.SearchResultVariantProductPopulator;
import de.hybris.platform.commerceservices.search.resultdata.SearchResultValueData;
public class TrainingSearchResultVariantProductPopulator extends SearchResultVariantProductPopulator
{
    
    @Override
    public void populate(final SearchResultValueData source, final ProductData target)
    {
        super.populate(source, target);
        //Adding online days to product data
        final Object obj = this.getValue(source, "onlineDays");
        if (obj != null)
        {
            target.setOnlineDays(this.<Integer> getValue(source, "onlineDays").intValue());
        }
        else
        {
            target.setOnlineDays(0);
        }
 
 
    }
}
package org.training.facades.search.converters.populators;

import de.hybris.platform.commercefacades.product.data.ProductData;
import de.hybris.platform.commercefacades.search.converters.populator.SearchResultVariantProductPopulator;
import de.hybris.platform.commerceservices.search.resultdata.SearchResultValueData;
public class TrainingSearchResultVariantProductPopulator extends SearchResultVariantProductPopulator
{
	
	@Override
	public void populate(final SearchResultValueData source, final ProductData target)
	{
		super.populate(source, target);
		//Adding online days to product data
		final Object obj = this.getValue(source, "onlineDays");
		if (obj != null)
		{
			target.setOnlineDays(this.<Integer> getValue(source, "onlineDays").intValue());
		}
		else
		{
			target.setOnlineDays(0);
		}


	}
}


Step 6


Define the above populator as a spring bean and link it to the existing populator using alias.

Add bean definition in trainingfacades-spring.xml as below

hybris\bin\custom\training\trainingfacades\resources\trainingfacades-spring.xml

1
2
3
4
5
<alias name="trainingSearchResultVariantProductPopulator" alias="commerceSearchResultProductPopulator"/>
    <bean id="trainingSearchResultVariantProductPopulator"
          class="org.training.facades.search.converters.populators.TrainingSearchResultVariantProductPopulator"
          parent="variantCommerceSearchResultProductPopulator">
    </bean>
<alias name="trainingSearchResultVariantProductPopulator" alias="commerceSearchResultProductPopulator"/>
    <bean id="trainingSearchResultVariantProductPopulator"
          class="org.training.facades.search.converters.populators.TrainingSearchResultVariantProductPopulator"
          parent="variantCommerceSearchResultProductPopulator">
    </bean>


Step 7

Modify the autocomplete.js file to include this new value as part of search box suggestion result.

hybris\bin\custom\training\trainingstorefront\web\webroot\_ui\desktop\common\js\acc.autocomplete.js

1
renderHtml +=   "<span class='title'>" + "Online Days " +item.onlineDays +"</span>";
renderHtml += 	"<span class='title'>" + "Online Days " +item.onlineDays +"</span>";

Add above line just below the line renderHtml +=”“;

1
onlineDays:obj.onlineDays
onlineDays:obj.onlineDays

Add above line just below the line
image: (obj.images!=null && self.options.displayProductImages) ? obj.images[0].url : null


Step 8

Import solr.impex file
Import the solr.impex file either manually through HAC or update the system to get it imported automatically

Step 9

Update products with online and offline dates in HMC as below

product offline date set


Step 10

Perform full indexing through HMC by selecting apparel-uk solr config

Step 11


Check the output

solr search suggestion modified output

This attribute specifies that how many more days product will be available on the site from the current date.


Displaying this attribute in the search result page

We need to modify productListerGridItem.tag file
hybris\bin\custom\training\trainingstorefront\web\webroot\WEB-INF\tags\desktop\product\productListerGridItem.tag

1
Product available for ${product.onlineDays} days
Product available for ${product.onlineDays} days

Add above line just below the div
< div class="details">

it should be like below

1
2
3
4
<div class="details">
<ycommerce:testId code="product_productName">${product.name}</ycommerce:testId>
</div>
Product available for ${product.onlineDays} days
<div class="details">
<ycommerce:testId code="product_productName">${product.name}</ycommerce:testId>
</div>
Product available for ${product.onlineDays} days



Now we can search a product and see the search result

Enter product code and click on search icon

See the below output

solr_search_result_page_output

About the Author

Karibasappa G C (KB)
Founder of javainsimpleway.com
I love Java and open source technologies and very much passionate about software development.
I like to share my knowledge with others especially on technology 🙂
I have given all the examples as simple as possible to understand for the beginners.
All the code posted on my blog is developed,compiled and tested in my development environment.
If you find any mistakes or bugs, Please drop an email to kb.knowledge.sharing@gmail.com

Connect with me on Facebook for more updates

Share this article on