Spring Filter
You need a way to dynamically filter entities without any effort? Just add me to your pom.xml
. Your API will gain a full featured search functionality. You don't work with APIs? No problem, you may still not want to mess with SQL, JPA predicates, security, and all of that I guess. From a technical point of view, I compile a simple syntax to JPA predicates.
Example
/search?filter= average(ratings) > 4.5 and brand.name in ('audi', 'land rover') and (year > 2018 or km < 50000) and color : 'white' and accidents is empty
You may also apply sorting using
sort
, for example:&sort=name,-brand.id
(where-
means descending)
/* Entity used in the query above */
@Entity public class Car {
@Id long id;
int year;
int km;
@Enumerated Color color;
@ManyToOne Brand brand;
@OneToMany List<Accident> accidents;
@ElementCollection List<Integer> ratings;
// ...
}
🚀 Yes we support booleans, dates, enums, functions, and even relations! Need something else? Tell us here.
Installation
<dependency>
<groupId>com.turkraft</groupId>
<artifactId>spring-filter</artifactId>
<version>0.9.3</version>
</dependency>
Usages
a. Controller
Requires javax.persistence-api, spring-data-jpa, spring-web and spring-webmvc
@GetMapping(value = "/search")
public List<Entity> search(@EntityFilter Specification<Entity> spec, Pageable page) {
return repo.findAll(spec, page);
}
The repository should implement
JpaSpecificationExecutor
in order to execute Spring's Specification,SimpleJpaRepository
is a well known implementation. You can remove thePageable
argument if pagination is not needed.
b. Specification
Requires javax.persistence-api, spring-data-jpa, spring-web
Specification<Entity> spec = new FilterSpecification<Entity>(input);
c. Predicate
Requires javax.persistence-api, spring-data-jpa
Predicate predicate = FilterCompiler.parse(String input, Root<?> r, CriteriaQuery<?> q, CriteriaBuilder cb);
⚠️ If you need to search over relations, you also require hibernate-core
d. Builder
/* Using static methods */
import static com.turkraft.springfilter.FilterQueryBuilder.*;
Filter filter = filter(like("name", "%jose%"));
/* Using lombok builder */
Filter filter = Filter.builder()
.body(ConditionInfix.builder()
.left(Field.builder()
.name("name")
.build())
.comparator(Comparator.LIKE)
.right(Input.builder()
.value(Text.builder()
.value("%jose%")
.build())
.build())
.build())
.build();
String input = filter.generate(); // name ~ '%jose%'
Predicate predicate = filter.generate(Root<?> r, CriteriaQuery<?> cq, CriteriaBuilder cb);
Specification<Entity> spec = new FilterSpecification<Entity>(filter);
Syntax
Fields
Field names should be directly given without any extra literals. Dots indicate nested fields. For example: category.updatedAt
Inputs
Numbers should be directly given. Booleans should also directly be given, valid values are true
and false
(case insensitive). Others such as strings, enums, dates, should be quoted. For example: status : 'active'
Operators
Literal (case insensitive) | Description | Example |
---|---|---|
and | and's two expressions | status : 'active' and createdAt > '1-1-2000' |
or | or's two expressions | value ~ 'hello' or name ~ 'world' |
not | not's an expression | not (id > 100 or category.order is null) |
You may prioritize operators using parentheses, for example:
x and (y or z)
Comparators
Literal (case insensitive) | Description | Example |
---|---|---|
~ | checks if the left (string) expression is similar to the right (string) expression | catalog.name ~ 'electronic%' |
: | checks if the left expression is equal to the right expression | id : 5 |
! | checks if the left expression is not equal to the right expression | username ! 'torshid' |
> | checks if the left expression is greater than the right expression | distance > 100 |
>: | checks if the left expression is greater or equal to the right expression | distance >: 100 |
< | checks if the left expression is smaller than the right expression | distance < 100 |
<: | checks if the left expression is smaller or equal to the right expression | distance <: 100 |
is null | checks if an expression is null | status is null |
is not null | checks if an expression is not null | status is not null |
is empty | checks if the (collection) expression is empty | children is empty |
is not empty | checks if the (collection) expression is not empty | children is not empty |
in | checks if an expression is present in the right expressions | status in ('initialized', 'active') |
Functions
A function is characterized by its name (case insensitive) followed by parentheses. For example: currentTime()
. Some functions might also take arguments, arguments are seperated with commas. For example: min(ratings) > 3
Name | Description | Example |
---|---|---|
absolute | returns the absolute | absolute(x) |
average | returns the average | average(ratings) |
min | returns the minimum | min(ratings) |
max | returns the maximum | max(ratings) |
sum | returns the sum | sum(scores) |
currentDate | returns the current date | currentDate() |
currentTime | returns the current time | currentTime() |
currentTimestamp | returns the current time stamp | currentTimestamp() |
size | returns the collection's size | size(accidents) |
length | returns the string's length | length(name) |
trim | returns the trimmed string | trim(name) |
Configuration
You may want to customize the behavior of the different processes taking place. For now, you can only change the date format but advanced customization will be soon available in order to let you completely personalize the tokenizer, the parser, the query builder, with the possibility of adding custom functions and much more.
Date format
You are able to change the date format by setting the static DATE_FORMATTER
field of the FilterConfig
class. You can also set it with the property turkraft.springfilter.dateformatter.pattern
Notes
When using @EntityFilter
, sorting is automatically done. The logic behind that is to support sorting nested fields, and to avoid duplicated joins if sorting was to be done externally. If you still want to manually sort or disable sorting completely, you may do it with @EntityFilter(sortParameterName = "")
Contributing
Ideas and pull requests are always welcome.
License
Distributed under the MIT license.