/spring-filter

Dynamically filters JPA entities and MongoDB collections with a simple query syntax. Provides JPA/Hibernate predicates, Spring Data specifications, and MongoDB queries. Easily integrates with Spring APIs.

Primary LanguageJavaMIT LicenseMIT

Spring Filter

Spring Filter Logo

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 (try it live)

/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

/* 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>1.0.2</version>
</dependency>

Usages

a. Controller

Requires javax.persistence-api, spring-data-jpa, spring-web and spring-webmvc

@GetMapping(value = "/search")
public List<Entity> search(@Filter 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 the Pageable argument if pagination is not needed.

b. Specification

Requires javax.persistence-api, spring-data-jpa, spring-web

Specification<Entity> spec = new FilterSpecification<Entity>(query);

c. Predicate

Requires javax.persistence-api, spring-data-jpa

Predicate predicate = ExpressionGenerator.run(String query, 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.FilterBuilder.*;
Filter filter = filter(like("name", "%jose%"));
String query = filter.generate(); // name ~ '%jose%'
// Predicate predicate = ExpressionGenerator.run(filter, 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')

Note that the * character can also be used instead of % when using the ~ comparator. By default, this comparator is case insensitive, the behavior can be changed with ExpressionGeneratorParameters.CASE_SENSITIVE_LIKE_OPERATOR.

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)
upper returns the uppercased string upper(name)
lower returns the lowercased string lower(name)
concat returns the concatenation of two given strings concat(firstName, concat(' ', lastName))

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 formatters inside the SpringFilterParameters class. You may see below the default patterns and how you can set them with properties:

Type Default Pattern Property Name
java.util.Date dd-MM-yyyy turkraft.springfilter.dateformatter.pattern
java.time.LocalDate dd-MM-yyyy turkraft.springfilter.localdateformatter.pattern
java.time.LocalDateTime dd-MM-yyyy'T'HH:mm:ss turkraft.springfilter.localdatetimeformatter.pattern
java.time.OffsetDateTime dd-MM-yyyy'T'HH:mm:ss.SSSXXX turkraft.springfilter.offsetdatetimeformatter.pattern
java.time.Instant dd-MM-yyyy'T'HH:mm:ss.SSSXXX Parses using DateTimeFormatter.ISO_INSTANT

MongoDB

MongoDB is also partially supported as an alternative to JPA. The query input is compiled to a Bson/Document filter. You can then use it as you wish with MongoTemplate or MongoOperations for example.

Requires spring-data-mongodb

Usage

@GetMapping(value = "/search")
public List<Entity> search(@Filter Document doc, Pageable page) {
  // your repo may implement DocumentExecutor for easy usage
  return repo.findAll(doc, page); 
}
Bson bson = BsonGenerator.run(filter);
Document doc = BsonUtils.getDocumentFromBson(bson);
Query query = BsonUtils.getQueryFromDocument(doc);
// ...

⚠️ Functions are currently not supported, and field types are limited to strings/enums, numbers, booleans, and collections

Articles

Contributing

Ideas and pull requests are always welcome. Google's Java Style is used for formatting.

Contributors

Thanks to @marcopag90 and @glodepa for adding support to MongoDB.

License

Distributed under the MIT license.