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
After implementation output will be as below
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
- 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
- <!-- 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
- #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
- <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
- //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
- 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
- <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
- 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 +=”“;
- 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
Step 10
Perform full indexing through HMC by selecting apparel-uk solr config
Step 11
Check the 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
- 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
- <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
KB, great articles! Do you think you could write one about using Hybris with Solr searching as in writing queries? I am struggling right now with trying to exclude a facet value through setting a value for SearchQueryData. Works in Solr but Hybris strips it away. Thanks!
Nice explanation and helped a lot.
Can you please share how to work with Facets?
Hi Karibasappa
You have mentioned that we should use the same value in indexed property that we have used in ProductOnlineDaysValueProvider and we have used noOfDaysOnline to create field name in our value provider but you have used onlineDays in indexed property in Solr.impex which is a ProductData Attribute. So it should noOfDaysOnline in Solr.impex as well to get indexed with value provider custom data.
Please correct me if i am wrong?
Please observe Step 3 , Its mentioned clearly.
Hi,
Thank you for your posts, they are really helpful. 🙂
I’m having an issue with Solr update indexing where if I change a product ‘approvalStatus’ attribute to unapproved, then it doesn’t get updated on Solr, unless I run a full indexing, which isn’t really great for such a small change. I couldn’t find a solution for this up till now.
Following your tutorials, I would think that for approvalStatus wouldn’t need a ValueProvider file as the value of the attribute wouldn’t really need changing. I did notice that on Solr Item Types, approvalStatus was not on the list, so I run an impex to add it, which were fine, but still no luck running indexing update.
I even created a ValueProvider file to test it trying to force it somehow, didn’t work either.
I wonder if you could give me any clue on why that would be happening?
Hi KB,
i have followed all the steps,but did not work for me . i could not see the onlinedays in the cart as well as in the productListerGridItem.tag page . i am seeing error in autoload.js file as onlineDays:obj.onlineDays , as Unexpected token onlineDays .
please help me.
Thanks,
Shankar
Very nice example.please add some more project examples
Sure, Thank you !!
Hi Karibasappa, its so nice to visit your site, i learned many things in hybris here. Can you please explain about business process, data hub, checkout flow. I know this is a lot to ask but i believe these things will be very much helpful.
HI!
Thanks for your tutorial.
I tried adding new indexed field (productOnlineDay) to SolrIndexedProperty.
I already saw my new indexed field on hmc (facet search indexed type) but unfortunately when I changed the offline date value and the solr will reindex the product but it dont add the my new indexed field to sorl. I debuged the code and it went to my Provider code.
Did I do it wrong…
Thanks
oh I just only ran a update index… When I ran full index and it created my new indexed field. Thanks
ok great 🙂
Simple nice and useful , appreciating.
Please keep more use case on hybris.
Thank you Sudipta !! sure will do it
Hi Karibasappa ;
Really nice with simple words , appreciating .
Please post some more use case on Hybris .
Hi,
You have give very nice explanation of Solr implementation.
Can you please suggest me how can I integrate Natural language search in solr with hybris.
Is there and out of the box feature available or we have to use any other way.
Hi,
final Object obj = this.getValue(source, “onlineDays”);
i am getting every time is null, please help me on this.
Hi,
Check in HMC whether offlineDate for a product you are searching is set or its null.
by default , it will be null.
So set this value to some future date and do the indexing and then search it.