Solr Sort Hybris - sorting

I have a provider, wich index 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 diferent informations, how can I select which one to use?

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!
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>
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>
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" />
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>
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.

Related

Can I make a TextInput required?

I have a lot of code like
if (myTextInput.text != "") {
handleEvent();
}
Does TextInput have some property I can use to automatically check for an empty string? Similar to how if I set it like :
<s:TextInput id="myInput" enter="myInputHandler()" restrict="A-Za-Z0-9"/>
then myInputHandler() only gets called if the text is alphanumeric. I'd like to add an additional restriction that length is greater than 0.
I know about the validators, but I still have to call them manually.
To make a TextInput component "required", you can, for example, create your own text input component and use a property to indicate if the control is required or not, and some event listeners like for FocusEvent.FOCUS_OUT event to force your user to enter something in that input.
For that, take this example :
package myComponent
{
import flash.events.FocusEvent;
import spark.components.TextInput;
public dynamic class MyTextInput extends TextInput
{
private var _required:Boolean = false;
public function MyTextInput()
{
super();
this.addEventListener(FocusEvent.FOCUS_OUT, on_KeyDown);
}
public function set required(required:Boolean): void {
this._required = required;
}
public function get required(): Boolean {
return this._required;
}
private function on_KeyDown(e:FocusEvent): void {
if(this.text == '' && this._required){
this.setFocus();
}
}
}
}
Of course this is just an example, you can use any behavior you want when your user left the input empty ...
Then to use that new component :
<s:Application xmlns:fx="http://ns.adobe.com/mxml/2009"
xmlns:s="library://ns.adobe.com/flex/spark"
xmlns:mx="library://ns.adobe.com/flex/mx"
<!-- don't forget to include the namespace definition for your custom component -->
xmlns:MyComponent="myComponent.*">
<MyComponent:MyTextInput required="true" restrict="A-Za-z0-9"/>
</s:Application>
For more about creating your own components, take a look here.
Hope that can help.
Try :
if ( String(myTextInput.text).length > 0 )
{
handleEvent();
}
If that's all the code you need (no extra commands) then just do as one-line :
if ( String(myTextInput.text).length > 0 ) { handleEvent(); }
Maybe, this is not the kind of solution but, you can put the TextField into a FormItem, which has the "required" field

How to sort a list that will be localized in JSF output

In my application I have a list of keys (strings), where the user can select one of them. In the user-interface, the keys will be output according to the current locale:
<h:selectOneMenu value="#{bean.selectedKey}">
<f:selectItems value="#{bean.allKeys}" var="_key" itemLabel="#{msgs[_key]}" />
</h:selectOneMenu>
My setup uses a standard resource-bundle configured in faces-config.xml as explained in this answer by BalusC. msgs in the example above is the name of the resource-bundle variable.
What I want now, is the items from the selectOneMenu to be sorted in alphabetic order. Of course the order depends on the used locale. The problem is, I can't/won't do the sorting in the backing-bean, as I don't know how the JSF-page will output the keys.
This problem seems quite generic to me, so I'm wondering what the best practice is to solve this kind of problem.
(Of course the problem is not only applicable to selectOneMenu. Any list/collection that will be output in the user-interface suffers from the same problem.)
You've basically 2 options.
Sort in client side with a little help of JS. I'll for simplicity assume that you're using jQuery.
<h:selectOneMenu ... styleClass="ordered">
$("select.ordered").each(function(index, select) {
var $select = $(select);
$select.find("option").sort(function(left, right) {
return left.text == right.text ? 0 : left.text < right.text ? -1 : 1;
}).appendTo($select);
if (!$select.find("option[selected]").length) {
select.options.selectedIndex = 0;
}
});
Sort in server side wherein you create List<SelectItem> and grab #{msgs} via injection. Assuming that you're using CDI and thus can't use #ManagedProperty("#{msgs}"), you'd need to create a producer for that first. I'll for simplicity assume that you're using OmniFaces (which is also confirmed based on your question history).
public class BundleProducer {
#Produces
public PropertyResourceBundle getBundle() {
return Faces.evaluateExpressionGet("#{msgs}");
}
}
Then you can make use of it as follows in the backing bean associated with <f:selectItems value>:
private static final Comparator<SelectItem> SORT_SELECTITEM_BY_LABEL = new Comparator<SelectItem>() {
#Override
public int compare(SelectItem left, SelectItem right) {
return left.getLabel().compareTo(right.getLabel());
}
};
private List<SelectItem> allKeys;
#Inject
private PropertyResourceBundle msgs;
#PostConstruct
public void init() {
allKeys = new ArrayList<>();
for (String key : keys) {
allKeys.add(new SelectItem(key, msgs.getString(key)));
}
Collections.sort(allKeys, SORT_SELECTITEM_BY_LABEL);
}
And reference it directly without var as follows:
<f:selectItems value="#{bean.allKeys}" />

Error 6046: Unable to generate function import return type of the store function

I have a scalar-valued function in my sql database.
I receive this error when importing this function into Entity Framework model:
Error 6046: Unable to generate function import return type of the store function 'GetContentByIdAndCul'.
The store function will be ignored and the function import will not be generated. ..\EntityModels.edmx
my function tsql is:
ALTER FUNCTION [FRM].[GetContentByIdAndCul]
(#Id int,#Culture nvarchar(5))
RETURNS nvarchar(max)
AS
BEGIN
declare #Result nvarchar(max)
if #Id is not null
set #Result='This Content not defined in this Language'
select #Result=Value from CUL.Contents
WHERE ID=#Id AND (CUL.Contents.Culture = LOWER(#Culture)
OR CUL.Contents.Culture = LOWER(SUBSTRING(#Culture,1,2)))
return #Result
END
Preceding answers show the good way to solve the problem but none works in real life.
Here's a tested solution with Entity Framework 6 that works for me. So it should works for you.
Import your scalar valued function
Import your scalar valued function [FRM].[GetContentByIdAndCul] into your Entity Framework model. It automatically creates corresponding entry in the storage model of your EntityModels.edmx file :
<Function Name="GetContentByIdAndCul" Aggregate="false" BuiltIn="false" NiladicFunction="false" IsComposable="true" ParameterTypeSemantics="AllowImplicitConversion" Schema="FRM" ReturnType="nvarchar(max)">
<Parameter Name="Id" Type="int" Mode="In" />
<Parameter Name="Culture" Type="nvarchar(5)" Mode="In" />
</Function>
Add code to wrap call to your scalar valued function
Create new source file and add code to auto generated DbContext class (say her name is MyEntities) using Partial class mechanism (https://msdn.microsoft.com/en-us/library/wa80x488%28v=vs.120%29.aspx)
public partial class MyEntities
{
[DbFunction("EntityModels.Store", "GetContentByIdAndCul")]
public string GetContentByIdAndCul(int id, string culture)
{
var objectContext = ((IObjectContextAdapter)this).ObjectContext;
var parameters = new List<ObjectParameter>();
parameters.Add(new ObjectParameter("Id", id));
parameters.Add(new ObjectParameter("Culture", culture));
return objectContext.CreateQuery<string>("EntityModels.Store.GetContentByIdAndCul(#Id, #Culture)", parameters.ToArray())
.Execute(MergeOption.NoTracking)
.FirstOrDefault();
}
}
Use your scalar valued function
Client code :
using (var context = new MyEntities())
{
int id = 1;
string culture = "fr-FR";
string result = null;
result = context.GetContentByIdAndCul(id, culture);
}
Until today, entity framework doesn't suport generating calls for scalar functions.
But, you can solve the problem writting a custom method like this inside your DbContext class:
public partial class YouDbContext
{
[DbFunction("YouDbContext.Store", "YourScalarFunction")]
public string YourScalarFunction(string parameter)
{
var lObjectContext = ((IObjectContextAdapter)this).ObjectContext;
return lObjectContext.
CreateQuery<string >(
"YouDbContext.Store.YourScalarFunction",
new ObjectParameter("parameterName", parameter)).
Execute(MergeOption.NoTracking).
FirstOrDefault();
}
}
Create a partial class for YOURMODEL.Context.cs: public partial class YOUREntities : DbContext
[DbFunction("YOURModel.Store", "YOURSCALARFUNCTION")]
public string YOURSCALARFUNCTION(string PARAMETER)
{
List<ObjectParameter> parameters = new List<ObjectParameter>(3);
parameters.Add(new ObjectParameter("PARAMETER", PARAMETER));
var lObjectContext = ((IObjectContextAdapter)this).ObjectContext;
var output = lObjectContext.
CreateQuery<string>("YOURMODEL.Store.YOURSCALARFUNCTION(#PARAMETER)", parameters.ToArray())
.Execute(MergeOption.NoTracking)
.FirstOrDefault();
return output;
}
Be sure that you have your function added to YOURMODEL.EDMX, should be some thing like this:
<Function Name="YOURSCALARFUNCTION" Aggregate="false" BuiltIn="false" NiladicFunction="false" IsComposable="true" ParameterTypeSemantics="AllowImplicitConversion" Schema="dbo" ReturnType="nvarchar(max)">
<Parameter Name="PARAMETER" Type="nvarchar(max)" Mode="In" />
</Function>

How to customize default message for BigDecimal

Here are the models:
public class myFormData {
private BigDecimal trackingNumber;
#Valid
private List<myCustomRecord> records = new ArrayList<myCustomRecord>();
}
public class myCustomRecord {
//the field also appears here (for child records)
private BigDecimal trackingNumber;
}
I have a controller method which receives this object at some point to do a save.
public #ResponseBody ValidationResponse save(Model model,
#Valid myFormData formData, BindingResult result,
HttpSession session){
//do stuff
}
The problem I'm having is if a string is passed into the trackingNumber field, the error message is not nice.
Failed to convert property value of type 'java.lang.String' to required type 'java.math.BigDecimal' for property 'records[0].trackingNumber'; nested exception is java.lang.NumberFormatException
Failed to convert property value of type 'java.lang.String' to required type 'java.math.BigDecimal' for property 'trackingNumber'; nested exception is java.lang.NumberFormatException
One other potential complication is I'm not using Spring form since I'm trying to do some ajax submissions. This is how I'm submitting the data:
function collectFormData(fields) {
var data = {};
//myFormData
data["trackingNumber"] = <<some value here>>;
for (var i = 0; i < fields.length; i++) {
data["records["+i+"].trackingNumber"] = <<some value>>;
//some more data to submit for child records
}
return data;
}
var mData = collectFormData(aSelectedRows);
if (aSelectedRows.length> 0) {
$.post('call_controller_save', mData, function(response) {
if (response.status === 'FAIL') {
displayError(response);
} else {
//SAVE SUCCESSFUL
$('#successMsgs').append("Data Saved");
}, 'json');
}
I've tried changing trackingNumber's data type to String and wrote a custom ConstraintValidator<CheckNumeric, String>. It does the trick... I can put in a custom message via ValidationMessages.properties, but I would like to keep the models true to the intended data type. Is there another way to control the error message and turn it into something nice and user friendly?
(I also understand the NumberFormatException is most likely happening in data binding, before the validation step? But I'm still not sure how to go about fixing the default message)
Edit
I use the following configuration
<bean class="org.springframework.context.support.ResourceBundleMessageSource"
id="messageSource">
<property name="basenames">
<list>
<value>messages</value>
<value>ValidationMessages</value>
</list>
</property>
</bean>
In each properties file, I have the identical content.
formData.trackingNumber=Invalid format
But the custom message is still not getting picked up. Question 2: Should it be formData or myFormData? I have confusion whether it is the class name or name of the form object used on the jsp page?
Add to validation messages - this kind of format is used by default by Spring if it cannot convert from supplied text to number:
typeMismatch.java.math.BigDecimal=Required format is number.
I dont think there is a way to control the validation error message in this case as it happens before the invocation of validate().
The thing we can do is,
Define validationError.properties
Have custom error message like
formData.trackingNumber=Invalid Format
Define a spring bean for org.springframework.context.support.ResourceBundleMessageSource class with p:basename as validationError

How to manually create Proxy for Detail Collection in NHibernate

I've got class, let it be Foo:
public class Foo
{
...
protected MyCollection<Detail> _details
public virtual MyCollection<Detail> Details
{
get { return _details ?? new MyCollection<Details>(); }
set { _details = value; ... }
}
...
}
public class Detail {...}
When I do LINQ query:
var q = session.Query<Foo>().Select(foo => new Foo( property1 = foo.property1, ... );
...
q.ToList();
I've got NULL in _details field, and when I access to Details to get all Lazy details, of course I get new MyCollection(), but not IPersistentBag (or else, IPersistentCollection).
So How can I manually create proxy collection (I've got session / sessionFactory references)?
[ Added ] here is the mappings (on Foo):
<bag name="Details" lazy="true" collection-type="NHibernateDataService.DetailBag`1[[DataObjects.Detail, DataObjects]], NHibernateDataService" cascade="all-delete-orphan" fetch="select" batch-size="1" access="property" inverse="true">
<key column="`Master`" />
<one-to-many class="DataObjects.Detail" />
</bag>
Thank you!
MyCollection can't be mapped to IPersistentBag, unless, of course, you implement that interface on MyCollection (and probably set the mapping type explicitly??)...
A more standard approach is to set the "collection-type" property on the bag mapping to a custom type - an implementation of IUserCollectionType, which you can choose to make a base class that MyCollection derives from.

Resources