Skip to content

Commit d0dcf49

Browse files
author
Eugen
committed
Merge pull request eugenp#192 from Doha2012/master
add rsql parser test
2 parents bb8f566 + f20361a commit d0dcf49

File tree

7 files changed

+340
-1
lines changed

7 files changed

+340
-1
lines changed

spring-security-rest-full/pom.xml

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -107,7 +107,15 @@
107107
<artifactId>querydsl-jpa</artifactId>
108108
<version>3.6.2</version>
109109
</dependency>
110-
110+
111+
<!-- Rsql -->
112+
113+
<dependency>
114+
<groupId>cz.jirutka.rsql</groupId>
115+
<artifactId>rsql-parser</artifactId>
116+
<version>2.0.0</version>
117+
</dependency>
118+
111119
<!-- web -->
112120

113121
<dependency>
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
package org.baeldung.persistence.dao.rsql;
2+
3+
import org.baeldung.persistence.model.User;
4+
import org.springframework.data.jpa.domain.Specification;
5+
6+
import cz.jirutka.rsql.parser.ast.AndNode;
7+
import cz.jirutka.rsql.parser.ast.ComparisonNode;
8+
import cz.jirutka.rsql.parser.ast.OrNode;
9+
import cz.jirutka.rsql.parser.ast.RSQLVisitor;
10+
11+
public class CustomRsqlVisitor<T> implements RSQLVisitor<Specification<User>, Void> {
12+
13+
private UserRsqlSpecBuilder builder;
14+
15+
public CustomRsqlVisitor() {
16+
builder = new UserRsqlSpecBuilder();
17+
}
18+
19+
@Override
20+
public Specification<User> visit(final AndNode node, final Void param) {
21+
return builder.createSpecification(node);
22+
}
23+
24+
@Override
25+
public Specification<User> visit(final OrNode node, final Void param) {
26+
return builder.createSpecification(node);
27+
}
28+
29+
@Override
30+
public Specification<User> visit(final ComparisonNode node, final Void params) {
31+
return builder.createSpecification(node);
32+
}
33+
34+
}
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
package org.baeldung.persistence.dao.rsql;
2+
3+
import cz.jirutka.rsql.parser.ast.ComparisonOperator;
4+
import cz.jirutka.rsql.parser.ast.RSQLOperators;
5+
6+
public enum RsqlSearchOperation {
7+
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(
8+
RSQLOperators.IN), NOT_IN(RSQLOperators.NOT_IN);
9+
10+
private ComparisonOperator operator;
11+
12+
private RsqlSearchOperation(final ComparisonOperator operator) {
13+
this.operator = operator;
14+
}
15+
16+
public static RsqlSearchOperation getSimpleOperator(final ComparisonOperator operator) {
17+
for (final RsqlSearchOperation operation : values()) {
18+
if (operation.getOperator() == operator) {
19+
return operation;
20+
}
21+
}
22+
return null;
23+
}
24+
25+
public ComparisonOperator getOperator() {
26+
return operator;
27+
}
28+
}
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
package org.baeldung.persistence.dao.rsql;
2+
3+
import java.util.ArrayList;
4+
import java.util.List;
5+
6+
import org.baeldung.persistence.model.User;
7+
import org.springframework.data.jpa.domain.Specifications;
8+
9+
import cz.jirutka.rsql.parser.ast.ComparisonNode;
10+
import cz.jirutka.rsql.parser.ast.LogicalNode;
11+
import cz.jirutka.rsql.parser.ast.LogicalOperator;
12+
import cz.jirutka.rsql.parser.ast.Node;
13+
14+
public class UserRsqlSpecBuilder {
15+
16+
public Specifications<User> createSpecification(final Node node) {
17+
if (node instanceof LogicalNode) {
18+
return createSpecification((LogicalNode) node);
19+
}
20+
if (node instanceof ComparisonNode) {
21+
return createSpecification((ComparisonNode) node);
22+
}
23+
return null;
24+
}
25+
26+
public Specifications<User> createSpecification(final LogicalNode logicalNode) {
27+
final List<Specifications<User>> specs = new ArrayList<Specifications<User>>();
28+
Specifications<User> temp;
29+
for (final Node node : logicalNode.getChildren()) {
30+
temp = createSpecification(node);
31+
if (temp != null) {
32+
specs.add(temp);
33+
}
34+
}
35+
36+
Specifications<User> result = specs.get(0);
37+
38+
if (logicalNode.getOperator() == LogicalOperator.AND) {
39+
for (int i = 1; i < specs.size(); i++) {
40+
result = Specifications.where(result).and(specs.get(i));
41+
}
42+
}
43+
44+
else if (logicalNode.getOperator() == LogicalOperator.OR) {
45+
for (int i = 1; i < specs.size(); i++) {
46+
result = Specifications.where(result).or(specs.get(i));
47+
}
48+
}
49+
50+
return result;
51+
}
52+
53+
public Specifications<User> createSpecification(final ComparisonNode comparisonNode) {
54+
final Specifications<User> result = Specifications.where(new UserRsqlSpecification(comparisonNode.getSelector(), comparisonNode.getOperator(), comparisonNode.getArguments()));
55+
return result;
56+
}
57+
58+
}
Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
package org.baeldung.persistence.dao.rsql;
2+
3+
import java.util.ArrayList;
4+
import java.util.List;
5+
6+
import javax.persistence.criteria.CriteriaBuilder;
7+
import javax.persistence.criteria.CriteriaQuery;
8+
import javax.persistence.criteria.Predicate;
9+
import javax.persistence.criteria.Root;
10+
11+
import org.baeldung.persistence.model.User;
12+
import org.springframework.data.jpa.domain.Specification;
13+
14+
import cz.jirutka.rsql.parser.ast.ComparisonOperator;
15+
16+
public class UserRsqlSpecification implements Specification<User> {
17+
18+
private String property;
19+
private ComparisonOperator operator;
20+
private List<String> arguments;
21+
22+
public UserRsqlSpecification(final String property, final ComparisonOperator operator, final List<String> arguments) {
23+
super();
24+
this.property = property;
25+
this.operator = operator;
26+
this.arguments = arguments;
27+
}
28+
29+
30+
@Override
31+
public Predicate toPredicate(final Root<User> root, final CriteriaQuery<?> query, final CriteriaBuilder builder) {
32+
final List<Object> args = castArguments(root);
33+
final Object argument = args.get(0);
34+
switch (RsqlSearchOperation.getSimpleOperator(operator)) {
35+
36+
case EQUAL: {
37+
if (argument instanceof String) {
38+
return builder.like(root.<String> get(property), argument.toString().replace('*', '%'));
39+
} else if (argument == null) {
40+
return builder.isNull(root.get(property));
41+
} else {
42+
return builder.equal(root.get(property), argument);
43+
}
44+
}
45+
case NOT_EQUAL: {
46+
if (argument instanceof String) {
47+
return builder.notLike(root.<String> get(property), argument.toString().replace('*', '%'));
48+
} else if (argument == null) {
49+
return builder.isNotNull(root.get(property));
50+
} else {
51+
return builder.notEqual(root.get(property), argument);
52+
}
53+
}
54+
case GREATER_THAN: {
55+
return builder.greaterThan(root.<String> get(property), argument.toString());
56+
}
57+
case GREATER_THAN_OR_EQUAL: {
58+
return builder.greaterThanOrEqualTo(root.<String> get(property), argument.toString());
59+
}
60+
case LESS_THAN: {
61+
return builder.lessThan(root.<String> get(property), argument.toString());
62+
}
63+
case LESS_THAN_OR_EQUAL: {
64+
return builder.lessThanOrEqualTo(root.<String> get(property), argument.toString());
65+
}
66+
case IN:
67+
return root.get(property).in(args);
68+
case NOT_IN:
69+
return builder.not(root.get(property).in(args));
70+
}
71+
72+
return null;
73+
}
74+
75+
// === private
76+
77+
private List<Object> castArguments(final Root<User> root) {
78+
final List<Object> args = new ArrayList<Object>();
79+
final Class<? extends Object> type = root.get(property).getJavaType();
80+
81+
for (final String argument : arguments) {
82+
if (type.equals(Integer.class)) {
83+
args.add(Integer.parseInt(argument));
84+
} else if (type.equals(Long.class)) {
85+
args.add(Long.parseLong(argument));
86+
} else {
87+
args.add(argument);
88+
}
89+
}
90+
91+
return args;
92+
}
93+
94+
}

spring-security-rest-full/src/main/java/org/baeldung/web/controller/UserController.java

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
import org.baeldung.persistence.dao.MyUserRepository;
1111
import org.baeldung.persistence.dao.UserRepository;
1212
import org.baeldung.persistence.dao.UserSpecificationsBuilder;
13+
import org.baeldung.persistence.dao.rsql.CustomRsqlVisitor;
1314
import org.baeldung.persistence.model.MyUser;
1415
import org.baeldung.persistence.model.User;
1516
import org.baeldung.web.util.SearchCriteria;
@@ -29,6 +30,9 @@
2930
import com.google.common.base.Preconditions;
3031
import com.mysema.query.types.expr.BooleanExpression;
3132

33+
import cz.jirutka.rsql.parser.RSQLParser;
34+
import cz.jirutka.rsql.parser.ast.Node;
35+
3236
@Controller
3337
public class UserController {
3438

@@ -91,6 +95,14 @@ public Iterable<MyUser> findAllByQuerydsl(@RequestParam(value = "search") final
9195
return mydao.findAll(exp);
9296
}
9397

98+
@RequestMapping(method = RequestMethod.GET, value = "/users/rsql")
99+
@ResponseBody
100+
public List<User> findAllByRsql(@RequestParam(value = "search") final String search) {
101+
final Node rootNode = new RSQLParser().parse(search);
102+
final Specification<User> spec = rootNode.accept(new CustomRsqlVisitor<User>());
103+
return dao.findAll(spec);
104+
}
105+
94106
// API - WRITE
95107

96108
@RequestMapping(method = RequestMethod.POST, value = "/users")
Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
package org.baeldung.persistence.query;
2+
3+
import static org.hamcrest.MatcherAssert.assertThat;
4+
import static org.hamcrest.collection.IsIn.isIn;
5+
import static org.hamcrest.core.IsNot.not;
6+
7+
import java.util.List;
8+
9+
import org.baeldung.persistence.dao.UserRepository;
10+
import org.baeldung.persistence.dao.rsql.CustomRsqlVisitor;
11+
import org.baeldung.persistence.model.User;
12+
import org.baeldung.spring.PersistenceConfig;
13+
import org.junit.Before;
14+
import org.junit.Test;
15+
import org.junit.runner.RunWith;
16+
import org.springframework.beans.factory.annotation.Autowired;
17+
import org.springframework.data.jpa.domain.Specification;
18+
import org.springframework.test.context.ContextConfiguration;
19+
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
20+
import org.springframework.test.context.transaction.TransactionConfiguration;
21+
import org.springframework.transaction.annotation.Transactional;
22+
23+
import cz.jirutka.rsql.parser.RSQLParser;
24+
import cz.jirutka.rsql.parser.ast.Node;
25+
26+
@RunWith(SpringJUnit4ClassRunner.class)
27+
@ContextConfiguration(classes = { PersistenceConfig.class })
28+
@Transactional
29+
@TransactionConfiguration
30+
public class RsqlTest {
31+
32+
@Autowired
33+
private UserRepository repository;
34+
35+
private User userJohn;
36+
37+
private User userTom;
38+
39+
@Before
40+
public void init() {
41+
userJohn = new User();
42+
userJohn.setFirstName("john");
43+
userJohn.setLastName("doe");
44+
userJohn.setEmail("[email protected]");
45+
userJohn.setAge(22);
46+
repository.save(userJohn);
47+
48+
userTom = new User();
49+
userTom.setFirstName("tom");
50+
userTom.setLastName("doe");
51+
userTom.setEmail("[email protected]");
52+
userTom.setAge(26);
53+
repository.save(userTom);
54+
}
55+
56+
@Test
57+
public void givenFirstAndLastName_whenGettingListOfUsers_thenCorrect() {
58+
final Node rootNode = new RSQLParser().parse("firstName==john;lastName==doe");
59+
final Specification<User> spec = rootNode.accept(new CustomRsqlVisitor<User>());
60+
final List<User> results = repository.findAll(spec);
61+
62+
assertThat(userJohn, isIn(results));
63+
assertThat(userTom, not(isIn(results)));
64+
}
65+
66+
@Test
67+
public void givenFirstNameInverse_whenGettingListOfUsers_thenCorrect() {
68+
final Node rootNode = new RSQLParser().parse("firstName!=john");
69+
final Specification<User> spec = rootNode.accept(new CustomRsqlVisitor<User>());
70+
final List<User> results = repository.findAll(spec);
71+
72+
assertThat(userTom, isIn(results));
73+
assertThat(userJohn, not(isIn(results)));
74+
}
75+
76+
@Test
77+
public void givenMinAge_whenGettingListOfUsers_thenCorrect() {
78+
final Node rootNode = new RSQLParser().parse("age>25");
79+
final Specification<User> spec = rootNode.accept(new CustomRsqlVisitor<User>());
80+
final List<User> results = repository.findAll(spec);
81+
82+
assertThat(userTom, isIn(results));
83+
assertThat(userJohn, not(isIn(results)));
84+
}
85+
86+
@Test
87+
public void givenFirstNamePrefix_whenGettingListOfUsers_thenCorrect() {
88+
final Node rootNode = new RSQLParser().parse("firstName==jo*");
89+
final Specification<User> spec = rootNode.accept(new CustomRsqlVisitor<User>());
90+
final List<User> results = repository.findAll(spec);
91+
92+
assertThat(userJohn, isIn(results));
93+
assertThat(userTom, not(isIn(results)));
94+
}
95+
96+
@Test
97+
public void givenListOfFirstName_whenGettingListOfUsers_thenCorrect() {
98+
final Node rootNode = new RSQLParser().parse("firstName=in=(john,jack)");
99+
final Specification<User> spec = rootNode.accept(new CustomRsqlVisitor<User>());
100+
final List<User> results = repository.findAll(spec);
101+
102+
assertThat(userJohn, isIn(results));
103+
assertThat(userTom, not(isIn(results)));
104+
}
105+
}

0 commit comments

Comments
 (0)