commit
d0dcf49b73
|
@ -108,6 +108,14 @@
|
|||
<version>3.6.2</version>
|
||||
</dependency>
|
||||
|
||||
<!-- Rsql -->
|
||||
|
||||
<dependency>
|
||||
<groupId>cz.jirutka.rsql</groupId>
|
||||
<artifactId>rsql-parser</artifactId>
|
||||
<version>2.0.0</version>
|
||||
</dependency>
|
||||
|
||||
<!-- web -->
|
||||
|
||||
<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.UserRepository;
|
||||
import org.baeldung.persistence.dao.UserSpecificationsBuilder;
|
||||
import org.baeldung.persistence.dao.rsql.CustomRsqlVisitor;
|
||||
import org.baeldung.persistence.model.MyUser;
|
||||
import org.baeldung.persistence.model.User;
|
||||
import org.baeldung.web.util.SearchCriteria;
|
||||
|
@ -29,6 +30,9 @@ import com.google.common.base.Joiner;
|
|||
import com.google.common.base.Preconditions;
|
||||
import com.mysema.query.types.expr.BooleanExpression;
|
||||
|
||||
import cz.jirutka.rsql.parser.RSQLParser;
|
||||
import cz.jirutka.rsql.parser.ast.Node;
|
||||
|
||||
@Controller
|
||||
public class UserController {
|
||||
|
||||
|
@ -91,6 +95,14 @@ public class UserController {
|
|||
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
|
||||
|
||||
@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