How to implement search dynamically with NativeSearchQueryBuilder()? - search

if userId is null ,I want to remove .withQuery(QueryBuilders.matchQuery("createdBy",userId)) this condition, how can I implement search dynamically according to my passing parameters.
public <T> List<T> search(String index, String type, String tenantId ,String userId, String queryString, Class<T> clzz){
log.debug("==========search==========");
String allField = "_all";
MultiMatchQueryBuilder multiMatchQueryBuilder = QueryBuilders.multiMatchQuery(queryString, allField);
SearchQuery searchQuery = new NativeSearchQueryBuilder()
.withIndices(index)
.withTypes(type)
.withQuery(multiMatchQueryBuilder)
.withQuery(QueryBuilders.matchQuery("tenantId",tenantId))
.withQuery(QueryBuilders.matchQuery("createdBy",userId))
.build();
List<T> list =
operations.queryForPage(searchQuery,clzz).getContent();
return list;
}

Right, matchQuery does not accept null value and throws:
java.lang.IllegalArgumentException: [match] requires query value
NativeSearchQueryBuilder is an object like any other so you can build it after some additional preparation. Probably not the prettiest, but will do what you want:
NativeSearchQueryBuilder nativeSearch = new NativeSearchQueryBuilder()
.withIndices(index)
.withTypes(type)
.withQuery(multiMatchQueryBuilder)
.withQuery(QueryBuilders.matchQuery("tenantId", tenantId));
if (userId != null) {
nativeSearch.withQuery(QueryBuilders.matchQuery("createdBy", userId));
}
SearchQuery searchQuery = nativeSearch.build();

Related

I need generic way to filter IQueryable data and filters are populated as dictionary

I need generic way to filter IQueryable data and filters are populated as dictionary. I have already created method like this.
public static IEnumerable<T> CustomApplyFilter<T>(this IQueryable<T> source, Dictionary<string, string> filterBy)
{
foreach (var key in filterBy.Keys)
{
source.Where(m => m.GetType().GetProperty(key).GetValue(m, null).Equals(filterBy[key]));
}
return source.ToList();
}
But its always returning same result.
please find the caller
Dictionary<string, string> dtFilter = new Dictionary<string, string>();
dtFilter.Add("Id", "2");
var res = context.Set<MyEntity>().CustomApplyFilter<MyEntity>(dtFilter);
The Where extension method does not change the content of the IQueryable it is applied to. The return value of the method should be used:
public static IEnumerable<T> CustomApplyFilter<T>(this IQueryable<T> source, Dictionary<string, string> filterBy)
{
foreach (var key in filterBy.Keys)
{
source = source.Where(m => m.GetType().GetProperty(key).GetValue(m, null).Equals(filterBy[key]));
}
return source.ToList();
}
UPDATE:
I should have noticed it, my answer so far was applicable to LINQ to Objects only. When using LINQ to Entities, however, there are certain restrictions; only expression that can be converted to an SQL query can be used. Getting properties through reflection is not such an expression obviously.
When this is the case, one possible solution would be to build the ExpressionTree manually.
public static IEnumerable<T> CustomApplyFilter<T>(this IQueryable<T> source, Dictionary<string, string> filterBy)
{
foreach (var key in filterBy.Keys)
{
var paramExpr = Expression.Parameter(typeof(T), key);
var keyPropExpr = Expression.Property(paramExpr, key);
var eqExpr = Expression.Equal(keyPropExpr, Expression.Constant(filterBy[key]));
var condExpr = Expression.Lambda<Func<T, bool>>(eqExpr, paramExpr);
source = source.Where(condExpr);
}
return source.ToList();
}
UPDATE2:
With the comment #Venkatesh Kumar given below, it is apparent that when the underlying type of the field provided is not of type string, this solution fails (with the error message : The binary operator Equal is not defined for the types 'System.Int64' and 'System.String').
One possible way to tackle this problem would be to have a dictionary of types and delegates to use for each such property.
Since this is a static method (an extension method which has to be static), declaring a static Dictionary in class scope would be reasonable:
Let's assume the name of the class in which CustomApplyFilter is declared is SOFExtensions:
internal static class SOFExtensions
{
private static Dictionary<Type, Func<string, object>> lookup = new Dictionary<Type, Func<string, object>>();
static SOFExtensions()
{
lookup.Add(typeof(string), x => { return x; });
lookup.Add(typeof(long), x => { return long.Parse(x); });
lookup.Add(typeof(int), x => { return int.Parse(x); });
lookup.Add(typeof(double), x => { return double.Parse(x); });
}
public static IEnumerable<T> CustomApplyFilter<T>(this IQueryable<T> source, Dictionary<string, string> filterBy)
{
foreach (var key in filterBy.Keys)
{
var paramExpr = Expression.Parameter(typeof(T), key);
var keyPropExpr = Expression.Property(paramExpr, key);
if (!lookup.ContainsKey(keyPropExpr.Type))
throw new Exception("Unknown type : " + keyPropExpr.Type.ToString());
var typeDelegate = lookup[keyPropExpr.Type];
var constantExp = typeDelegate(filterBy[key]);
var eqExpr = Expression.Equal(keyPropExpr, Expression.Constant(constantExp));
var condExpr = Expression.Lambda<Func<T, bool>>(eqExpr, paramExpr);
source = source.Where(condExpr);
}
return source.ToList();
}
}
Other types and proper delegates for them should be added to the lookup Dictionary as required.

Conversion of a small int type field to a string not working

I have a model and method below. The field type of BudgetHealth in the database is smallint/null.
I want to convert the value retrieved to a string. Code compiles but fails during runtime with an error
that I cannot do the conversion.
What is the best way to write this?
public string ProjectId { get; set; }
public string BudgetHealth { get; set; }
public string TeamHealth { get; set; }
public Projecthealthnotes GetProgressHealthDetails(string projectId, DateTime strstatusDate)
{
Projecthealthnotes objProjHnotes = new Projecthealthnotes();
objProjHnotes = (from jj in _objContext.tbl_Project_Status_Followup
where (jj.ProjectID.Equals(projectId)) && (jj.StatusDate.Equals(strstatusDate))
select new Projecthealthnotes()
{
ProjectId = jj.ProjectID,
BudgetHealth = Convert.ToString(jj.BudgetHealth),
TeamHealth = jj.TeamHealth
}).FirstOrDefault();
return objProjHnotes;
}
You could try:
BudgetHealth = ((jj.BudgetHealth != null) ? jj.BudgetHealth.ToString() : String.Empty),
Edit: It's actually probably this line, rather than your BudgetHealth:
jj.StatusDate.Equals(strstatusDate)
Because strstatusDate is a DateTime
I guess you should use let keyword in query expression for cast int to the string value.
let : In a query expression, it is sometimes useful to store the result of a sub-expression in order to use it in subsequent clauses. You can do this with the let keyword, which creates a new range variable and initializes it with the result of the expression you supply. Once initialized with a value, the range variable cannot be used to store another value. However, if the range variable holds a queryable type, it can be queried.
I hope below code will be helpful to you.
objProjHnotes = (from jj in _objContext.tbl_Project_Status_Followup
let strBudgetHealth = Convert.ToString(jj.BudgetHealth)
where (jj.ProjectID.Equals(projectId)) && (jj.StatusDate.Equals(strstatusDate))
select new Projecthealthnotes()
{
ProjectId = jj.ProjectID,
BudgetHealth = strBudgetHealth,
TeamHealth = jj.TeamHealth
}).FirstOrDefault();

How to override the CRUD/list() function? Play! framework

I'm trying to override the list() function of the CRUD module for one of my models.
I found this on google groups which is essentially the issue i'm having.
Basically I want to filter the list based on certian categories, I tried this:
CONTROLLER
public static void list(string category){
List<Duty> object = Duty.getByCategory(category);
render(object);
}
MODEL
public static List<Duty> getByCategory(String category){
List<Duty> result = Duty.find("select distinct d from Duty d join " +
"d.category c where c.name = ? order by d.name", category).fetch();
return result;
}
I get the following error:
How do you overwrite the list action?
Any help will be much appreciated.
It seems that you are overriding the controller but not the template. The signature of the CRUD list method is this one, slightly different that yours:
public static void list(int page, String search, String searchFields, String orderBy, String order) {
ObjectType type = ObjectType.get(getControllerClass());
notFoundIfNull(type);
if (page < 1) {
page = 1;
}
List<Model> objects = type.findPage(page, search, searchFields, orderBy, order, (String) request.args.get("where"));
Long count = type.count(search, searchFields, (String) request.args.get("where"));
Long totalCount = type.count(null, null, (String) request.args.get("where"));
try {
render(type, objects, count, totalCount, page, orderBy, order);
} catch (TemplateNotFoundException e) {
render("CRUD/list.html", type, objects, count, totalCount, page, orderBy, order);
}
}
You will notice that render() is passing many more parameters that you do, and probably they are not optional. Try to provide values for them.
You can override the CRUD list method, and add filters passing many parameters in where for example:
public static void list(int page, String search, String searchFields, String orderBy, String order) {
ObjectType type = ObjectType.get(getControllerClass());
notFoundIfNull(type);
if (page < 1) {
page = 1;
}
String where = "nameAttribute =" + value;
List<Model> objects = type.findPage(page, search, searchFields, orderBy, order, where);
Long count = type.count(search, searchFields, where);
Long totalCount = type.count(null, null, where);
try {
render(type, objects, count, totalCount, page, orderBy, order);
} catch (TemplateNotFoundException e) {
render("CRUD/list.html", type, objects, count, totalCount, page, orderBy, order);
}
}
Try to call that override method from view(xtml).
<form action="#{Controler.overrideList()}" method="POST">
And use previous code,and add filters passing many parameters in where = "..."

RequestMapping on presence of one of multiple parameters

I have a Spring3 controller in which I'm using the #RequestMapping annotation. I know I can use the params value to route based on the the presence or lack of a url parameter, but is there a way to route based on the presence of one of two parameters?
Ideally I'd have something like the following:
#RequestMapping(value="/auth", params="error OR problem")
public ModelAndView errorInAuthenticate()
Where I route to errorInAuthenticate if the parameters error OR problem exist.
Unfortunately #RequestMapping params are combined using AND, not OR. (Source)
simply map both params as not required and test them:
#RequestMapping(value="/auth")
public ModelAndView errorInAuthenticate(#RequestParam(value="error", required=false) String errorParam,
#RequestParam(value="problem", required=false) String problemParam) {
if(errorParam != null || problemParam != null) {
//redirect
}
}
You can do it using Spring AOP and create a surrounding aspect for that request mapping.
Create an annotation like the following:
public #interface RequestParameterOrValidation{
String[] value() default {};
}
Then you can annotate your request mapping method with it:
#GetMapping("/test")
#RequestParameterOrValidation(value={"a", "b"})
public void test(
#RequestParam(value = "a", required = false) String a,
#RequestParam(value = "b", required = false) String b) {
// API code goes here...
}
Create an aspect around the annotation. Something like:
#Aspect
#Component
public class RequestParameterOrValidationAspect {
#Around("#annotation(x.y.z.RequestParameterOrValidation) && execution(public * *(..))")
public Object time(final ProceedingJoinPoint joinPoint) throws Throwable {
Object[] args= joinPoint.getArgs();
MethodSignature methodSignature = (MethodSignature) thisJoinPoint.getStaticPart().getSignature();
Method method = methodSignature.getMethod();
Annotation[][] parameterAnnotations = method.getParameterAnnotations();
RequestParameterOrValidation requestParamsOrValidation= method.getAnnotation(RequestParameterOrValidation.class);
String[] params=requestParamsOrValidation.value();
boolean isValid=false;
for (int argIndex = 0; argIndex < args.length; argIndex++) {
for (Annotation annotation : parameterAnnotations[argIndex]) {
if (!(annotation instanceof RequestParam))
continue;
RequestParam requestParam = (RequestParam) annotation;
if (Arrays.stream(params).anyMatch(requestParam.value()::equals) && args[argIndex]!=null) {
// Atleast one request param exist so its a valid value
return joinPoint.proceed();
}
}
}
throw new IllegalArgumentException("illegal request");
}
}
Note:- that it would be a good option to return 400 BAD REQUEST here since the request was not valid. Depends on the context, of course, but this is a general rule of thumb to start with.

Automapper uri to string convention

Using Automapper, what is the best way to setup a global convention such that all System.Uri properties are converted to a string that represents the AbsoluteUri property?
Ideally, I'd like to have a null System.Uri resolve to a value of String.Empty rather than null.
Setup the map:
Mapper.CreateMap<System.Uri, string>().ConvertUsing<UriToStringConverter>();
Create the TypeConverter class:
public class UriToStringConverter : ITypeConverter<System.Uri, string>
{
public string Convert(ResolutionContext context)
{
if (context.SourceValue == null)
{
return String.Empty;
}
return ((System.Uri)context.SourceValue).AbsoluteUri;
}
}

Resources