commit
d0dcf49b73
|
@ -107,7 +107,15 @@
|
||||||
<artifactId>querydsl-jpa</artifactId>
|
<artifactId>querydsl-jpa</artifactId>
|
||||||
<version>3.6.2</version>
|
<version>3.6.2</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
|
<!-- Rsql -->
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>cz.jirutka.rsql</groupId>
|
||||||
|
<artifactId>rsql-parser</artifactId>
|
||||||
|
<version>2.0.0</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
<!-- web -->
|
<!-- web -->
|
||||||
|
|
||||||
<dependency>
|
<dependency>
|
||||||
|
|
|
@ -0,0 +1,34 @@
|
||||||
|
package org.baeldung.persistence.dao.rsql;
|
||||||
|
|
||||||
|
import org.baeldung.persistence.model.User;
|
||||||
|
import org.springframework.data.jpa.domain.Specification;
|
||||||
|
|
||||||
|
import cz.jirutka.rsql.parser.ast.AndNode;
|
||||||
|
import cz.jirutka.rsql.parser.ast.ComparisonNode;
|
||||||
|
import cz.jirutka.rsql.parser.ast.OrNode;
|
||||||
|
import cz.jirutka.rsql.parser.ast.RSQLVisitor;
|
||||||
|
|
||||||
|
public class CustomRsqlVisitor<T> implements RSQLVisitor<Specification<User>, Void> {
|
||||||
|
|
||||||
|
private UserRsqlSpecBuilder builder;
|
||||||
|
|
||||||
|
public CustomRsqlVisitor() {
|
||||||
|
builder = new UserRsqlSpecBuilder();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Specification<User> visit(final AndNode node, final Void param) {
|
||||||
|
return builder.createSpecification(node);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Specification<User> visit(final OrNode node, final Void param) {
|
||||||
|
return builder.createSpecification(node);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Specification<User> visit(final ComparisonNode node, final Void params) {
|
||||||
|
return builder.createSpecification(node);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,28 @@
|
||||||
|
package org.baeldung.persistence.dao.rsql;
|
||||||
|
|
||||||
|
import cz.jirutka.rsql.parser.ast.ComparisonOperator;
|
||||||
|
import cz.jirutka.rsql.parser.ast.RSQLOperators;
|
||||||
|
|
||||||
|
public enum RsqlSearchOperation {
|
||||||
|
EQUAL(RSQLOperators.EQUAL), NOT_EQUAL(RSQLOperators.NOT_EQUAL), GREATER_THAN(RSQLOperators.GREATER_THAN), GREATER_THAN_OR_EQUAL(RSQLOperators.GREATER_THAN_OR_EQUAL), LESS_THAN(RSQLOperators.LESS_THAN), LESS_THAN_OR_EQUAL(RSQLOperators.LESS_THAN_OR_EQUAL), IN(
|
||||||
|
RSQLOperators.IN), NOT_IN(RSQLOperators.NOT_IN);
|
||||||
|
|
||||||
|
private ComparisonOperator operator;
|
||||||
|
|
||||||
|
private RsqlSearchOperation(final ComparisonOperator operator) {
|
||||||
|
this.operator = operator;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static RsqlSearchOperation getSimpleOperator(final ComparisonOperator operator) {
|
||||||
|
for (final RsqlSearchOperation operation : values()) {
|
||||||
|
if (operation.getOperator() == operator) {
|
||||||
|
return operation;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ComparisonOperator getOperator() {
|
||||||
|
return operator;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,58 @@
|
||||||
|
package org.baeldung.persistence.dao.rsql;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import org.baeldung.persistence.model.User;
|
||||||
|
import org.springframework.data.jpa.domain.Specifications;
|
||||||
|
|
||||||
|
import cz.jirutka.rsql.parser.ast.ComparisonNode;
|
||||||
|
import cz.jirutka.rsql.parser.ast.LogicalNode;
|
||||||
|
import cz.jirutka.rsql.parser.ast.LogicalOperator;
|
||||||
|
import cz.jirutka.rsql.parser.ast.Node;
|
||||||
|
|
||||||
|
public class UserRsqlSpecBuilder {
|
||||||
|
|
||||||
|
public Specifications<User> createSpecification(final Node node) {
|
||||||
|
if (node instanceof LogicalNode) {
|
||||||
|
return createSpecification((LogicalNode) node);
|
||||||
|
}
|
||||||
|
if (node instanceof ComparisonNode) {
|
||||||
|
return createSpecification((ComparisonNode) node);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Specifications<User> createSpecification(final LogicalNode logicalNode) {
|
||||||
|
final List<Specifications<User>> specs = new ArrayList<Specifications<User>>();
|
||||||
|
Specifications<User> temp;
|
||||||
|
for (final Node node : logicalNode.getChildren()) {
|
||||||
|
temp = createSpecification(node);
|
||||||
|
if (temp != null) {
|
||||||
|
specs.add(temp);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Specifications<User> result = specs.get(0);
|
||||||
|
|
||||||
|
if (logicalNode.getOperator() == LogicalOperator.AND) {
|
||||||
|
for (int i = 1; i < specs.size(); i++) {
|
||||||
|
result = Specifications.where(result).and(specs.get(i));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
else if (logicalNode.getOperator() == LogicalOperator.OR) {
|
||||||
|
for (int i = 1; i < specs.size(); i++) {
|
||||||
|
result = Specifications.where(result).or(specs.get(i));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Specifications<User> createSpecification(final ComparisonNode comparisonNode) {
|
||||||
|
final Specifications<User> result = Specifications.where(new UserRsqlSpecification(comparisonNode.getSelector(), comparisonNode.getOperator(), comparisonNode.getArguments()));
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,94 @@
|
||||||
|
package org.baeldung.persistence.dao.rsql;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import javax.persistence.criteria.CriteriaBuilder;
|
||||||
|
import javax.persistence.criteria.CriteriaQuery;
|
||||||
|
import javax.persistence.criteria.Predicate;
|
||||||
|
import javax.persistence.criteria.Root;
|
||||||
|
|
||||||
|
import org.baeldung.persistence.model.User;
|
||||||
|
import org.springframework.data.jpa.domain.Specification;
|
||||||
|
|
||||||
|
import cz.jirutka.rsql.parser.ast.ComparisonOperator;
|
||||||
|
|
||||||
|
public class UserRsqlSpecification implements Specification<User> {
|
||||||
|
|
||||||
|
private String property;
|
||||||
|
private ComparisonOperator operator;
|
||||||
|
private List<String> arguments;
|
||||||
|
|
||||||
|
public UserRsqlSpecification(final String property, final ComparisonOperator operator, final List<String> arguments) {
|
||||||
|
super();
|
||||||
|
this.property = property;
|
||||||
|
this.operator = operator;
|
||||||
|
this.arguments = arguments;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Predicate toPredicate(final Root<User> root, final CriteriaQuery<?> query, final CriteriaBuilder builder) {
|
||||||
|
final List<Object> args = castArguments(root);
|
||||||
|
final Object argument = args.get(0);
|
||||||
|
switch (RsqlSearchOperation.getSimpleOperator(operator)) {
|
||||||
|
|
||||||
|
case EQUAL: {
|
||||||
|
if (argument instanceof String) {
|
||||||
|
return builder.like(root.<String> get(property), argument.toString().replace('*', '%'));
|
||||||
|
} else if (argument == null) {
|
||||||
|
return builder.isNull(root.get(property));
|
||||||
|
} else {
|
||||||
|
return builder.equal(root.get(property), argument);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case NOT_EQUAL: {
|
||||||
|
if (argument instanceof String) {
|
||||||
|
return builder.notLike(root.<String> get(property), argument.toString().replace('*', '%'));
|
||||||
|
} else if (argument == null) {
|
||||||
|
return builder.isNotNull(root.get(property));
|
||||||
|
} else {
|
||||||
|
return builder.notEqual(root.get(property), argument);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case GREATER_THAN: {
|
||||||
|
return builder.greaterThan(root.<String> get(property), argument.toString());
|
||||||
|
}
|
||||||
|
case GREATER_THAN_OR_EQUAL: {
|
||||||
|
return builder.greaterThanOrEqualTo(root.<String> get(property), argument.toString());
|
||||||
|
}
|
||||||
|
case LESS_THAN: {
|
||||||
|
return builder.lessThan(root.<String> get(property), argument.toString());
|
||||||
|
}
|
||||||
|
case LESS_THAN_OR_EQUAL: {
|
||||||
|
return builder.lessThanOrEqualTo(root.<String> get(property), argument.toString());
|
||||||
|
}
|
||||||
|
case IN:
|
||||||
|
return root.get(property).in(args);
|
||||||
|
case NOT_IN:
|
||||||
|
return builder.not(root.get(property).in(args));
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// === private
|
||||||
|
|
||||||
|
private List<Object> castArguments(final Root<User> root) {
|
||||||
|
final List<Object> args = new ArrayList<Object>();
|
||||||
|
final Class<? extends Object> type = root.get(property).getJavaType();
|
||||||
|
|
||||||
|
for (final String argument : arguments) {
|
||||||
|
if (type.equals(Integer.class)) {
|
||||||
|
args.add(Integer.parseInt(argument));
|
||||||
|
} else if (type.equals(Long.class)) {
|
||||||
|
args.add(Long.parseLong(argument));
|
||||||
|
} else {
|
||||||
|
args.add(argument);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return args;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -10,6 +10,7 @@ import org.baeldung.persistence.dao.MyUserPredicatesBuilder;
|
||||||
import org.baeldung.persistence.dao.MyUserRepository;
|
import org.baeldung.persistence.dao.MyUserRepository;
|
||||||
import org.baeldung.persistence.dao.UserRepository;
|
import org.baeldung.persistence.dao.UserRepository;
|
||||||
import org.baeldung.persistence.dao.UserSpecificationsBuilder;
|
import org.baeldung.persistence.dao.UserSpecificationsBuilder;
|
||||||
|
import org.baeldung.persistence.dao.rsql.CustomRsqlVisitor;
|
||||||
import org.baeldung.persistence.model.MyUser;
|
import org.baeldung.persistence.model.MyUser;
|
||||||
import org.baeldung.persistence.model.User;
|
import org.baeldung.persistence.model.User;
|
||||||
import org.baeldung.web.util.SearchCriteria;
|
import org.baeldung.web.util.SearchCriteria;
|
||||||
|
@ -29,6 +30,9 @@ import com.google.common.base.Joiner;
|
||||||
import com.google.common.base.Preconditions;
|
import com.google.common.base.Preconditions;
|
||||||
import com.mysema.query.types.expr.BooleanExpression;
|
import com.mysema.query.types.expr.BooleanExpression;
|
||||||
|
|
||||||
|
import cz.jirutka.rsql.parser.RSQLParser;
|
||||||
|
import cz.jirutka.rsql.parser.ast.Node;
|
||||||
|
|
||||||
@Controller
|
@Controller
|
||||||
public class UserController {
|
public class UserController {
|
||||||
|
|
||||||
|
@ -91,6 +95,14 @@ public class UserController {
|
||||||
return mydao.findAll(exp);
|
return mydao.findAll(exp);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@RequestMapping(method = RequestMethod.GET, value = "/users/rsql")
|
||||||
|
@ResponseBody
|
||||||
|
public List<User> findAllByRsql(@RequestParam(value = "search") final String search) {
|
||||||
|
final Node rootNode = new RSQLParser().parse(search);
|
||||||
|
final Specification<User> spec = rootNode.accept(new CustomRsqlVisitor<User>());
|
||||||
|
return dao.findAll(spec);
|
||||||
|
}
|
||||||
|
|
||||||
// API - WRITE
|
// API - WRITE
|
||||||
|
|
||||||
@RequestMapping(method = RequestMethod.POST, value = "/users")
|
@RequestMapping(method = RequestMethod.POST, value = "/users")
|
||||||
|
|
|
@ -0,0 +1,105 @@
|
||||||
|
package org.baeldung.persistence.query;
|
||||||
|
|
||||||
|
import static org.hamcrest.MatcherAssert.assertThat;
|
||||||
|
import static org.hamcrest.collection.IsIn.isIn;
|
||||||
|
import static org.hamcrest.core.IsNot.not;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import org.baeldung.persistence.dao.UserRepository;
|
||||||
|
import org.baeldung.persistence.dao.rsql.CustomRsqlVisitor;
|
||||||
|
import org.baeldung.persistence.model.User;
|
||||||
|
import org.baeldung.spring.PersistenceConfig;
|
||||||
|
import org.junit.Before;
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.junit.runner.RunWith;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.data.jpa.domain.Specification;
|
||||||
|
import org.springframework.test.context.ContextConfiguration;
|
||||||
|
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
|
||||||
|
import org.springframework.test.context.transaction.TransactionConfiguration;
|
||||||
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
|
|
||||||
|
import cz.jirutka.rsql.parser.RSQLParser;
|
||||||
|
import cz.jirutka.rsql.parser.ast.Node;
|
||||||
|
|
||||||
|
@RunWith(SpringJUnit4ClassRunner.class)
|
||||||
|
@ContextConfiguration(classes = { PersistenceConfig.class })
|
||||||
|
@Transactional
|
||||||
|
@TransactionConfiguration
|
||||||
|
public class RsqlTest {
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private UserRepository repository;
|
||||||
|
|
||||||
|
private User userJohn;
|
||||||
|
|
||||||
|
private User userTom;
|
||||||
|
|
||||||
|
@Before
|
||||||
|
public void init() {
|
||||||
|
userJohn = new User();
|
||||||
|
userJohn.setFirstName("john");
|
||||||
|
userJohn.setLastName("doe");
|
||||||
|
userJohn.setEmail("john@doe.com");
|
||||||
|
userJohn.setAge(22);
|
||||||
|
repository.save(userJohn);
|
||||||
|
|
||||||
|
userTom = new User();
|
||||||
|
userTom.setFirstName("tom");
|
||||||
|
userTom.setLastName("doe");
|
||||||
|
userTom.setEmail("tom@doe.com");
|
||||||
|
userTom.setAge(26);
|
||||||
|
repository.save(userTom);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void givenFirstAndLastName_whenGettingListOfUsers_thenCorrect() {
|
||||||
|
final Node rootNode = new RSQLParser().parse("firstName==john;lastName==doe");
|
||||||
|
final Specification<User> spec = rootNode.accept(new CustomRsqlVisitor<User>());
|
||||||
|
final List<User> results = repository.findAll(spec);
|
||||||
|
|
||||||
|
assertThat(userJohn, isIn(results));
|
||||||
|
assertThat(userTom, not(isIn(results)));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void givenFirstNameInverse_whenGettingListOfUsers_thenCorrect() {
|
||||||
|
final Node rootNode = new RSQLParser().parse("firstName!=john");
|
||||||
|
final Specification<User> spec = rootNode.accept(new CustomRsqlVisitor<User>());
|
||||||
|
final List<User> results = repository.findAll(spec);
|
||||||
|
|
||||||
|
assertThat(userTom, isIn(results));
|
||||||
|
assertThat(userJohn, not(isIn(results)));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void givenMinAge_whenGettingListOfUsers_thenCorrect() {
|
||||||
|
final Node rootNode = new RSQLParser().parse("age>25");
|
||||||
|
final Specification<User> spec = rootNode.accept(new CustomRsqlVisitor<User>());
|
||||||
|
final List<User> results = repository.findAll(spec);
|
||||||
|
|
||||||
|
assertThat(userTom, isIn(results));
|
||||||
|
assertThat(userJohn, not(isIn(results)));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void givenFirstNamePrefix_whenGettingListOfUsers_thenCorrect() {
|
||||||
|
final Node rootNode = new RSQLParser().parse("firstName==jo*");
|
||||||
|
final Specification<User> spec = rootNode.accept(new CustomRsqlVisitor<User>());
|
||||||
|
final List<User> results = repository.findAll(spec);
|
||||||
|
|
||||||
|
assertThat(userJohn, isIn(results));
|
||||||
|
assertThat(userTom, not(isIn(results)));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void givenListOfFirstName_whenGettingListOfUsers_thenCorrect() {
|
||||||
|
final Node rootNode = new RSQLParser().parse("firstName=in=(john,jack)");
|
||||||
|
final Specification<User> spec = rootNode.accept(new CustomRsqlVisitor<User>());
|
||||||
|
final List<User> results = repository.findAll(spec);
|
||||||
|
|
||||||
|
assertThat(userJohn, isIn(results));
|
||||||
|
assertThat(userTom, not(isIn(results)));
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue