Skip to content

Commit b65fa99

Browse files
committed
Initial commit
0 parents  commit b65fa99

File tree

9 files changed

+723
-0
lines changed

9 files changed

+723
-0
lines changed

.gitignore

+44
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
# Compiled class file
2+
*.class
3+
4+
# Log file
5+
*.log
6+
7+
# BlueJ files
8+
*.ctxt
9+
10+
# Mobile Tools for Java (J2ME)
11+
.mtj.tmp/
12+
13+
# Package Files #
14+
*.jar
15+
*.war
16+
*.nar
17+
*.ear
18+
*.zip
19+
*.tar.gz
20+
*.rar
21+
22+
# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml
23+
hs_err_pid*
24+
25+
*/bin
26+
*/build
27+
*/target
28+
*/.classpath
29+
*/.settings
30+
*/.project
31+
.classpath
32+
.settings
33+
.project
34+
**/*.swp
35+
**/*.log
36+
**/*.bak
37+
**/.springBeans
38+
**/.mvn
39+
**/target
40+
**/.factorypath
41+
**/*.iml
42+
**/.idea
43+
44+
*/sys.log*

LICENSE

+21
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
MIT License
2+
3+
Copyright (c) 2018 perplexhub
4+
5+
Permission is hereby granted, free of charge, to any person obtaining a copy
6+
of this software and associated documentation files (the "Software"), to deal
7+
in the Software without restriction, including without limitation the rights
8+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9+
copies of the Software, and to permit persons to whom the Software is
10+
furnished to do so, subject to the following conditions:
11+
12+
The above copyright notice and this permission notice shall be included in all
13+
copies or substantial portions of the Software.
14+
15+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21+
SOFTWARE.

README.md

+60
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
# rsql-jpa-specification
2+
3+
Idea from rsql-parser, rsql-jpa and Dynamic-Specification-RSQL
4+
Visit [RSQL / FIQL parser](https://github.com/jirutka/rsql-parser)
5+
Visit [RSQL for JPA](https://github.com/tennaito/rsql-jpa)
6+
Visit [Dynamic-Specification-RSQL](https://github.com/srigalamilitan/Dynamic-Specification-RSQL)
7+
8+
## 1) Inject the EntityManager
9+
10+
### 1.1)
11+
12+
```java
13+
@ComponentScan("com.perplexhub.rsql.jpa")
14+
```
15+
16+
### 1.2)
17+
18+
```java
19+
@Bean
20+
public com.perplexhub.rsql.jpa.RsqlJpaSpecification RsqlJpaSpecification(EntityManager entityManager) {
21+
return new com.perplexhub.rsql.jpa.RsqlJpaSpecification(entityManager);
22+
}
23+
```
24+
25+
### 1.3)
26+
27+
```java
28+
new RsqlJpaSpecification(entityManager);
29+
```
30+
31+
## 2) Add JpaSpecificationExecutor to your JPA repository class
32+
33+
```java
34+
package com.perplexhub.rsql.repository;
35+
36+
import org.springframework.data.jpa.repository.JpaRepository;
37+
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
38+
39+
import com.perplexhub.rsql.model.User;
40+
41+
public interface UserRepository extends JpaRepository<User, String>, JpaSpecificationExecutor<User> {
42+
}
43+
```
44+
45+
## 3) Obtain the Specification from RsqlJpaSpecification.rsql(filter) using RSQL syntax
46+
47+
```java
48+
filter = "company.code==perplexhub"; //equal
49+
filter = "company.code==perplex*"; //like perplex%
50+
filter = "company.code==*hub"; //like %hub
51+
filter = "company.code==*plex*"; //like %plex%
52+
filter = "company.code==^*PLEX*"; //ignore case like %PLEX%
53+
repository.findAll(RsqlJpaSpecification.rsql(filter));
54+
repository.findAll(RsqlJpaSpecification.rsql(filter), pageable);
55+
```
56+
57+
Example:
58+
[RSQL / FIQL parser](https://github.com/jirutka/rsql-parser#examples)
59+
[RSQL for JPA](https://github.com/tennaito/rsql-jpa#examples-of-rsql)
60+
[Dynamic-Specification-RSQL](https://github.com/srigalamilitan/Dynamic-Specification-RSQL#implementation-rsql-in-services-layer)

pom.xml

+79
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
2+
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
3+
<modelVersion>4.0.0</modelVersion>
4+
<groupId>com.perplexhub</groupId>
5+
<artifactId>rsql-jpa-specification</artifactId>
6+
<version>1.0.0-SNAPSHOT</version>
7+
<packaging>jar</packaging>
8+
<scm>
9+
<url>https://github.com/perplexhub/rsql-jpa-specification</url>
10+
<connection>scm:git:ssh://git@github.com/perplexhub/rsql-jpa-specification.git</connection>
11+
<developerConnection>scm:git:ssh://git@github.com/perplexhub/rsql-jpa-specification.git</developerConnection>
12+
<tag>HEAD</tag>
13+
</scm>
14+
<properties>
15+
<java.version>1.8</java.version>
16+
<maven.compiler.target>1.8</maven.compiler.target>
17+
<spring.boot.version>2.0.2.RELEASE</spring.boot.version>
18+
</properties>
19+
<dependencies>
20+
<dependency>
21+
<groupId>com.github.tennaito</groupId>
22+
<artifactId>rsql-jpa</artifactId>
23+
<version>2.0.2</version>
24+
</dependency>
25+
<dependency>
26+
<groupId>cz.jirutka.rsql</groupId>
27+
<artifactId>rsql-parser</artifactId>
28+
<version>2.1.0</version>
29+
</dependency>
30+
<dependency>
31+
<groupId>org.springframework.boot</groupId>
32+
<artifactId>spring-boot-starter-data-jpa</artifactId>
33+
<version>${spring.boot.version}</version>
34+
<scope>provided</scope>
35+
</dependency>
36+
<dependency>
37+
<groupId>org.springframework.boot</groupId>
38+
<artifactId>spring-boot-configuration-processor</artifactId>
39+
<version>${spring.boot.version}</version>
40+
<optional>true</optional>
41+
</dependency>
42+
</dependencies>
43+
<build>
44+
<plugins>
45+
<plugin>
46+
<artifactId>maven-compiler-plugin</artifactId>
47+
<version>3.7.0</version>
48+
<executions>
49+
<execution>
50+
<id>default-compile</id>
51+
<phase>compile</phase>
52+
<goals>
53+
<goal>compile</goal>
54+
</goals>
55+
<configuration>
56+
<source>1.8</source>
57+
<target>1.8</target>
58+
</configuration>
59+
</execution>
60+
<execution>
61+
<id>default-testCompile</id>
62+
<phase>test-compile</phase>
63+
<goals>
64+
<goal>testCompile</goal>
65+
</goals>
66+
<configuration>
67+
<source>1.8</source>
68+
<target>1.8</target>
69+
</configuration>
70+
</execution>
71+
</executions>
72+
<configuration>
73+
<source>1.8</source>
74+
<target>1.8</target>
75+
</configuration>
76+
</plugin>
77+
</plugins>
78+
</build>
79+
</project>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,168 @@
1+
package com.perplexhub.rsql.jpa;
2+
3+
import static cz.jirutka.rsql.parser.ast.RSQLOperators.EQUAL;
4+
import static cz.jirutka.rsql.parser.ast.RSQLOperators.GREATER_THAN;
5+
import static cz.jirutka.rsql.parser.ast.RSQLOperators.GREATER_THAN_OR_EQUAL;
6+
import static cz.jirutka.rsql.parser.ast.RSQLOperators.IN;
7+
import static cz.jirutka.rsql.parser.ast.RSQLOperators.LESS_THAN;
8+
import static cz.jirutka.rsql.parser.ast.RSQLOperators.LESS_THAN_OR_EQUAL;
9+
import static cz.jirutka.rsql.parser.ast.RSQLOperators.NOT_EQUAL;
10+
11+
import java.lang.reflect.Constructor;
12+
import java.time.LocalDate;
13+
import java.time.LocalDateTime;
14+
import java.util.ArrayList;
15+
import java.util.List;
16+
17+
import javax.persistence.criteria.CriteriaBuilder;
18+
import javax.persistence.criteria.Path;
19+
import javax.persistence.criteria.Predicate;
20+
import javax.persistence.criteria.Root;
21+
import javax.persistence.metamodel.Attribute;
22+
23+
import org.springframework.core.convert.ConversionService;
24+
import org.springframework.core.convert.support.DefaultConversionService;
25+
import org.springframework.util.StringUtils;
26+
27+
import cz.jirutka.rsql.parser.ast.AndNode;
28+
import cz.jirutka.rsql.parser.ast.ComparisonNode;
29+
import cz.jirutka.rsql.parser.ast.ComparisonOperator;
30+
import cz.jirutka.rsql.parser.ast.Node;
31+
import cz.jirutka.rsql.parser.ast.OrNode;
32+
import cz.jirutka.rsql.parser.ast.RSQLVisitor;
33+
34+
// clone from com.putracode.utils.JPARsqlConverter
35+
@SuppressWarnings({ "rawtypes", "unchecked" })
36+
public class RsqlJpaConverter implements RSQLVisitor<Predicate, Root> {
37+
private final CriteriaBuilder builder;
38+
private final ConversionService conversionService = new DefaultConversionService();
39+
40+
public RsqlJpaConverter(CriteriaBuilder builder) {
41+
this.builder = builder;
42+
}
43+
44+
public Predicate visit(AndNode node, Root root) {
45+
46+
return builder.and(processNodes(node.getChildren(), root));
47+
}
48+
49+
public Predicate visit(OrNode node, Root root) {
50+
51+
return builder.or(processNodes(node.getChildren(), root));
52+
}
53+
54+
public Predicate visit(ComparisonNode node, Root root) {
55+
ComparisonOperator op = node.getOperator();
56+
//Path attrPath = root.get(node.getSelector());
57+
//Attribute attribute = root.getModel().getAttribute(node.getSelector());
58+
RsqlJpaHolder holder = RsqlJpaSpecification.findPropertyPath(node.getSelector(), root);
59+
Path attrPath = holder.path;
60+
Attribute attribute = holder.attribute;
61+
Class type = attribute.getJavaType();
62+
if (node.getArguments().size() > 1) {
63+
/**
64+
* Implementasi List
65+
*/
66+
List<Object> listObject = new ArrayList<>();
67+
for (String argument : node.getArguments()) {
68+
listObject.add(castDynamicClass(type, argument));
69+
}
70+
if (op.equals(IN)) {
71+
return attrPath.in(listObject);
72+
} else {
73+
return builder.not(attrPath.in(listObject));
74+
}
75+
76+
} else {
77+
/**
78+
* Searching With One Value
79+
*/
80+
Object argument = castDynamicClass(type, node.getArguments().get(0));
81+
if (op.equals(EQUAL)) {
82+
if (type.equals(String.class)) {
83+
if (argument.toString().contains("*") && argument.toString().contains("^")) {
84+
return builder.like(builder.upper(attrPath), argument.toString().replace("*", "%").replace("^", "").toUpperCase());
85+
} else if (argument.toString().contains("*")) {
86+
return builder.like(attrPath, argument.toString().replace('*', '%'));
87+
} else if (argument.toString().contains("^")) {
88+
89+
return builder.equal(builder.upper(attrPath), argument.toString().replace("^", "").toUpperCase());
90+
} else {
91+
return builder.equal(attrPath, argument);
92+
}
93+
} else if (argument == null) {
94+
return builder.isNull(attrPath);
95+
} else {
96+
return builder.equal(attrPath, argument);
97+
}
98+
}
99+
if (op.equals(NOT_EQUAL)) {
100+
if (type.equals(String.class)) {
101+
if (argument.toString().contains("*") && argument.toString().contains("^")) {
102+
return builder.notLike(builder.upper(attrPath), argument.toString().replace("*", "%").replace("^", "").toUpperCase());
103+
} else if (argument.toString().contains("*")) {
104+
return builder.notLike(attrPath, argument.toString().replace('*', '%'));
105+
} else if (argument.toString().contains("^")) {
106+
return builder.notEqual(builder.upper(attrPath), argument.toString().replace("^", "").toUpperCase());
107+
} else {
108+
return builder.notEqual(attrPath, argument);
109+
}
110+
} else if (argument == null) {
111+
return builder.isNotNull(attrPath);
112+
} else {
113+
return builder.notEqual(attrPath, argument);
114+
}
115+
}
116+
if (!Comparable.class.isAssignableFrom(type)) {
117+
throw new IllegalArgumentException(String.format(
118+
"Operator %s can be used only for Comparables", op));
119+
}
120+
Comparable comparable = (Comparable) conversionService.convert(argument, type);
121+
122+
if (op.equals(GREATER_THAN)) {
123+
return builder.greaterThan(attrPath, comparable);
124+
}
125+
if (op.equals(GREATER_THAN_OR_EQUAL)) {
126+
return builder.greaterThanOrEqualTo(attrPath, comparable);
127+
}
128+
if (op.equals(LESS_THAN)) {
129+
return builder.lessThan(attrPath, comparable);
130+
}
131+
if (op.equals(LESS_THAN_OR_EQUAL)) {
132+
return builder.lessThanOrEqualTo(attrPath, comparable);
133+
}
134+
}
135+
throw new IllegalArgumentException("Unknown operator: " + op);
136+
}
137+
138+
private Predicate[] processNodes(List<Node> nodes, Root root) {
139+
140+
Predicate[] predicates = new Predicate[nodes.size()];
141+
142+
for (int i = 0; i < nodes.size(); i++) {
143+
predicates[i] = nodes.get(i).accept(this, root);
144+
}
145+
return predicates;
146+
}
147+
148+
public Object castDynamicClass(Class dynamicClass, String value) {
149+
Object object = null;
150+
try {
151+
if (dynamicClass.equals(LocalDate.class)) {
152+
object = LocalDate.parse(value);
153+
} else if (dynamicClass.equals(LocalDateTime.class)) {
154+
object = LocalDateTime.parse(value);
155+
} else if (dynamicClass.equals(Character.class)) {
156+
object = (!StringUtils.isEmpty(value) ? value.charAt(0) : null);
157+
} else {
158+
Constructor<?> cons = (Constructor<?>) dynamicClass.getConstructor(new Class<?>[] { String.class });
159+
object = cons.newInstance(new Object[] { value });
160+
}
161+
162+
return object;
163+
} catch (Exception e) {
164+
e.printStackTrace();
165+
}
166+
return null;
167+
}
168+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
package com.perplexhub.rsql.jpa;
2+
3+
import javax.persistence.criteria.Path;
4+
import javax.persistence.metamodel.Attribute;
5+
6+
class RsqlJpaHolder<X, Y> {
7+
Path<X> path;
8+
Attribute<X, Y> attribute;
9+
}

0 commit comments

Comments
 (0)