Spring Boot & JPA: CriterialBuilder check if String contains Expression<String> - spring-boot

1.) I want to get an Expression using an Expression and a String Pattern.
It should become true if the Expression is included in the String Pattern not the other way.
The string pattern can look like "MFD" or "MF" or any other combination of the three letters M,F,D. The Expression is either M, F or D.
So far I have the following code, but i think there is a better solution.
public class AthleteSpecification implements Specification<Athlete> {
private String gender;
public AthleteSpecification(String gender) {
super();
this.gender = gender;
}
#Override
public Predicate toPredicate(Root<Athlete> root, CriteriaQuery<?> query, CriteriaBuilder cb) {
Predicate p = cb.disjunction();
if (this.gender == null) {
return cb.conjunction();
} else {
Expression<Boolean> isMaleOrFemaleorDivers;
if (this.gender != null) {
// This is a working solution but not nice
Expression<Boolean> isMale = cb.equal(root.get("gender"), gender.contains("M") ? "M" : "");
Expression<Boolean> isMaleOrFemale = cb.or(isMale,
cb.equal(root.get("gender"), gender.contains("F") ? "F" : ""));
isMaleOrFemaleorDivers = cb.or(isMaleOrFemale,
cb.equal(root.get("gender"), gender.contains("D") ? "D" : ""));
// This is not a solution because i think it only checks if the String is in the Expression<String> not the other way
// Expression<Integer> test = cb.locate(root.get("gender"), gender);
// isMaleOrFemaleorDivers = cb.greaterThan(test, 0);
} else {
isMaleOrFemaleorDivers = cb.conjunction();
}
p.getExpressions().add(cb.and(isNameMatching, isMaleOrFemaleorDivers));
return p;
}
}
}

Related

How to append specification based on condition in spring data jpa

I am creating a spring boot project and working on spring data jpa and currently I am using custom query to fetch data from db based on users selection and this is my page where user can select option based on their condition https://i.imgur.com/coO3BCJ.png
So, I googled it and found we can use specification but as I am very new to specification, so, I used specification and I want that based on users choice, it should keep adding specification, so, this is my conditional specification...
Specification<UserDetails> specification =
Specification.where(UserDetailsSpecification
.isAgeBetween(customSearch.getFromage(), customSearch.getToage()));
if(!customSearch.getManglik().isBlank()) {
specification.and(UserDetailsSpecification.isManglik
(customSearch.getManglik()));
}
if(!customSearch.getMaritalStatus().isBlank()) {
specification.and(UserDetailsSpecification
.hasMaritalStatus(customSearch.getMaritalStatus()));
}
if(!customSearch.getReligion().isBlank()) {
specification.and(UserDetailsSpecification.hasReligion
(customSearch.getReligion()));
}
if(!customSearch.getCaste().isBlank()) {
specification.and(UserDetailsSpecification.hasCaste
(customSearch.getCaste()));
}
if(!customSearch.getLocation().isBlank()) {
specification.and(UserDetailsSpecification.hasLocation
(customSearch.getLocation()));
}
listOfCustomSearch=userDetailsRepository
.findAll(specification, pageable);
List<UserDetails> listOfAllSearchedUsers = listOfCustomSearch.getContent();
but it is not appending the specification and just filtering the data based on only
Specification<UserDetails> specification = Specification.where(UserDetailsSpecification
.isAgeBetween(customSearch.getFromage(), customSearch.getToage()));
so, based on users selection, so, my final query should be something like this(If user has selected all fields):
Specification<UserDetails> specification = Specification.where(UserDetailsSpecification
.isAgeBetween(customSearch.getFromage(), customSearch.getToage())
.and(UserDetailsSpecification.isManglik(customSearch.getManglik()) .and(UserDetailsSpecification.hasMaritalStatus(customSearch.getMaritalStatus())) .and(UserDetailsSpecification.hasReligion(customSearch.getReligion()))
.and(UserDetailsSpecification.hasCaste(customSearch.getCaste()))
.and(UserDetailsSpecification.hasLocation(customSearch.getLocation()))))
But suppose if user has selected only let suppose 3 or 4 fields, so, my final specification should be something like below:(This specification should be completely depends upon user selection)
Specification<UserDetails> specification = Specification.where(UserDetailsSpecification
.isAgeBetween(customSearch.getFromage(), customSearch.getToage())
.and(UserDetailsSpecification.isManglik(customSearch.getManglik())
.and(UserDetailsSpecification.hasLocation(customSearch.getLocation()))))
Currently it is not appending specification based on users selection, so, please help me in adding specification based on users selections
Finally, I found the solution and we can create customized specification like this below:
public static Specification<UserDetails> getSpecs(String gender, int fromAge, int toAge, String manglikStatus, String maritalStatus, String religion, String caste, String location){
Specification<UserDetails> spec = null;
Specification<UserDetails> temp = null;
if(!gender.isBlank() && !gender.isEmpty() && gender!=null && !gender.contains("$")) {
spec = getSpecsForGenderDetails(gender);
temp = spec!=null?Specification.where(spec).and(temp):temp;
}
if(fromAge!=0 || toAge!=0) {
spec = isAgeBetween(fromAge, toAge);
temp = spec!=null?Specification.where(spec).and(temp):temp;
}
if(!manglikStatus.isBlank() && !manglikStatus.isEmpty() && manglikStatus!=null && !manglikStatus.contains("$")) {
spec = isManglik(manglikStatus);
temp = spec!=null?Specification.where(spec).and(temp):temp;
}
if(!maritalStatus.isBlank() && !maritalStatus.isEmpty() && maritalStatus!=null && !maritalStatus.contains("$")) {
spec = hasMaritalStatus(maritalStatus);
temp = spec!=null?Specification.where(spec).and(temp):temp;
}
if(!religion.isBlank() && !religion.isEmpty() && religion!=null && !religion.contains("$")) {
spec = hasReligion(religion);
temp = spec!=null?Specification.where(spec).and(temp):temp;
}
if(!caste.isBlank() && !caste.isEmpty() && caste!=null && !caste.equalsIgnoreCase("select") && !caste.contains("$")) {
spec = hasCaste(caste);
temp = spec!=null?Specification.where(spec).and(temp):temp;
}
if(!location.isBlank() && !location.isEmpty() && location!=null && !location.contains("$")) {
spec = hasLocation(location);
temp = spec!=null?Specification.where(spec).and(temp):temp;
}
return temp;
}
And based on that we can define our method like this:
private static Specification<UserDetails> getSpecsForGenderDetails(String gender) {
return ((root, query, criteriaBuilder) -> {
return criteriaBuilder.equal(root.get("gender"),gender);
});
}
private static Specification<UserDetails> isAgeBetween(int fromAge, int toAge){
return ((root, query, criteriaBuilder) -> {
return criteriaBuilder.between(root.get("age"), fromAge, toAge);
});
}
private static Specification<UserDetails> isManglik(String manglikStatus){
return ((root, query, criteriaBuilder) -> {
return criteriaBuilder.like(criteriaBuilder.lower(root.get("manglikStatus")),"%" +manglikStatus.toLowerCase() +"%");
});
}
private static Specification<UserDetails> hasMaritalStatus(String maritalStatus){
return ((root, query, criteriaBuilder) -> {
return criteriaBuilder.like(criteriaBuilder.lower(root.get("maritalStatus")),"%" +maritalStatus.toLowerCase() +"%");
});
}
private static Specification<UserDetails> hasReligion(String religion){
return ((root, query, criteriaBuilder) -> {
return criteriaBuilder.like(criteriaBuilder.lower(root.get("religion")),"%" +religion.toLowerCase() +"%");
});
}
private static Specification<UserDetails> hasCaste(String caste){
return ((root, query, criteriaBuilder) -> {
return criteriaBuilder.like(criteriaBuilder.lower(root.get("caste")),"%" +caste.toLowerCase() +"%");
});
}
private static Specification<UserDetails> hasLocation(String presentState){
return ((root, query, criteriaBuilder) -> {
return criteriaBuilder.like(criteriaBuilder.lower(root.get("presentState")),"%" +presentState.toLowerCase() +"%");
});
}

Custom fields with FormBuilder in the Microsoft Bot Framework - not working

I tried this solution: Custom fields with FormBuilder in the Microsoft Bot Framework
But failed to get it working....The problem I encountered is that when I assign the base.Form = value, the _prompt in the _field gets a default recognizer, and it won't get overriden in the next line's SetRecognizer call, that only replaces the _field's recognizer.
However the matching process uses the _prompt's recognizer internally ( ? ).
Here is my code:
public class LuisIntentRecognizer<T> : RecognizePrimitive<T>
where T : class
{
public LuisIntentRecognizer(IField<T> field, string luisModelID, string luisSubscriptionKey)
: base(field)
{
_luisModelID = luisModelID;
_luisSubscriptionKey = luisSubscriptionKey;
}
public override DescribeAttribute ValueDescription(object value)
{
return new DescribeAttribute((string)value);
}
public override IEnumerable<string> ValidInputs(object value)
{
yield return (string)value;
}
public override TermMatch Parse(string input)
{
TermMatch result = null;
if (!string.IsNullOrWhiteSpace(input))
{
var luisModel = new LuisModelAttribute(_luisModelID, _luisSubscriptionKey);
var luisService = new LuisService(luisModel);
var luisResult = luisService.QueryAsync(input).Result; // TODO refactor somehow to async
var winner = luisResult.Intents.MaxBy(i => i.Score ?? 0d);
if (winner != null && !string.IsNullOrEmpty(winner.Intent))
{
result = new TermMatch(0, winner.Intent.Length, 0.0, winner.Intent);
}
else
{
result = new TermMatch(0, input.Length, 0.0, input);
}
}
return result;
}
public override string Help(T state, object defaultValue)
{
var prompt = new Prompter<T>(_field.Template(TemplateUsage.StringHelp), _field.Form, null);
var args = HelpArgs(state, defaultValue);
return prompt.Prompt(state, _field.Name, args.ToArray()).Prompt;
}
private string _luisModelID;
private string _luisSubscriptionKey;
}
public class LuisIntentField<T> : FieldReflector<T>
where T : class
{
public LuisIntentField(string name, string luisModelID, string luisSubscriptionKey, bool ignoreAnnotations = false)
: base(name, ignoreAnnotations)
{
_luisModelID = luisModelID;
_luisSubscriptionKey = luisSubscriptionKey;
}
public override IForm<T> Form
{
set
{
base.Form = value;
base.SetRecognizer(new LuisIntentRecognizer<T>(this, _luisModelID, _luisSubscriptionKey));
}
}
private string _luisModelID;
private string _luisSubscriptionKey;
}
Could anyone get it working?
Thanks
It seems to be a bug in the framework indeed: https://github.com/Microsoft/BotBuilder/issues/879

NSubstitute not matching Linq Expression

I am implementing a repository pattern Query class and testing using NSubstitute.
Repository interface:
public interface IMyRepository
{
IQueryable<T> Query<T>(Expression<Func<T, bool>> filter) where T : class;
}
DateTimeProvider interface:
public interface IMyDateTimeProvider
{
DateTime GetDateNow();
}
Application interface:
public interface IMyApplication
{
List<Thing> GetThingsByQuery(int status);
}
Application implementation:
public class MyApplication : IMyApplication
{
private readonly IMyRepository myRepository;
private readonly IMyDateTimeProvider myDateTimeProvider;
public MyApplication(IMyRepository myRepository, IMyDateTimeProvider myDateTimeProvider)
{
this.myRepository = myRepository;
this.myDateTimeProvider = myDateTimeProvider;
}
public List<Thing> GetThingsByQuery(int status)
{
var createdDate = this.myDateTimeProvider.GetDateNow();
return this.myRepository.Query<Thing>(t => t.CreatedDate == createdDate && t.Status == status).ToList();
}
}
Test:
[TestClass]
public class ApplicationTest
{
private IMyApplication myApplication;
private IMyDateTimeProvider myDateTimeProvider;
private IMyRepository myRepository;
[TestMethod]
public void QueriesRepository()
{
// Arrange
var createdDate = new DateTime(2014, 1, 1);
this.myDateTimeProvider.GetDateNow().Returns(createdDate);
const int Status = 1;
// Act
this.myApplication.GetThingsByQuery(Status);
// Assert
this.myRepository.Received().Query<Thing>(t => t.CreatedDate == createdDate && t.Status == Status);
}
[TestInitialize]
public void TestInitialize()
{
this.myRepository = Substitute.For<IMyRepository>();
this.myDateTimeProvider = Substitute.For<IMyDateTimeProvider>();
this.myApplication = new MyApplication(this.myRepository, this.myDateTimeProvider);
}
}
But the test fails with the following message:
NSubstitute.Exceptions.ReceivedCallsException: Expected to receive a call matching:
Query<Thing>(t => ((t.CreatedDate == value(MySolution.Test.ApplicationTest+<>c__DisplayClass0).createdDate) AndAlso (t.Status == 1)))
Actually received no matching calls.
Received 1 non-matching call (non-matching arguments indicated with '*' characters):
Query<Thing>(*t => ((t.CreatedDate == value(MySolution.Application.MyApplication+<>c__DisplayClass0).createdDate) AndAlso (t.Status == value(MySolution.Application.MyApplication+<>c__DisplayClass0).status))*)
The DateTime and Status are being parsed into value() which are different between the Application and the Test.
Why is this? How can I fix this?
For complicate expressions if often find it easier to assert on captured arguments by using callbacks than with Received(). An (incomplete) example:
Expression<Func<Thing, bool>> receivedFilter receivedFilter = null;
myRepository.When(x => x.Query<Thing>(Arg.Any<...>))
.Do(x => receivedQuery = x.Arg<Expression<Func<Thing, bool>>>());
Then, assert on the captured filter expression. It might actually simpler to just execute the expression's filter func (see e.g. here)
Func<Thing, bool> predicate = receivedFilter.Compile();
var matchingThing = new Thing
{ CreatedDate = createdData, Status = Status };
// assert matching
predicate(matchingThing).Should().BeTrue();
// assert non.matching
predicate(nonMatchingThing).Should().BeFalse();
This approach seems to make the test a little more black-boxed but this is in general not a bad thing.
The default equality comparer for an Expression is being used (referential equality):
eg, the expression (t => t.CreatedDate == createdDate && t.Status == Status``) in:
this.myRepository.Received().Query<Thing>(t => t.CreatedDate == createdDate
&& t.Status == Status );
Is a different instance to the expression in:
return this.myRepository.Query<Thing>(t => t.CreatedDate == createdDate
&& t.Status == status ).ToList();
To fix validate this method call check out argument matchers within NSubstitute.
But as an example:
Func<Expression<Thing, bool>, bool> validator =
// TODO this needs to be written properly, based on the expression,
// not its string representation
e => e.Body.ToString() == "t.CreatedDate == createdDate
&& t.Status == Status";
this.myRepository.Received().Query<Thing>(Arg.Is<Expression<Thing, bool>>(validator));

Trying to save comma-separated list

Trying to save selections from a CheckBoxList as a comma-separated list (string) in DB (one or more choices selected). I am using a proxy in order to save as a string because otherwise I'd have to create separate tables in the DB for a relation - the work is not worth it for this simple scenario and I was hoping that I could just convert it to a string and avoid that.
The CheckBoxList uses an enum for it's choices:
public enum Selection
{
Selection1,
Selection2,
Selection3
}
Not to be convoluted, but I use [Display(Name="Choice 1")] and an extension class to display something friendly on the UI. Not sure if I can save that string instead of just the enum, although I think if I save as enum it's not a big deal for me to "display" the friendly string on UI on some confirmation page.
This is the "Record" class that saves a string in the DB:
public virtual string MyCheckBox { get; set; }
This is the "Proxy", which is some sample I found but not directly dealing with enum, and which uses IEnumerable<string> (or should it be IEnumerable<Selection>?):
public IEnumerable<string> MyCheckBox
{
get
{
if (String.IsNullOrWhiteSpace(Record.MyCheckBox)) return new string[] { };
return Record
.MyCheckBox
.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries)
.Select(r => r.Trim())
.Where(r => !String.IsNullOrEmpty(r));
}
set
{
Record.MyCheckBox = value == null ? null : String.Join(",", value);
}
}
To save in the DB, I am trying to do this in a create class:
proxy.MyCheckBox = record.MyCheckBox; //getting error here
but am getting the error:
Cannot implicitly convert 'string' to System.Collections.Generic.IEnumerable'
I don't know, if it's possible or better, to use Parse or ToString from the API for enum values.
I know that doing something like this will store whatever I put in the ("") into the DB, so it's just a matter of figuring out how to overcome the error (or, if there is an alternative):
proxy.MyCheckBox = new[] {"foo", "bar"};
I am not good with this stuff and have just been digging and digging to come up with a solution. Any help is much appreciated.
You can accomplish this using a custom user type. The example below uses an ISet<string> on the class and stores the values as a delimited string.
[Serializable]
public class CommaDelimitedSet : IUserType
{
const string delimiter = ",";
#region IUserType Members
public new bool Equals(object x, object y)
{
if (ReferenceEquals(x, y))
{
return true;
}
var xSet = x as ISet<string>;
var ySet = y as ISet<string>;
if (xSet == null || ySet == null)
{
return false;
}
// compare set contents
return xSet.Except(ySet).Count() == 0 && ySet.Except(xSet).Count() == 0;
}
public int GetHashCode(object x)
{
return x.GetHashCode();
}
public object NullSafeGet(IDataReader rs, string[] names, object owner)
{
var outValue = NHibernateUtil.String.NullSafeGet(rs, names[0]) as string;
if (string.IsNullOrEmpty(outValue))
{
return new HashSet<string>();
}
else
{
var splitArray = outValue.Split(new[] {Delimiter}, StringSplitOptions.RemoveEmptyEntries);
return new HashSet<string>(splitArray);
}
}
public void NullSafeSet(IDbCommand cmd, object value, int index)
{
var inValue = value as ISet<string>;
object setValue = inValue == null ? null : string.Join(Delimiter, inValue);
NHibernateUtil.String.NullSafeSet(cmd, setValue, index);
}
public object DeepCopy(object value)
{
// return new ISet so that Equals can work
// see http://www.mail-archive.com/nhusers#googlegroups.com/msg11054.html
var set = value as ISet<string>;
if (set == null)
{
return null;
}
return new HashSet<string>(set);
}
public object Replace(object original, object target, object owner)
{
return original;
}
public object Assemble(object cached, object owner)
{
return DeepCopy(cached);
}
public object Disassemble(object value)
{
return DeepCopy(value);
}
public SqlType[] SqlTypes
{
get { return new[] {new SqlType(DbType.String)}; }
}
public Type ReturnedType
{
get { return typeof(ISet<string>); }
}
public bool IsMutable
{
get { return false; }
}
#endregion
}
Usage in mapping file:
Map(x => x.CheckboxValues.CustomType<CommaDelimitedSet>();

How do I apply a default IComparable<T> in a Linq OrderBy clause

I have a type which has a default sort order as it implements IComparable<T> and IComparable. I'm not getting the results I expect from LINQ , basically it looks as if the IComparable<T> which the type implements is not being applied.
I thought I would get the result I want with an expression in the form:
var result = MyEnumerable<T>.OrderBy(r => r);
where T itself implements IComparable<T>. It's not happening.
I can see related questions where specific IComparable<T> classes are specified for the sort, but I can't find one which uses the default IComparable<T> implemented by T itself.
My syntax is clearly incorrect. What is the correct syntax please?
Thanks in advance.
OrderBy uses the default comparer Comparer<T>.Default which in turn will default to use the IComparable<T> implementation for T, or the non-generic IComparable if the former does not exist.
This code works:
public class Program
{
static void Main(string[] args)
{
var list = new List<Stuff>
{
new Stuff("one"),
new Stuff("two"),
new Stuff("three"),
new Stuff("four")
};
var sorted = list.OrderBy(x => x);
foreach (var stuff in sorted)
{
Console.Out.WriteLine(stuff.Name);
}
}
}
public class Stuff : IComparable<Stuff>
{
public string Name { get; set; }
public Stuff(string name)
{
Name = name;
}
public int CompareTo(Stuff other)
{
return String.CompareOrdinal(Name, other.Name);
}
}
public static class GenericSorter
{
public static IOrderedEnumerable<T> Sort<T>(IEnumerable<T> toSort, Dictionary<string, SortingOrder> sortOptions)
{
IOrderedEnumerable<T> orderedList = null;
foreach (KeyValuePair<string, SortingOrder> entry in sortOptions)
{
if (orderedList != null)
{
if (entry.Value == SortingOrder.Ascending)
{
orderedList = orderedList.ApplyOrder<T>(entry.Key, "ThenBy");
}
else
{
orderedList = orderedList.ApplyOrder<T>(entry.Key, "ThenByDescending");
}
}
else
{
if (entry.Value == SortingOrder.Ascending)
{
orderedList = toSort.ApplyOrder<T>(entry.Key, "OrderBy");
}
else
{
orderedList = toSort.ApplyOrder<T>(entry.Key, "OrderByDescending");
}
}
}
return orderedList;
}
private static IOrderedEnumerable<T> ApplyOrder<T>(this IEnumerable<T> source, string property, string methodName)
{
ParameterExpression param = Expression.Parameter(typeof(T), "x");
Expression expr = param;
foreach (string prop in property.Split('.'))
{
expr = Expression.PropertyOrField(expr, prop);
}
Type delegateType = typeof(Func<,>).MakeGenericType(typeof(T), expr.Type);
LambdaExpression lambda = Expression.Lambda(delegateType, expr, param);
MethodInfo mi = typeof(Enumerable).GetMethods().Single(
method => method.Name == methodName
&& method.IsGenericMethodDefinition
&& method.GetGenericArguments().Length == 2
&& method.GetParameters().Length == 2)
.MakeGenericMethod(typeof(T), expr.Type);
return (IOrderedEnumerable<T>)mi.Invoke(null, new object[] { source, lambda.Compile() });
}
}

Resources