move query language to new module (#2864)
* move security content from spring-security-rest-full * swagger update * move query language to new module * rename spring-security-rest-full to spring-rest-full
This commit is contained in:
committed by
Grzegorz Piwowarek
parent
864c2e2190
commit
1c9477390d
13
spring-rest-query-language/.gitignore
vendored
Normal file
13
spring-rest-query-language/.gitignore
vendored
Normal file
@@ -0,0 +1,13 @@
|
||||
*.class
|
||||
|
||||
#folders#
|
||||
/target
|
||||
/neoDb*
|
||||
/data
|
||||
/src/main/webapp/WEB-INF/classes
|
||||
*/META-INF/*
|
||||
|
||||
# Packaged files #
|
||||
*.jar
|
||||
*.war
|
||||
*.ear
|
||||
35
spring-rest-query-language/README.md
Normal file
35
spring-rest-query-language/README.md
Normal file
@@ -0,0 +1,35 @@
|
||||
=========
|
||||
|
||||
## REST Example Project Query Language
|
||||
|
||||
### Courses
|
||||
The "REST With Spring" Classes: http://bit.ly/restwithspring
|
||||
|
||||
The "Learn Spring Security" Classes: http://github.learnspringsecurity.com
|
||||
|
||||
### Relevant Articles:
|
||||
|
||||
- [REST Query Language with Spring and JPA Criteria](http://www.baeldung.com/rest-search-language-spring-jpa-criteria)
|
||||
- [REST Query Language with Spring Data JPA Specifications](http://www.baeldung.com/rest-api-search-language-spring-data-specifications)
|
||||
- [REST Query Language with Spring Data JPA and QueryDSL](http://www.baeldung.com/rest-api-search-language-spring-data-querydsl)
|
||||
- [REST Query Language – Advanced Search Operations](http://www.baeldung.com/rest-api-query-search-language-more-operations)
|
||||
- [REST Query Language with RSQL](http://www.baeldung.com/rest-api-search-language-rsql-fiql)
|
||||
- [REST Query Language – Implementing OR Operation](http://www.baeldung.com/rest-api-query-search-or-operation)
|
||||
|
||||
|
||||
|
||||
|
||||
### Build the Project
|
||||
```
|
||||
mvn clean install
|
||||
```
|
||||
|
||||
|
||||
### Set up MySQL
|
||||
```
|
||||
mysql -u root -p
|
||||
> CREATE USER 'tutorialuser'@'localhost' IDENTIFIED BY 'tutorialmy5ql';
|
||||
> GRANT ALL PRIVILEGES ON *.* TO 'tutorialuser'@'localhost';
|
||||
> FLUSH PRIVILEGES;
|
||||
```
|
||||
|
||||
399
spring-rest-query-language/pom.xml
Normal file
399
spring-rest-query-language/pom.xml
Normal file
@@ -0,0 +1,399 @@
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<groupId>com.baeldung</groupId>
|
||||
<artifactId>spring-rest-query-language</artifactId>
|
||||
<version>0.1-SNAPSHOT</version>
|
||||
|
||||
<name>spring-rest-query-language</name>
|
||||
<packaging>war</packaging>
|
||||
|
||||
<parent>
|
||||
<artifactId>parent-boot-4</artifactId>
|
||||
<groupId>com.baeldung</groupId>
|
||||
<version>0.0.1-SNAPSHOT</version>
|
||||
<relativePath>../parent-boot-4</relativePath>
|
||||
</parent>
|
||||
|
||||
<dependencies>
|
||||
|
||||
<!-- Spring Boot Dependencies -->
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-web</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-actuator</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.aspectj</groupId>
|
||||
<artifactId>aspectjweaver</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.apache.tomcat.embed</groupId>
|
||||
<artifactId>tomcat-embed-jasper</artifactId>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
|
||||
<!-- Spring -->
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springframework</groupId>
|
||||
<artifactId>spring-core</artifactId>
|
||||
<exclusions>
|
||||
<exclusion>
|
||||
<artifactId>commons-logging</artifactId>
|
||||
<groupId>commons-logging</groupId>
|
||||
</exclusion>
|
||||
</exclusions>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework</groupId>
|
||||
<artifactId>spring-context</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework</groupId>
|
||||
<artifactId>spring-jdbc</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework</groupId>
|
||||
<artifactId>spring-beans</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework</groupId>
|
||||
<artifactId>spring-aop</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework</groupId>
|
||||
<artifactId>spring-tx</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework</groupId>
|
||||
<artifactId>spring-expression</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springframework</groupId>
|
||||
<artifactId>spring-web</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework</groupId>
|
||||
<artifactId>spring-webmvc</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springframework.data</groupId>
|
||||
<artifactId>spring-data-commons</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- deployment -->
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-tomcat</artifactId>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
|
||||
<!-- Querydsl -->
|
||||
|
||||
<dependency>
|
||||
<groupId>com.querydsl</groupId>
|
||||
<artifactId>querydsl-apt</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.querydsl</groupId>
|
||||
<artifactId>querydsl-jpa</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- Rsql -->
|
||||
|
||||
<dependency>
|
||||
<groupId>cz.jirutka.rsql</groupId>
|
||||
<artifactId>rsql-parser</artifactId>
|
||||
<version>${rsql.version}</version>
|
||||
</dependency>
|
||||
|
||||
<!-- web -->
|
||||
|
||||
<dependency>
|
||||
<groupId>org.apache.httpcomponents</groupId>
|
||||
<artifactId>httpclient</artifactId>
|
||||
<exclusions>
|
||||
<exclusion>
|
||||
<artifactId>commons-logging</artifactId>
|
||||
<groupId>commons-logging</groupId>
|
||||
</exclusion>
|
||||
</exclusions>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.httpcomponents</groupId>
|
||||
<artifactId>httpcore</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- persistence -->
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springframework</groupId>
|
||||
<artifactId>spring-orm</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.data</groupId>
|
||||
<artifactId>spring-data-jpa</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.hibernate</groupId>
|
||||
<artifactId>hibernate-entitymanager</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>xml-apis</groupId>
|
||||
<artifactId>xml-apis</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.javassist</groupId>
|
||||
<artifactId>javassist</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>mysql</groupId>
|
||||
<artifactId>mysql-connector-java</artifactId>
|
||||
<scope>runtime</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.h2database</groupId>
|
||||
<artifactId>h2</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- web -->
|
||||
|
||||
<dependency>
|
||||
<groupId>javax.servlet</groupId>
|
||||
<artifactId>javax.servlet-api</artifactId>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>javax.servlet</groupId>
|
||||
<artifactId>jstl</artifactId>
|
||||
<scope>runtime</scope>
|
||||
</dependency>
|
||||
|
||||
<!-- marshalling -->
|
||||
|
||||
<dependency>
|
||||
<groupId>com.fasterxml.jackson.core</groupId>
|
||||
<artifactId>jackson-databind</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.thoughtworks.xstream</groupId>
|
||||
<artifactId>xstream</artifactId>
|
||||
<version>${xstream.version}</version>
|
||||
</dependency>
|
||||
|
||||
<!-- util -->
|
||||
|
||||
<dependency>
|
||||
<groupId>com.google.guava</groupId>
|
||||
<artifactId>guava</artifactId>
|
||||
<version>${guava.version}</version>
|
||||
</dependency>
|
||||
|
||||
<!-- test scoped -->
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springframework</groupId>
|
||||
<artifactId>spring-test</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
|
||||
<!-- <dependency> -->
|
||||
<!-- <groupId>org.hamcrest</groupId> -->
|
||||
<!-- <artifactId>hamcrest-core</artifactId> -->
|
||||
<!-- <scope>test</scope> -->
|
||||
<!-- </dependency> -->
|
||||
<dependency>
|
||||
<groupId>org.hamcrest</groupId>
|
||||
<artifactId>hamcrest-library</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.mockito</groupId>
|
||||
<artifactId>mockito-core</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
<finalName>spring-rest-query-language</finalName>
|
||||
<resources>
|
||||
<resource>
|
||||
<directory>src/main/resources</directory>
|
||||
<filtering>true</filtering>
|
||||
</resource>
|
||||
</resources>
|
||||
|
||||
<plugins>
|
||||
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-war-plugin</artifactId>
|
||||
</plugin>
|
||||
|
||||
<plugin>
|
||||
<groupId>org.codehaus.cargo</groupId>
|
||||
<artifactId>cargo-maven2-plugin</artifactId>
|
||||
<version>${cargo-maven2-plugin.version}</version>
|
||||
<configuration>
|
||||
<wait>true</wait>
|
||||
<container>
|
||||
<containerId>jetty8x</containerId>
|
||||
<type>embedded</type>
|
||||
<systemProperties>
|
||||
<!-- <provPersistenceTarget>cargo</provPersistenceTarget> -->
|
||||
</systemProperties>
|
||||
</container>
|
||||
<configuration>
|
||||
<properties>
|
||||
<cargo.servlet.port>8082</cargo.servlet.port>
|
||||
</properties>
|
||||
</configuration>
|
||||
</configuration>
|
||||
</plugin>
|
||||
|
||||
<!-- Querydsl and Specifications -->
|
||||
|
||||
<plugin>
|
||||
<groupId>com.mysema.maven</groupId>
|
||||
<artifactId>apt-maven-plugin</artifactId>
|
||||
<version>${apt-maven-plugin.version}</version>
|
||||
<executions>
|
||||
<execution>
|
||||
<goals>
|
||||
<goal>process</goal>
|
||||
</goals>
|
||||
<configuration>
|
||||
<outputDirectory>target/generated-sources/java</outputDirectory>
|
||||
<processor>com.querydsl.apt.jpa.JPAAnnotationProcessor</processor>
|
||||
</configuration>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
|
||||
</plugins>
|
||||
|
||||
</build>
|
||||
|
||||
<profiles>
|
||||
<profile>
|
||||
<id>integration</id>
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-surefire-plugin</artifactId>
|
||||
<executions>
|
||||
<execution>
|
||||
<phase>integration-test</phase>
|
||||
<goals>
|
||||
<goal>test</goal>
|
||||
</goals>
|
||||
<configuration>
|
||||
<excludes>
|
||||
<exclude>**/*LiveTest.java</exclude>
|
||||
</excludes>
|
||||
<includes>
|
||||
<include>**/*IntegrationTest.java</include>
|
||||
</includes>
|
||||
</configuration>
|
||||
</execution>
|
||||
</executions>
|
||||
<configuration>
|
||||
<systemPropertyVariables>
|
||||
<test.mime>json</test.mime>
|
||||
</systemPropertyVariables>
|
||||
</configuration>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
</profile>
|
||||
|
||||
<profile>
|
||||
<id>live</id>
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-surefire-plugin</artifactId>
|
||||
<executions>
|
||||
<execution>
|
||||
<phase>integration-test</phase>
|
||||
<goals>
|
||||
<goal>test</goal>
|
||||
</goals>
|
||||
<configuration>
|
||||
<excludes>
|
||||
<exclude>**/*IntegrationTest.java</exclude>
|
||||
</excludes>
|
||||
<includes>
|
||||
<include>**/*LiveTest.java</include>
|
||||
</includes>
|
||||
</configuration>
|
||||
</execution>
|
||||
</executions>
|
||||
<configuration>
|
||||
<systemPropertyVariables>
|
||||
<test.mime>json</test.mime>
|
||||
</systemPropertyVariables>
|
||||
</configuration>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.codehaus.cargo</groupId>
|
||||
<artifactId>cargo-maven2-plugin</artifactId>
|
||||
<configuration>
|
||||
<wait>false</wait>
|
||||
</configuration>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>start-server</id>
|
||||
<phase>pre-integration-test</phase>
|
||||
<goals>
|
||||
<goal>start</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
<execution>
|
||||
<id>stop-server</id>
|
||||
<phase>post-integration-test</phase>
|
||||
<goals>
|
||||
<goal>stop</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
</profile>
|
||||
</profiles>
|
||||
|
||||
<properties>
|
||||
<!-- persistence -->
|
||||
<rsql.version>2.1.0</rsql.version>
|
||||
|
||||
<!-- various -->
|
||||
<xstream.version>1.4.9</xstream.version>
|
||||
|
||||
<!-- util -->
|
||||
<guava.version>19.0</guava.version>
|
||||
<commons-lang3.version>3.5</commons-lang3.version>
|
||||
|
||||
<!-- Maven plugins -->
|
||||
<cargo-maven2-plugin.version>1.6.1</cargo-maven2-plugin.version>
|
||||
<apt-maven-plugin.version>1.1.3</apt-maven-plugin.version>
|
||||
</properties>
|
||||
|
||||
</project>
|
||||
@@ -0,0 +1,99 @@
|
||||
package org.baeldung.persistence.dao;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.Deque;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.baeldung.web.util.SearchOperation;
|
||||
import org.baeldung.web.util.SpecSearchCriteria;
|
||||
import org.springframework.data.jpa.domain.Specification;
|
||||
import org.springframework.data.jpa.domain.Specifications;
|
||||
|
||||
public class GenericSpecificationsBuilder<U> {
|
||||
|
||||
private final List<SpecSearchCriteria> params;
|
||||
|
||||
public GenericSpecificationsBuilder() {
|
||||
this.params = new ArrayList<>();
|
||||
}
|
||||
|
||||
public final GenericSpecificationsBuilder<U> with(final String key, final String operation, final Object value, final String prefix, final String suffix) {
|
||||
return with(null, key, operation, value, prefix, suffix);
|
||||
}
|
||||
|
||||
public final GenericSpecificationsBuilder<U> with(final String precedenceIndicator, final String key, final String operation, final Object value, final String prefix, final String suffix) {
|
||||
SearchOperation op = SearchOperation.getSimpleOperation(operation.charAt(0));
|
||||
if (op != null) {
|
||||
if (op == SearchOperation.EQUALITY) // the operation may be complex operation
|
||||
{
|
||||
final boolean startWithAsterisk = prefix != null && prefix.contains(SearchOperation.ZERO_OR_MORE_REGEX);
|
||||
final boolean endWithAsterisk = suffix != null && suffix.contains(SearchOperation.ZERO_OR_MORE_REGEX);
|
||||
|
||||
if (startWithAsterisk && endWithAsterisk) {
|
||||
op = SearchOperation.CONTAINS;
|
||||
} else if (startWithAsterisk) {
|
||||
op = SearchOperation.ENDS_WITH;
|
||||
} else if (endWithAsterisk) {
|
||||
op = SearchOperation.STARTS_WITH;
|
||||
}
|
||||
}
|
||||
params.add(new SpecSearchCriteria(precedenceIndicator, key, op, value));
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
public Specification<U> build(Function<SpecSearchCriteria, Specification<U>> converter) {
|
||||
|
||||
if (params.size() == 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
final List<Specification<U>> specs = params.stream()
|
||||
.map(converter)
|
||||
.collect(Collectors.toCollection(ArrayList::new));
|
||||
|
||||
Specification<U> result = specs.get(0);
|
||||
|
||||
for (int idx = 1; idx < specs.size(); idx++) {
|
||||
result = params.get(idx)
|
||||
.isOrPredicate()
|
||||
? Specifications.where(result)
|
||||
.or(specs.get(idx))
|
||||
: Specifications.where(result)
|
||||
.and(specs.get(idx));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public Specification<U> build(Deque<?> postFixedExprStack, Function<SpecSearchCriteria, Specification<U>> converter) {
|
||||
|
||||
Deque<Specification<U>> specStack = new LinkedList<>();
|
||||
|
||||
Collections.reverse((List<?>) postFixedExprStack);
|
||||
|
||||
while (!postFixedExprStack.isEmpty()) {
|
||||
Object mayBeOperand = postFixedExprStack.pop();
|
||||
|
||||
if (!(mayBeOperand instanceof String)) {
|
||||
specStack.push(converter.apply((SpecSearchCriteria) mayBeOperand));
|
||||
} else {
|
||||
Specification<U> operand1 = specStack.pop();
|
||||
Specification<U> operand2 = specStack.pop();
|
||||
if (mayBeOperand.equals(SearchOperation.AND_OPERATOR))
|
||||
specStack.push(Specifications.where(operand1)
|
||||
.and(operand2));
|
||||
else if (mayBeOperand.equals(SearchOperation.OR_OPERATOR))
|
||||
specStack.push(Specifications.where(operand1)
|
||||
.or(operand2));
|
||||
}
|
||||
|
||||
}
|
||||
return specStack.pop();
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
package org.baeldung.persistence.dao;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.baeldung.persistence.model.User;
|
||||
import org.baeldung.web.util.SearchCriteria;
|
||||
|
||||
public interface IUserDAO {
|
||||
List<User> searchUser(List<SearchCriteria> params);
|
||||
|
||||
void save(User entity);
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
package org.baeldung.persistence.dao;
|
||||
|
||||
import org.baeldung.persistence.model.MyUser;
|
||||
import org.baeldung.web.util.SearchCriteria;
|
||||
|
||||
import com.querydsl.core.types.dsl.BooleanExpression;
|
||||
import com.querydsl.core.types.dsl.NumberPath;
|
||||
import com.querydsl.core.types.dsl.PathBuilder;
|
||||
import com.querydsl.core.types.dsl.StringPath;
|
||||
|
||||
public class MyUserPredicate {
|
||||
|
||||
private SearchCriteria criteria;
|
||||
|
||||
public MyUserPredicate(final SearchCriteria criteria) {
|
||||
this.criteria = criteria;
|
||||
}
|
||||
|
||||
public BooleanExpression getPredicate() {
|
||||
final PathBuilder<MyUser> entityPath = new PathBuilder<>(MyUser.class, "myUser");
|
||||
|
||||
if (isNumeric(criteria.getValue().toString())) {
|
||||
final NumberPath<Integer> path = entityPath.getNumber(criteria.getKey(), Integer.class);
|
||||
final int value = Integer.parseInt(criteria.getValue().toString());
|
||||
switch (criteria.getOperation()) {
|
||||
case ":":
|
||||
return path.eq(value);
|
||||
case ">":
|
||||
return path.goe(value);
|
||||
case "<":
|
||||
return path.loe(value);
|
||||
}
|
||||
} else {
|
||||
final StringPath path = entityPath.getString(criteria.getKey());
|
||||
if (criteria.getOperation().equalsIgnoreCase(":")) {
|
||||
return path.containsIgnoreCase(criteria.getValue().toString());
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public SearchCriteria getCriteria() {
|
||||
return criteria;
|
||||
}
|
||||
|
||||
public void setCriteria(final SearchCriteria criteria) {
|
||||
this.criteria = criteria;
|
||||
}
|
||||
|
||||
public static boolean isNumeric(final String str) {
|
||||
try {
|
||||
Integer.parseInt(str);
|
||||
} catch (final NumberFormatException e) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
package org.baeldung.persistence.dao;
|
||||
|
||||
import com.querydsl.core.types.dsl.BooleanExpression;
|
||||
import org.baeldung.web.util.SearchCriteria;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public final class MyUserPredicatesBuilder {
|
||||
private final List<SearchCriteria> params;
|
||||
|
||||
public MyUserPredicatesBuilder() {
|
||||
params = new ArrayList<>();
|
||||
}
|
||||
|
||||
public MyUserPredicatesBuilder with(final String key, final String operation, final Object value) {
|
||||
params.add(new SearchCriteria(key, operation, value));
|
||||
return this;
|
||||
}
|
||||
|
||||
public BooleanExpression build() {
|
||||
if (params.size() == 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
final List<BooleanExpression> predicates = new ArrayList<>();
|
||||
MyUserPredicate predicate;
|
||||
for (final SearchCriteria param : params) {
|
||||
predicate = new MyUserPredicate(param);
|
||||
final BooleanExpression exp = predicate.getPredicate();
|
||||
if (exp != null) {
|
||||
predicates.add(exp);
|
||||
}
|
||||
}
|
||||
|
||||
BooleanExpression result = predicates.get(0);
|
||||
for (int i = 1; i < predicates.size(); i++) {
|
||||
result = result.and(predicates.get(i));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
package org.baeldung.persistence.dao;
|
||||
|
||||
import com.querydsl.core.types.dsl.StringExpression;
|
||||
import org.baeldung.persistence.model.MyUser;
|
||||
import org.baeldung.persistence.model.QMyUser;
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
import org.springframework.data.querydsl.QueryDslPredicateExecutor;
|
||||
import org.springframework.data.querydsl.binding.QuerydslBinderCustomizer;
|
||||
import org.springframework.data.querydsl.binding.QuerydslBindings;
|
||||
|
||||
import com.querydsl.core.types.dsl.StringPath;
|
||||
import org.springframework.data.querydsl.binding.SingleValueBinding;
|
||||
|
||||
public interface MyUserRepository extends JpaRepository<MyUser, Long>, QueryDslPredicateExecutor<MyUser>, QuerydslBinderCustomizer<QMyUser> {
|
||||
@Override
|
||||
default public void customize(final QuerydslBindings bindings, final QMyUser root) {
|
||||
bindings.bind(String.class)
|
||||
.first((SingleValueBinding<StringPath, String>) StringExpression::containsIgnoreCase);
|
||||
bindings.excluding(root.email);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
package org.baeldung.persistence.dao;
|
||||
|
||||
import org.baeldung.persistence.model.User;
|
||||
import org.baeldung.web.util.SearchCriteria;
|
||||
import org.springframework.stereotype.Repository;
|
||||
|
||||
import javax.persistence.EntityManager;
|
||||
import javax.persistence.PersistenceContext;
|
||||
import javax.persistence.criteria.CriteriaBuilder;
|
||||
import javax.persistence.criteria.CriteriaQuery;
|
||||
import javax.persistence.criteria.Predicate;
|
||||
import javax.persistence.criteria.Root;
|
||||
import java.util.List;
|
||||
|
||||
@Repository
|
||||
public class UserDAO implements IUserDAO {
|
||||
|
||||
@PersistenceContext
|
||||
private EntityManager entityManager;
|
||||
|
||||
@Override
|
||||
public List<User> searchUser(final List<SearchCriteria> params) {
|
||||
final CriteriaBuilder builder = entityManager.getCriteriaBuilder();
|
||||
final CriteriaQuery<User> query = builder.createQuery(User.class);
|
||||
final Root r = query.from(User.class);
|
||||
|
||||
Predicate predicate = builder.conjunction();
|
||||
|
||||
for (final SearchCriteria param : params) {
|
||||
if (param.getOperation().equalsIgnoreCase(">")) {
|
||||
predicate = builder.and(predicate, builder.greaterThanOrEqualTo(r.get(param.getKey()), param.getValue().toString()));
|
||||
} else if (param.getOperation().equalsIgnoreCase("<")) {
|
||||
predicate = builder.and(predicate, builder.lessThanOrEqualTo(r.get(param.getKey()), param.getValue().toString()));
|
||||
} else if (param.getOperation().equalsIgnoreCase(":")) {
|
||||
if (r.get(param.getKey()).getJavaType() == String.class) {
|
||||
predicate = builder.and(predicate, builder.like(r.get(param.getKey()), "%" + param.getValue() + "%"));
|
||||
} else {
|
||||
predicate = builder.and(predicate, builder.equal(r.get(param.getKey()), param.getValue()));
|
||||
}
|
||||
}
|
||||
}
|
||||
query.where(predicate);
|
||||
|
||||
return entityManager.createQuery(query).getResultList();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void save(final User entity) {
|
||||
entityManager.persist(entity);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
package org.baeldung.persistence.dao;
|
||||
|
||||
import org.baeldung.persistence.model.User;
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
|
||||
|
||||
public interface UserRepository extends JpaRepository<User, Long>, JpaSpecificationExecutor<User> {
|
||||
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
package org.baeldung.persistence.dao;
|
||||
|
||||
import org.baeldung.persistence.model.User;
|
||||
import org.baeldung.web.util.SpecSearchCriteria;
|
||||
import org.springframework.data.jpa.domain.Specification;
|
||||
|
||||
import javax.persistence.criteria.CriteriaBuilder;
|
||||
import javax.persistence.criteria.CriteriaQuery;
|
||||
import javax.persistence.criteria.Predicate;
|
||||
import javax.persistence.criteria.Root;
|
||||
|
||||
public class UserSpecification implements Specification<User> {
|
||||
|
||||
private SpecSearchCriteria criteria;
|
||||
|
||||
public UserSpecification(final SpecSearchCriteria criteria) {
|
||||
super();
|
||||
this.criteria = criteria;
|
||||
}
|
||||
|
||||
public SpecSearchCriteria getCriteria() {
|
||||
return criteria;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Predicate toPredicate(final Root<User> root, final CriteriaQuery<?> query, final CriteriaBuilder builder) {
|
||||
switch (criteria.getOperation()) {
|
||||
case EQUALITY:
|
||||
return builder.equal(root.get(criteria.getKey()), criteria.getValue());
|
||||
case NEGATION:
|
||||
return builder.notEqual(root.get(criteria.getKey()), criteria.getValue());
|
||||
case GREATER_THAN:
|
||||
return builder.greaterThan(root.get(criteria.getKey()), criteria.getValue().toString());
|
||||
case LESS_THAN:
|
||||
return builder.lessThan(root.get(criteria.getKey()), criteria.getValue().toString());
|
||||
case LIKE:
|
||||
return builder.like(root.get(criteria.getKey()), criteria.getValue().toString());
|
||||
case STARTS_WITH:
|
||||
return builder.like(root.get(criteria.getKey()), criteria.getValue() + "%");
|
||||
case ENDS_WITH:
|
||||
return builder.like(root.get(criteria.getKey()), "%" + criteria.getValue());
|
||||
case CONTAINS:
|
||||
return builder.like(root.get(criteria.getKey()), "%" + criteria.getValue() + "%");
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
package org.baeldung.persistence.dao;
|
||||
|
||||
import org.baeldung.persistence.model.User;
|
||||
import org.baeldung.web.util.SearchOperation;
|
||||
import org.baeldung.web.util.SpecSearchCriteria;
|
||||
import org.springframework.data.jpa.domain.Specification;
|
||||
import org.springframework.data.jpa.domain.Specifications;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public final class UserSpecificationsBuilder {
|
||||
|
||||
private final List<SpecSearchCriteria> params;
|
||||
|
||||
public UserSpecificationsBuilder() {
|
||||
params = new ArrayList<>();
|
||||
}
|
||||
|
||||
// API
|
||||
|
||||
public final UserSpecificationsBuilder with(final String key, final String operation, final Object value, final String prefix, final String suffix) {
|
||||
return with(null, key, operation, value, prefix, suffix);
|
||||
}
|
||||
|
||||
public final UserSpecificationsBuilder with(final String orPredicate, final String key, final String operation, final Object value, final String prefix, final String suffix) {
|
||||
SearchOperation op = SearchOperation.getSimpleOperation(operation.charAt(0));
|
||||
if (op != null) {
|
||||
if (op == SearchOperation.EQUALITY) { // the operation may be complex operation
|
||||
final boolean startWithAsterisk = prefix != null && prefix.contains(SearchOperation.ZERO_OR_MORE_REGEX);
|
||||
final boolean endWithAsterisk = suffix != null && suffix.contains(SearchOperation.ZERO_OR_MORE_REGEX);
|
||||
|
||||
if (startWithAsterisk && endWithAsterisk) {
|
||||
op = SearchOperation.CONTAINS;
|
||||
} else if (startWithAsterisk) {
|
||||
op = SearchOperation.ENDS_WITH;
|
||||
} else if (endWithAsterisk) {
|
||||
op = SearchOperation.STARTS_WITH;
|
||||
}
|
||||
}
|
||||
params.add(new SpecSearchCriteria(orPredicate, key, op, value));
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
public Specification<User> build() {
|
||||
|
||||
if (params.size() == 0)
|
||||
return null;
|
||||
|
||||
Specification<User> result = new UserSpecification(params.get(0));
|
||||
|
||||
for (int i = 1; i < params.size(); i++) {
|
||||
result = params.get(i)
|
||||
.isOrPredicate()
|
||||
? Specifications.where(result)
|
||||
.or(new UserSpecification(params.get(i)))
|
||||
: Specifications.where(result)
|
||||
.and(new UserSpecification(params.get(i)));
|
||||
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public final UserSpecificationsBuilder with(UserSpecification spec) {
|
||||
params.add(spec.getCriteria());
|
||||
return this;
|
||||
}
|
||||
|
||||
public final UserSpecificationsBuilder with(SpecSearchCriteria criteria) {
|
||||
params.add(criteria);
|
||||
return this;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
package org.baeldung.persistence.dao.rsql;
|
||||
|
||||
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<T>, Void> {
|
||||
|
||||
private GenericRsqlSpecBuilder<T> builder;
|
||||
|
||||
public CustomRsqlVisitor() {
|
||||
builder = new GenericRsqlSpecBuilder<T>();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Specification<T> visit(final AndNode node, final Void param) {
|
||||
return builder.createSpecification(node);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Specification<T> visit(final OrNode node, final Void param) {
|
||||
return builder.createSpecification(node);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Specification<T> visit(final ComparisonNode node, final Void params) {
|
||||
return builder.createSpecification(node);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
package org.baeldung.persistence.dao.rsql;
|
||||
|
||||
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;
|
||||
import org.springframework.data.jpa.domain.Specifications;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class GenericRsqlSpecBuilder<T> {
|
||||
|
||||
public Specifications<T> createSpecification(final Node node) {
|
||||
if (node instanceof LogicalNode) {
|
||||
return createSpecification((LogicalNode) node);
|
||||
}
|
||||
if (node instanceof ComparisonNode) {
|
||||
return createSpecification((ComparisonNode) node);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public Specifications<T> createSpecification(final LogicalNode logicalNode) {
|
||||
final List<Specifications<T>> specs = new ArrayList<Specifications<T>>();
|
||||
Specifications<T> temp;
|
||||
for (final Node node : logicalNode.getChildren()) {
|
||||
temp = createSpecification(node);
|
||||
if (temp != null) {
|
||||
specs.add(temp);
|
||||
}
|
||||
}
|
||||
|
||||
Specifications<T> 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<T> createSpecification(final ComparisonNode comparisonNode) {
|
||||
return Specifications.where(new GenericRsqlSpecification<T>(comparisonNode.getSelector(), comparisonNode.getOperator(), comparisonNode.getArguments()));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,90 @@
|
||||
package org.baeldung.persistence.dao.rsql;
|
||||
|
||||
import cz.jirutka.rsql.parser.ast.ComparisonOperator;
|
||||
import org.springframework.data.jpa.domain.Specification;
|
||||
|
||||
import javax.persistence.criteria.CriteriaBuilder;
|
||||
import javax.persistence.criteria.CriteriaQuery;
|
||||
import javax.persistence.criteria.Predicate;
|
||||
import javax.persistence.criteria.Root;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class GenericRsqlSpecification<T> implements Specification<T> {
|
||||
|
||||
private String property;
|
||||
private ComparisonOperator operator;
|
||||
private List<String> arguments;
|
||||
|
||||
public GenericRsqlSpecification(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<T> 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.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<T> 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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
package org.baeldung.persistence.dao.rsql;
|
||||
|
||||
import cz.jirutka.rsql.parser.ast.ComparisonOperator;
|
||||
import cz.jirutka.rsql.parser.ast.RSQLOperators;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
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;
|
||||
|
||||
RsqlSearchOperation(final ComparisonOperator operator) {
|
||||
this.operator = operator;
|
||||
}
|
||||
|
||||
public static RsqlSearchOperation getSimpleOperator(final ComparisonOperator operator) {
|
||||
return Arrays.stream(values())
|
||||
.filter(operation -> operation.getOperator() == operator)
|
||||
.findAny().orElse(null);
|
||||
}
|
||||
|
||||
public ComparisonOperator getOperator() {
|
||||
return operator;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,107 @@
|
||||
package org.baeldung.persistence.model;
|
||||
|
||||
import javax.persistence.Entity;
|
||||
import javax.persistence.GeneratedValue;
|
||||
import javax.persistence.GenerationType;
|
||||
import javax.persistence.Id;
|
||||
|
||||
// used for Querydsl test
|
||||
|
||||
@Entity
|
||||
public class MyUser {
|
||||
@Id
|
||||
@GeneratedValue(strategy = GenerationType.AUTO)
|
||||
private Long id;
|
||||
|
||||
private String firstName;
|
||||
|
||||
private String lastName;
|
||||
|
||||
private String email;
|
||||
|
||||
private int age;
|
||||
|
||||
public MyUser() {
|
||||
super();
|
||||
}
|
||||
|
||||
public MyUser(final String firstName, final String lastName, final String email, final int age) {
|
||||
super();
|
||||
this.firstName = firstName;
|
||||
this.lastName = lastName;
|
||||
this.email = email;
|
||||
this.age = age;
|
||||
}
|
||||
|
||||
//
|
||||
|
||||
public Long getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public void setId(final Long id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public String getFirstName() {
|
||||
return firstName;
|
||||
}
|
||||
|
||||
public void setFirstName(final String firstName) {
|
||||
this.firstName = firstName;
|
||||
}
|
||||
|
||||
public String getLastName() {
|
||||
return lastName;
|
||||
}
|
||||
|
||||
public void setLastName(final String lastName) {
|
||||
this.lastName = lastName;
|
||||
}
|
||||
|
||||
public String getEmail() {
|
||||
return email;
|
||||
}
|
||||
|
||||
public void setEmail(final String username) {
|
||||
email = username;
|
||||
}
|
||||
|
||||
public int getAge() {
|
||||
return age;
|
||||
}
|
||||
|
||||
public void setAge(final int age) {
|
||||
this.age = age;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
final int prime = 31;
|
||||
int result = 1;
|
||||
result = prime * result + ((email == null) ? 0 : email.hashCode());
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(final Object obj) {
|
||||
if (this == obj)
|
||||
return true;
|
||||
if (obj == null)
|
||||
return false;
|
||||
if (getClass() != obj.getClass())
|
||||
return false;
|
||||
final MyUser user = (MyUser) obj;
|
||||
if (!email.equals(user.email))
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
final StringBuilder builder = new StringBuilder();
|
||||
builder.append("MyUser [firstName=").append(firstName).append("]").append("[lastName=").append(lastName).append("]").append("[username").append(email).append("]");
|
||||
return builder.toString();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,94 @@
|
||||
package org.baeldung.persistence.model;
|
||||
|
||||
import javax.persistence.Entity;
|
||||
import javax.persistence.GeneratedValue;
|
||||
import javax.persistence.GenerationType;
|
||||
import javax.persistence.Id;
|
||||
|
||||
@Entity
|
||||
public class User {
|
||||
|
||||
@Id
|
||||
@GeneratedValue(strategy = GenerationType.AUTO)
|
||||
private Long id;
|
||||
|
||||
private String firstName;
|
||||
|
||||
private String lastName;
|
||||
|
||||
private String email;
|
||||
|
||||
private int age;
|
||||
|
||||
public User() {
|
||||
super();
|
||||
}
|
||||
|
||||
public Long getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public void setId(final Long id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public String getFirstName() {
|
||||
return firstName;
|
||||
}
|
||||
|
||||
public void setFirstName(final String firstName) {
|
||||
this.firstName = firstName;
|
||||
}
|
||||
|
||||
public String getLastName() {
|
||||
return lastName;
|
||||
}
|
||||
|
||||
public void setLastName(final String lastName) {
|
||||
this.lastName = lastName;
|
||||
}
|
||||
|
||||
public String getEmail() {
|
||||
return email;
|
||||
}
|
||||
|
||||
public void setEmail(final String username) {
|
||||
email = username;
|
||||
}
|
||||
|
||||
public int getAge() {
|
||||
return age;
|
||||
}
|
||||
|
||||
public void setAge(final int age) {
|
||||
this.age = age;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
final int prime = 31;
|
||||
int result = 1;
|
||||
result = prime * result + ((email == null) ? 0 : email.hashCode());
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(final Object obj) {
|
||||
if (this == obj)
|
||||
return true;
|
||||
if (obj == null)
|
||||
return false;
|
||||
if (getClass() != obj.getClass())
|
||||
return false;
|
||||
final User user = (User) obj;
|
||||
return email.equals(user.email);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
final StringBuilder builder = new StringBuilder();
|
||||
builder.append("User [firstName=").append(firstName).append("]").append("[lastName=").append(lastName).append("]").append("[username").append(email).append("]");
|
||||
return builder.toString();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
package org.baeldung.persistence.model;
|
||||
|
||||
import javax.persistence.metamodel.SingularAttribute;
|
||||
import javax.persistence.metamodel.StaticMetamodel;
|
||||
|
||||
@StaticMetamodel(User.class)
|
||||
public class User_ {
|
||||
public static volatile SingularAttribute<User, Long> id;
|
||||
public static volatile SingularAttribute<User, String> firstName;
|
||||
public static volatile SingularAttribute<User, String> lastName;
|
||||
public static volatile SingularAttribute<User, Integer> age;
|
||||
public static volatile SingularAttribute<User, String> email;
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
package org.baeldung.spring;
|
||||
|
||||
import javax.servlet.ServletContext;
|
||||
import javax.servlet.ServletException;
|
||||
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
import org.springframework.boot.builder.SpringApplicationBuilder;
|
||||
import org.springframework.boot.web.support.SpringBootServletInitializer;
|
||||
import org.springframework.context.annotation.ComponentScan;
|
||||
import org.springframework.scheduling.annotation.EnableScheduling;
|
||||
import org.springframework.web.context.request.RequestContextListener;
|
||||
|
||||
/**
|
||||
* Main Application Class - uses Spring Boot. Just run this as a normal Java
|
||||
* class to run up a Jetty Server (on http://localhost:8082/spring-rest-query-language)
|
||||
*
|
||||
*/
|
||||
@EnableScheduling
|
||||
@EnableAutoConfiguration
|
||||
@ComponentScan("org.baeldung")
|
||||
@SpringBootApplication
|
||||
public class Application extends SpringBootServletInitializer {
|
||||
|
||||
@Override
|
||||
protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {
|
||||
return application.sources(Application.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStartup(ServletContext sc) throws ServletException {
|
||||
// Manages the lifecycle of the root application context
|
||||
sc.addListener(new RequestContextListener());
|
||||
}
|
||||
|
||||
public static void main(final String[] args) {
|
||||
SpringApplication.run(Application.class, args);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,85 @@
|
||||
package org.baeldung.spring;
|
||||
|
||||
import java.util.Properties;
|
||||
|
||||
import javax.sql.DataSource;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.ComponentScan;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.annotation.PropertySource;
|
||||
import org.springframework.core.env.Environment;
|
||||
import org.springframework.dao.annotation.PersistenceExceptionTranslationPostProcessor;
|
||||
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
|
||||
import org.springframework.jdbc.datasource.DriverManagerDataSource;
|
||||
import org.springframework.orm.jpa.JpaTransactionManager;
|
||||
import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean;
|
||||
import org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter;
|
||||
import org.springframework.transaction.PlatformTransactionManager;
|
||||
import org.springframework.transaction.annotation.EnableTransactionManagement;
|
||||
|
||||
import com.google.common.base.Preconditions;
|
||||
|
||||
@Configuration
|
||||
@EnableTransactionManagement
|
||||
@PropertySource({ "classpath:persistence-${envTarget:h2}.properties" })
|
||||
@ComponentScan({ "org.baeldung.persistence" })
|
||||
// @ImportResource("classpath*:springDataPersistenceConfig.xml")
|
||||
@EnableJpaRepositories(basePackages = "org.baeldung.persistence.dao")
|
||||
public class PersistenceConfig {
|
||||
|
||||
@Autowired
|
||||
private Environment env;
|
||||
|
||||
public PersistenceConfig() {
|
||||
super();
|
||||
}
|
||||
|
||||
@Bean
|
||||
public LocalContainerEntityManagerFactoryBean entityManagerFactory() {
|
||||
final LocalContainerEntityManagerFactoryBean em = new LocalContainerEntityManagerFactoryBean();
|
||||
em.setDataSource(dataSource());
|
||||
em.setPackagesToScan(new String[] { "org.baeldung.persistence.model" });
|
||||
|
||||
final HibernateJpaVendorAdapter vendorAdapter = new HibernateJpaVendorAdapter();
|
||||
// vendorAdapter.set
|
||||
em.setJpaVendorAdapter(vendorAdapter);
|
||||
em.setJpaProperties(additionalProperties());
|
||||
|
||||
return em;
|
||||
}
|
||||
|
||||
@Bean
|
||||
public DataSource dataSource() {
|
||||
final DriverManagerDataSource dataSource = new DriverManagerDataSource();
|
||||
dataSource.setDriverClassName(Preconditions.checkNotNull(env.getProperty("jdbc.driverClassName")));
|
||||
dataSource.setUrl(Preconditions.checkNotNull(env.getProperty("jdbc.url")));
|
||||
dataSource.setUsername(Preconditions.checkNotNull(env.getProperty("jdbc.user")));
|
||||
dataSource.setPassword(Preconditions.checkNotNull(env.getProperty("jdbc.pass")));
|
||||
|
||||
return dataSource;
|
||||
}
|
||||
|
||||
@Bean
|
||||
public PlatformTransactionManager transactionManager() {
|
||||
final JpaTransactionManager transactionManager = new JpaTransactionManager();
|
||||
transactionManager.setEntityManagerFactory(entityManagerFactory().getObject());
|
||||
|
||||
return transactionManager;
|
||||
}
|
||||
|
||||
@Bean
|
||||
public PersistenceExceptionTranslationPostProcessor exceptionTranslation() {
|
||||
return new PersistenceExceptionTranslationPostProcessor();
|
||||
}
|
||||
|
||||
final Properties additionalProperties() {
|
||||
final Properties hibernateProperties = new Properties();
|
||||
hibernateProperties.setProperty("hibernate.hbm2ddl.auto", env.getProperty("hibernate.hbm2ddl.auto"));
|
||||
hibernateProperties.setProperty("hibernate.dialect", env.getProperty("hibernate.dialect"));
|
||||
// hibernateProperties.setProperty("hibernate.globally_quoted_identifiers", "true");
|
||||
return hibernateProperties;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
package org.baeldung.spring;
|
||||
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.ComponentScan;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.web.servlet.ViewResolver;
|
||||
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
|
||||
import org.springframework.web.servlet.config.annotation.ViewControllerRegistry;
|
||||
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;
|
||||
import org.springframework.web.servlet.view.InternalResourceViewResolver;
|
||||
|
||||
@Configuration
|
||||
@ComponentScan("org.baeldung.web")
|
||||
@EnableWebMvc
|
||||
public class WebConfig extends WebMvcConfigurerAdapter {
|
||||
|
||||
public WebConfig() {
|
||||
super();
|
||||
}
|
||||
|
||||
@Bean
|
||||
public ViewResolver viewResolver() {
|
||||
final InternalResourceViewResolver viewResolver = new InternalResourceViewResolver();
|
||||
viewResolver.setPrefix("/WEB-INF/view/");
|
||||
viewResolver.setSuffix(".jsp");
|
||||
return viewResolver;
|
||||
}
|
||||
|
||||
// API
|
||||
@Override
|
||||
public void addViewControllers(final ViewControllerRegistry registry) {
|
||||
super.addViewControllers(registry);
|
||||
registry.addViewController("/homepage.html");
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
package org.baeldung.web.controller;
|
||||
|
||||
import org.springframework.stereotype.Controller;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
|
||||
@Controller
|
||||
@RequestMapping(value = "/")
|
||||
public class HomeController {
|
||||
|
||||
public String index() {
|
||||
return "homepage";
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,171 @@
|
||||
package org.baeldung.web.controller;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import org.baeldung.persistence.dao.GenericSpecificationsBuilder;
|
||||
import org.baeldung.persistence.dao.IUserDAO;
|
||||
import org.baeldung.persistence.dao.MyUserPredicatesBuilder;
|
||||
import org.baeldung.persistence.dao.MyUserRepository;
|
||||
import org.baeldung.persistence.dao.UserRepository;
|
||||
import org.baeldung.persistence.dao.UserSpecification;
|
||||
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.CriteriaParser;
|
||||
import org.baeldung.web.util.SearchCriteria;
|
||||
import org.baeldung.web.util.SearchOperation;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.data.jpa.domain.Specification;
|
||||
import org.springframework.data.querydsl.binding.QuerydslPredicate;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.stereotype.Controller;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMethod;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
import org.springframework.web.bind.annotation.ResponseBody;
|
||||
import org.springframework.web.bind.annotation.ResponseStatus;
|
||||
|
||||
import com.google.common.base.Joiner;
|
||||
import com.google.common.base.Preconditions;
|
||||
import com.querydsl.core.types.Predicate;
|
||||
import com.querydsl.core.types.dsl.BooleanExpression;
|
||||
|
||||
import cz.jirutka.rsql.parser.RSQLParser;
|
||||
import cz.jirutka.rsql.parser.ast.Node;
|
||||
|
||||
//@EnableSpringDataWebSupport
|
||||
@Controller
|
||||
@RequestMapping(value = "/auth/")
|
||||
public class UserController {
|
||||
|
||||
@Autowired
|
||||
private IUserDAO service;
|
||||
|
||||
@Autowired
|
||||
private UserRepository dao;
|
||||
|
||||
@Autowired
|
||||
private MyUserRepository myUserRepository;
|
||||
|
||||
public UserController() {
|
||||
super();
|
||||
}
|
||||
|
||||
// API - READ
|
||||
|
||||
@RequestMapping(method = RequestMethod.GET, value = "/users")
|
||||
@ResponseBody
|
||||
public List<User> findAll(@RequestParam(value = "search", required = false) String search) {
|
||||
List<SearchCriteria> params = new ArrayList<SearchCriteria>();
|
||||
if (search != null) {
|
||||
Pattern pattern = Pattern.compile("(\\w+?)(:|<|>)(\\w+?),");
|
||||
Matcher matcher = pattern.matcher(search + ",");
|
||||
while (matcher.find()) {
|
||||
params.add(new SearchCriteria(matcher.group(1), matcher.group(2), matcher.group(3)));
|
||||
}
|
||||
}
|
||||
return service.searchUser(params);
|
||||
}
|
||||
|
||||
@RequestMapping(method = RequestMethod.GET, value = "/users/spec")
|
||||
@ResponseBody
|
||||
public List<User> findAllBySpecification(@RequestParam(value = "search") String search) {
|
||||
UserSpecificationsBuilder builder = new UserSpecificationsBuilder();
|
||||
String operationSetExper = Joiner.on("|")
|
||||
.join(SearchOperation.SIMPLE_OPERATION_SET);
|
||||
Pattern pattern = Pattern.compile("(\\w+?)(" + operationSetExper + ")(\\p{Punct}?)(\\w+?)(\\p{Punct}?),");
|
||||
Matcher matcher = pattern.matcher(search + ",");
|
||||
while (matcher.find()) {
|
||||
builder.with(matcher.group(1), matcher.group(2), matcher.group(4), matcher.group(3), matcher.group(5));
|
||||
}
|
||||
|
||||
Specification<User> spec = builder.build();
|
||||
return dao.findAll(spec);
|
||||
}
|
||||
|
||||
@GetMapping(value = "/users/espec")
|
||||
@ResponseBody
|
||||
public List<User> findAllByOrPredicate(@RequestParam(value = "search") String search) {
|
||||
Specification<User> spec = resolveSpecification(search);
|
||||
return dao.findAll(spec);
|
||||
}
|
||||
|
||||
@GetMapping(value = "/users/spec/adv")
|
||||
@ResponseBody
|
||||
public List<User> findAllByAdvPredicate(@RequestParam(value = "search") String search) {
|
||||
Specification<User> spec = resolveSpecificationFromInfixExpr(search);
|
||||
return dao.findAll(spec);
|
||||
}
|
||||
|
||||
protected Specification<User> resolveSpecificationFromInfixExpr(String searchParameters) {
|
||||
CriteriaParser parser = new CriteriaParser();
|
||||
GenericSpecificationsBuilder<User> specBuilder = new GenericSpecificationsBuilder<>();
|
||||
return specBuilder.build(parser.parse(searchParameters), UserSpecification::new);
|
||||
}
|
||||
|
||||
protected Specification<User> resolveSpecification(String searchParameters) {
|
||||
|
||||
UserSpecificationsBuilder builder = new UserSpecificationsBuilder();
|
||||
String operationSetExper = Joiner.on("|")
|
||||
.join(SearchOperation.SIMPLE_OPERATION_SET);
|
||||
Pattern pattern = Pattern.compile("(\\p{Punct}?)(\\w+?)(" + operationSetExper + ")(\\p{Punct}?)(\\w+?)(\\p{Punct}?),");
|
||||
Matcher matcher = pattern.matcher(searchParameters + ",");
|
||||
while (matcher.find()) {
|
||||
builder.with(matcher.group(1), matcher.group(2), matcher.group(3), matcher.group(5), matcher.group(4), matcher.group(6));
|
||||
}
|
||||
return builder.build();
|
||||
}
|
||||
|
||||
@RequestMapping(method = RequestMethod.GET, value = "/myusers")
|
||||
@ResponseBody
|
||||
public Iterable<MyUser> findAllByQuerydsl(@RequestParam(value = "search") String search) {
|
||||
MyUserPredicatesBuilder builder = new MyUserPredicatesBuilder();
|
||||
if (search != null) {
|
||||
Pattern pattern = Pattern.compile("(\\w+?)(:|<|>)(\\w+?),");
|
||||
Matcher matcher = pattern.matcher(search + ",");
|
||||
while (matcher.find()) {
|
||||
builder.with(matcher.group(1), matcher.group(2), matcher.group(3));
|
||||
}
|
||||
}
|
||||
BooleanExpression exp = builder.build();
|
||||
return myUserRepository.findAll(exp);
|
||||
}
|
||||
|
||||
@RequestMapping(method = RequestMethod.GET, value = "/users/rsql")
|
||||
@ResponseBody
|
||||
public List<User> findAllByRsql(@RequestParam(value = "search") String search) {
|
||||
Node rootNode = new RSQLParser().parse(search);
|
||||
Specification<User> spec = rootNode.accept(new CustomRsqlVisitor<User>());
|
||||
return dao.findAll(spec);
|
||||
}
|
||||
|
||||
@RequestMapping(method = RequestMethod.GET, value = "/api/myusers")
|
||||
@ResponseBody
|
||||
public Iterable<MyUser> findAllByWebQuerydsl(@QuerydslPredicate(root = MyUser.class) Predicate predicate) {
|
||||
return myUserRepository.findAll(predicate);
|
||||
}
|
||||
|
||||
// API - WRITE
|
||||
|
||||
@RequestMapping(method = RequestMethod.POST, value = "/users")
|
||||
@ResponseStatus(HttpStatus.CREATED)
|
||||
public void create(@RequestBody User resource) {
|
||||
Preconditions.checkNotNull(resource);
|
||||
dao.save(resource);
|
||||
}
|
||||
|
||||
@RequestMapping(method = RequestMethod.POST, value = "/myusers")
|
||||
@ResponseStatus(HttpStatus.CREATED)
|
||||
public void addMyUser(@RequestBody MyUser resource) {
|
||||
Preconditions.checkNotNull(resource);
|
||||
myUserRepository.save(resource);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,84 @@
|
||||
package org.baeldung.web.error;
|
||||
|
||||
import javax.persistence.EntityNotFoundException;
|
||||
|
||||
import org.baeldung.web.exception.MyResourceNotFoundException;
|
||||
import org.hibernate.exception.ConstraintViolationException;
|
||||
import org.springframework.dao.DataAccessException;
|
||||
import org.springframework.dao.DataIntegrityViolationException;
|
||||
import org.springframework.dao.InvalidDataAccessApiUsageException;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.http.converter.HttpMessageNotReadableException;
|
||||
import org.springframework.web.bind.MethodArgumentNotValidException;
|
||||
import org.springframework.web.bind.annotation.ControllerAdvice;
|
||||
import org.springframework.web.bind.annotation.ExceptionHandler;
|
||||
import org.springframework.web.context.request.WebRequest;
|
||||
import org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler;
|
||||
|
||||
@ControllerAdvice
|
||||
public class RestResponseEntityExceptionHandler extends ResponseEntityExceptionHandler {
|
||||
|
||||
public RestResponseEntityExceptionHandler() {
|
||||
super();
|
||||
}
|
||||
|
||||
// API
|
||||
|
||||
// 400
|
||||
|
||||
@ExceptionHandler({ ConstraintViolationException.class })
|
||||
public ResponseEntity<Object> handleBadRequest(final ConstraintViolationException ex, final WebRequest request) {
|
||||
final String bodyOfResponse = "This should be application specific";
|
||||
return handleExceptionInternal(ex, bodyOfResponse, new HttpHeaders(), HttpStatus.BAD_REQUEST, request);
|
||||
}
|
||||
|
||||
@ExceptionHandler({ DataIntegrityViolationException.class })
|
||||
public ResponseEntity<Object> handleBadRequest(final DataIntegrityViolationException ex, final WebRequest request) {
|
||||
final String bodyOfResponse = "This should be application specific";
|
||||
return handleExceptionInternal(ex, bodyOfResponse, new HttpHeaders(), HttpStatus.BAD_REQUEST, request);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected ResponseEntity<Object> handleHttpMessageNotReadable(final HttpMessageNotReadableException ex, final HttpHeaders headers, final HttpStatus status, final WebRequest request) {
|
||||
final String bodyOfResponse = "This should be application specific";
|
||||
// ex.getCause() instanceof JsonMappingException, JsonParseException // for additional information later on
|
||||
return handleExceptionInternal(ex, bodyOfResponse, headers, HttpStatus.BAD_REQUEST, request);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected ResponseEntity<Object> handleMethodArgumentNotValid(final MethodArgumentNotValidException ex, final HttpHeaders headers, final HttpStatus status, final WebRequest request) {
|
||||
final String bodyOfResponse = "This should be application specific";
|
||||
return handleExceptionInternal(ex, bodyOfResponse, headers, HttpStatus.BAD_REQUEST, request);
|
||||
}
|
||||
|
||||
|
||||
// 404
|
||||
|
||||
@ExceptionHandler(value = { EntityNotFoundException.class, MyResourceNotFoundException.class })
|
||||
protected ResponseEntity<Object> handleNotFound(final RuntimeException ex, final WebRequest request) {
|
||||
final String bodyOfResponse = "This should be application specific";
|
||||
return handleExceptionInternal(ex, bodyOfResponse, new HttpHeaders(), HttpStatus.NOT_FOUND, request);
|
||||
}
|
||||
|
||||
// 409
|
||||
|
||||
@ExceptionHandler({ InvalidDataAccessApiUsageException.class, DataAccessException.class })
|
||||
protected ResponseEntity<Object> handleConflict(final RuntimeException ex, final WebRequest request) {
|
||||
final String bodyOfResponse = "This should be application specific";
|
||||
return handleExceptionInternal(ex, bodyOfResponse, new HttpHeaders(), HttpStatus.CONFLICT, request);
|
||||
}
|
||||
|
||||
// 412
|
||||
|
||||
// 500
|
||||
|
||||
@ExceptionHandler({ NullPointerException.class, IllegalArgumentException.class, IllegalStateException.class })
|
||||
/*500*/public ResponseEntity<Object> handleInternal(final RuntimeException ex, final WebRequest request) {
|
||||
logger.error("500 Status Code", ex);
|
||||
final String bodyOfResponse = "This should be application specific";
|
||||
return handleExceptionInternal(ex, bodyOfResponse, new HttpHeaders(), HttpStatus.INTERNAL_SERVER_ERROR, request);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
package org.baeldung.web.exception;
|
||||
|
||||
public final class MyResourceNotFoundException extends RuntimeException {
|
||||
|
||||
public MyResourceNotFoundException() {
|
||||
super();
|
||||
}
|
||||
|
||||
public MyResourceNotFoundException(final String message, final Throwable cause) {
|
||||
super(message, cause);
|
||||
}
|
||||
|
||||
public MyResourceNotFoundException(final String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
public MyResourceNotFoundException(final Throwable cause) {
|
||||
super(cause);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
package org.baeldung.web.util;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.Deque;
|
||||
import java.util.HashMap;
|
||||
import java.util.LinkedList;
|
||||
import java.util.Map;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import com.google.common.base.Joiner;
|
||||
|
||||
public class CriteriaParser {
|
||||
|
||||
private static Map<String, Operator> ops;
|
||||
|
||||
private static Pattern SpecCriteraRegex = Pattern.compile("^(\\w+?)(" + Joiner.on("|")
|
||||
.join(SearchOperation.SIMPLE_OPERATION_SET) + ")(\\p{Punct}?)(\\w+?)(\\p{Punct}?)$");
|
||||
|
||||
private enum Operator {
|
||||
OR(1), AND(2);
|
||||
final int precedence;
|
||||
|
||||
Operator(int p) {
|
||||
precedence = p;
|
||||
}
|
||||
}
|
||||
|
||||
static {
|
||||
Map<String, Operator> tempMap = new HashMap<>();
|
||||
tempMap.put("AND", Operator.AND);
|
||||
tempMap.put("OR", Operator.OR);
|
||||
tempMap.put("or", Operator.OR);
|
||||
tempMap.put("and", Operator.AND);
|
||||
|
||||
ops = Collections.unmodifiableMap(tempMap);
|
||||
}
|
||||
|
||||
private static boolean isHigerPrecedenceOperator(String currOp, String prevOp) {
|
||||
return (ops.containsKey(prevOp) && ops.get(prevOp).precedence >= ops.get(currOp).precedence);
|
||||
}
|
||||
|
||||
public Deque<?> parse(String searchParam) {
|
||||
|
||||
Deque<Object> output = new LinkedList<>();
|
||||
Deque<String> stack = new LinkedList<>();
|
||||
|
||||
for (String token : searchParam.split("\\s+")) {
|
||||
if (ops.containsKey(token)) {
|
||||
while (!stack.isEmpty() && isHigerPrecedenceOperator(token, stack.peek()))
|
||||
output.push(stack.pop()
|
||||
.equalsIgnoreCase(SearchOperation.OR_OPERATOR) ? SearchOperation.OR_OPERATOR : SearchOperation.AND_OPERATOR);
|
||||
stack.push(token.equalsIgnoreCase(SearchOperation.OR_OPERATOR) ? SearchOperation.OR_OPERATOR : SearchOperation.AND_OPERATOR);
|
||||
} else if (token.equals(SearchOperation.LEFT_PARANTHESIS)) {
|
||||
stack.push(SearchOperation.LEFT_PARANTHESIS);
|
||||
} else if (token.equals(SearchOperation.RIGHT_PARANTHESIS)) {
|
||||
while (!stack.peek()
|
||||
.equals(SearchOperation.LEFT_PARANTHESIS))
|
||||
output.push(stack.pop());
|
||||
stack.pop();
|
||||
} else {
|
||||
|
||||
Matcher matcher = SpecCriteraRegex.matcher(token);
|
||||
while (matcher.find()) {
|
||||
output.push(new SpecSearchCriteria(matcher.group(1), matcher.group(2), matcher.group(3), matcher.group(4), matcher.group(5)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
while (!stack.isEmpty())
|
||||
output.push(stack.pop());
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
package org.baeldung.web.util;
|
||||
|
||||
public class SearchCriteria {
|
||||
|
||||
private String key;
|
||||
private String operation;
|
||||
private Object value;
|
||||
|
||||
public SearchCriteria() {
|
||||
|
||||
}
|
||||
|
||||
public SearchCriteria(final String key, final String operation, final Object value) {
|
||||
super();
|
||||
this.key = key;
|
||||
this.operation = operation;
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
public String getKey() {
|
||||
return key;
|
||||
}
|
||||
|
||||
public void setKey(final String key) {
|
||||
this.key = key;
|
||||
}
|
||||
|
||||
public String getOperation() {
|
||||
return operation;
|
||||
}
|
||||
|
||||
public void setOperation(final String operation) {
|
||||
this.operation = operation;
|
||||
}
|
||||
|
||||
public Object getValue() {
|
||||
return value;
|
||||
}
|
||||
|
||||
public void setValue(final Object value) {
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
package org.baeldung.web.util;
|
||||
|
||||
public enum SearchOperation {
|
||||
EQUALITY, NEGATION, GREATER_THAN, LESS_THAN, LIKE, STARTS_WITH, ENDS_WITH, CONTAINS;
|
||||
|
||||
public static final String[] SIMPLE_OPERATION_SET = { ":", "!", ">", "<", "~" };
|
||||
|
||||
public static final String OR_PREDICATE_FLAG = "'";
|
||||
|
||||
public static final String ZERO_OR_MORE_REGEX = "*";
|
||||
|
||||
public static final String OR_OPERATOR = "OR";
|
||||
|
||||
public static final String AND_OPERATOR = "AND";
|
||||
|
||||
public static final String LEFT_PARANTHESIS = "(";
|
||||
|
||||
public static final String RIGHT_PARANTHESIS = ")";
|
||||
|
||||
public static SearchOperation getSimpleOperation(final char input) {
|
||||
switch (input) {
|
||||
case ':':
|
||||
return EQUALITY;
|
||||
case '!':
|
||||
return NEGATION;
|
||||
case '>':
|
||||
return GREATER_THAN;
|
||||
case '<':
|
||||
return LESS_THAN;
|
||||
case '~':
|
||||
return LIKE;
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,82 @@
|
||||
package org.baeldung.web.util;
|
||||
|
||||
public class SpecSearchCriteria {
|
||||
|
||||
private String key;
|
||||
private SearchOperation operation;
|
||||
private Object value;
|
||||
private boolean orPredicate;
|
||||
|
||||
public SpecSearchCriteria() {
|
||||
|
||||
}
|
||||
|
||||
public SpecSearchCriteria(final String key, final SearchOperation operation, final Object value) {
|
||||
super();
|
||||
this.key = key;
|
||||
this.operation = operation;
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
public SpecSearchCriteria(final String orPredicate, final String key, final SearchOperation operation, final Object value) {
|
||||
super();
|
||||
this.orPredicate = orPredicate != null && orPredicate.equals(SearchOperation.OR_PREDICATE_FLAG);
|
||||
this.key = key;
|
||||
this.operation = operation;
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
public SpecSearchCriteria(String key, String operation, String prefix, String value, String suffix) {
|
||||
SearchOperation op = SearchOperation.getSimpleOperation(operation.charAt(0));
|
||||
if (op != null) {
|
||||
if (op == SearchOperation.EQUALITY) { // the operation may be complex operation
|
||||
final boolean startWithAsterisk = prefix != null && prefix.contains(SearchOperation.ZERO_OR_MORE_REGEX);
|
||||
final boolean endWithAsterisk = suffix != null && suffix.contains(SearchOperation.ZERO_OR_MORE_REGEX);
|
||||
|
||||
if (startWithAsterisk && endWithAsterisk) {
|
||||
op = SearchOperation.CONTAINS;
|
||||
} else if (startWithAsterisk) {
|
||||
op = SearchOperation.ENDS_WITH;
|
||||
} else if (endWithAsterisk) {
|
||||
op = SearchOperation.STARTS_WITH;
|
||||
}
|
||||
}
|
||||
}
|
||||
this.key = key;
|
||||
this.operation = op;
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
public String getKey() {
|
||||
return key;
|
||||
}
|
||||
|
||||
public void setKey(final String key) {
|
||||
this.key = key;
|
||||
}
|
||||
|
||||
public SearchOperation getOperation() {
|
||||
return operation;
|
||||
}
|
||||
|
||||
public void setOperation(final SearchOperation operation) {
|
||||
this.operation = operation;
|
||||
}
|
||||
|
||||
public Object getValue() {
|
||||
return value;
|
||||
}
|
||||
|
||||
public void setValue(final Object value) {
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
public boolean isOrPredicate() {
|
||||
return orPredicate;
|
||||
}
|
||||
|
||||
public void setOrPredicate(boolean orPredicate) {
|
||||
this.orPredicate = orPredicate;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
server.port=8082
|
||||
server.context-path=/spring-rest-query-language
|
||||
5
spring-rest-query-language/src/main/resources/data.sql
Normal file
5
spring-rest-query-language/src/main/resources/data.sql
Normal file
@@ -0,0 +1,5 @@
|
||||
insert into User (id, firstName, lastName, email, age) values (1, 'john', 'doe', 'john@doe.com', 22);
|
||||
insert into User (id, firstName, lastName, email, age) values (2, 'tom', 'doe', 'tom@doe.com', 26);
|
||||
|
||||
insert into MyUser (id, firstName, lastName, email, age) values (1, 'john', 'doe', 'john@doe.com', 22);
|
||||
insert into MyUser (id, firstName, lastName, email, age) values (2, 'tom', 'doe', 'tom@doe.com', 26);
|
||||
19
spring-rest-query-language/src/main/resources/logback.xml
Normal file
19
spring-rest-query-language/src/main/resources/logback.xml
Normal file
@@ -0,0 +1,19 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<configuration>
|
||||
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
|
||||
<encoder>
|
||||
<pattern>web - %date [%thread] %-5level %logger{36} - %message%n
|
||||
</pattern>
|
||||
</encoder>
|
||||
</appender>
|
||||
|
||||
<logger name="org.springframework" level="WARN" />
|
||||
<logger name="org.springframework.transaction" level="WARN" />
|
||||
|
||||
<!-- in order to debug some marshalling issues, this needs to be TRACE -->
|
||||
<logger name="org.springframework.web.servlet.mvc" level="WARN" />
|
||||
|
||||
<root level="INFO">
|
||||
<appender-ref ref="STDOUT" />
|
||||
</root>
|
||||
</configuration>
|
||||
@@ -0,0 +1,22 @@
|
||||
## jdbc.X
|
||||
#jdbc.driverClassName=com.mysql.jdbc.Driver
|
||||
#jdbc.url=jdbc:mysql://localhost:3306/spring_hibernate4_01?createDatabaseIfNotExist=true
|
||||
#jdbc.user=tutorialuser
|
||||
#jdbc.pass=tutorialmy5ql
|
||||
#
|
||||
## hibernate.X
|
||||
#hibernate.dialect=org.hibernate.dialect.MySQL5Dialect
|
||||
#hibernate.show_sql=false
|
||||
#hibernate.hbm2ddl.auto=create-drop
|
||||
|
||||
|
||||
# jdbc.X
|
||||
jdbc.driverClassName=org.h2.Driver
|
||||
jdbc.url=jdbc:h2:mem:security_permission;DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=FALSE
|
||||
jdbc.user=sa
|
||||
jdbc.pass=
|
||||
|
||||
# hibernate.X
|
||||
hibernate.dialect=org.hibernate.dialect.H2Dialect
|
||||
hibernate.show_sql=false
|
||||
hibernate.hbm2ddl.auto=create-drop
|
||||
@@ -0,0 +1,10 @@
|
||||
# jdbc.X
|
||||
jdbc.driverClassName=com.mysql.jdbc.Driver
|
||||
jdbc.url=jdbc:mysql://localhost:3306/spring_hibernate4_01?createDatabaseIfNotExist=true
|
||||
jdbc.user=tutorialuser
|
||||
jdbc.pass=tutorialmy5ql
|
||||
|
||||
# hibernate.X
|
||||
hibernate.dialect=org.hibernate.dialect.MySQL5Dialect
|
||||
hibernate.show_sql=false
|
||||
hibernate.hbm2ddl.auto=create-drop
|
||||
@@ -0,0 +1,12 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:jpa="http://www.springframework.org/schema/data/jpa"
|
||||
xsi:schemaLocation="
|
||||
http://www.springframework.org/schema/beans
|
||||
http://www.springframework.org/schema/beans/spring-beans.xsd
|
||||
http://www.springframework.org/schema/data/jpa
|
||||
http://www.springframework.org/schema/data/jpa/spring-jpa.xsd"
|
||||
>
|
||||
|
||||
<jpa:repositories base-package="org.baeldung.persistence.dao"/>
|
||||
|
||||
</beans>
|
||||
@@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<beans xmlns="http://www.springframework.org/schema/beans"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.2.xsd" >
|
||||
|
||||
</beans>
|
||||
@@ -0,0 +1,7 @@
|
||||
<html>
|
||||
<head></head>
|
||||
|
||||
<body>
|
||||
<h1>This is the body of the sample view</h1>
|
||||
</body>
|
||||
</html>
|
||||
42
spring-rest-query-language/src/main/webapp/WEB-INF/web.xml
Normal file
42
spring-rest-query-language/src/main/webapp/WEB-INF/web.xml
Normal file
@@ -0,0 +1,42 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://java.sun.com/xml/ns/javaee" xmlns:web="http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
|
||||
xsi:schemaLocation="
|
||||
http://java.sun.com/xml/ns/javaee
|
||||
http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd" id="WebApp_ID" version="3.0"
|
||||
>
|
||||
|
||||
<display-name>Spring REST Query language Application</display-name>
|
||||
|
||||
<!-- Spring root -->
|
||||
<context-param>
|
||||
<param-name>contextClass</param-name>
|
||||
<param-value>
|
||||
org.springframework.web.context.support.AnnotationConfigWebApplicationContext
|
||||
</param-value>
|
||||
</context-param>
|
||||
<context-param>
|
||||
<param-name>contextConfigLocation</param-name>
|
||||
<param-value>org.baeldung.spring</param-value>
|
||||
</context-param>
|
||||
|
||||
<listener>
|
||||
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
|
||||
</listener>
|
||||
|
||||
<!-- Spring child -->
|
||||
<servlet>
|
||||
<servlet-name>api</servlet-name>
|
||||
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
|
||||
<load-on-startup>1</load-on-startup>
|
||||
</servlet>
|
||||
<servlet-mapping>
|
||||
<servlet-name>api</servlet-name>
|
||||
<url-pattern>/</url-pattern>
|
||||
</servlet-mapping>
|
||||
|
||||
|
||||
<welcome-file-list>
|
||||
<welcome-file>index.html</welcome-file>
|
||||
</welcome-file-list>
|
||||
|
||||
</web-app>
|
||||
@@ -0,0 +1,108 @@
|
||||
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.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import org.baeldung.persistence.dao.IUserDAO;
|
||||
import org.baeldung.persistence.model.User;
|
||||
import org.baeldung.spring.PersistenceConfig;
|
||||
import org.baeldung.web.util.SearchCriteria;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.test.annotation.Rollback;
|
||||
import org.springframework.test.context.ContextConfiguration;
|
||||
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
@RunWith(SpringJUnit4ClassRunner.class)
|
||||
@ContextConfiguration(classes = { PersistenceConfig.class })
|
||||
@Transactional
|
||||
@Rollback
|
||||
public class JPACriteriaQueryIntegrationTest {
|
||||
|
||||
@Autowired
|
||||
private IUserDAO userApi;
|
||||
|
||||
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);
|
||||
userApi.save(userJohn);
|
||||
|
||||
userTom = new User();
|
||||
userTom.setFirstName("tom");
|
||||
userTom.setLastName("doe");
|
||||
userTom.setEmail("tom@doe.com");
|
||||
userTom.setAge(26);
|
||||
userApi.save(userTom);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void givenFirstAndLastName_whenGettingListOfUsers_thenCorrect() {
|
||||
final List<SearchCriteria> params = new ArrayList<SearchCriteria>();
|
||||
params.add(new SearchCriteria("firstName", ":", "john"));
|
||||
params.add(new SearchCriteria("lastName", ":", "doe"));
|
||||
|
||||
final List<User> results = userApi.searchUser(params);
|
||||
|
||||
assertThat(userJohn, isIn(results));
|
||||
assertThat(userTom, not(isIn(results)));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void givenLast_whenGettingListOfUsers_thenCorrect() {
|
||||
final List<SearchCriteria> params = new ArrayList<SearchCriteria>();
|
||||
params.add(new SearchCriteria("lastName", ":", "doe"));
|
||||
|
||||
final List<User> results = userApi.searchUser(params);
|
||||
assertThat(userJohn, isIn(results));
|
||||
assertThat(userTom, isIn(results));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void givenLastAndAge_whenGettingListOfUsers_thenCorrect() {
|
||||
final List<SearchCriteria> params = new ArrayList<SearchCriteria>();
|
||||
params.add(new SearchCriteria("lastName", ":", "doe"));
|
||||
params.add(new SearchCriteria("age", ">", "25"));
|
||||
|
||||
final List<User> results = userApi.searchUser(params);
|
||||
|
||||
assertThat(userTom, isIn(results));
|
||||
assertThat(userJohn, not(isIn(results)));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void givenWrongFirstAndLast_whenGettingListOfUsers_thenCorrect() {
|
||||
final List<SearchCriteria> params = new ArrayList<SearchCriteria>();
|
||||
params.add(new SearchCriteria("firstName", ":", "adam"));
|
||||
params.add(new SearchCriteria("lastName", ":", "fox"));
|
||||
|
||||
final List<User> results = userApi.searchUser(params);
|
||||
assertThat(userJohn, not(isIn(results)));
|
||||
assertThat(userTom, not(isIn(results)));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void givenPartialFirst_whenGettingListOfUsers_thenCorrect() {
|
||||
final List<SearchCriteria> params = new ArrayList<SearchCriteria>();
|
||||
params.add(new SearchCriteria("firstName", ":", "jo"));
|
||||
|
||||
final List<User> results = userApi.searchUser(params);
|
||||
|
||||
assertThat(userJohn, isIn(results));
|
||||
assertThat(userTom, not(isIn(results)));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,97 @@
|
||||
package org.baeldung.persistence.query;
|
||||
|
||||
import static org.hamcrest.MatcherAssert.assertThat;
|
||||
import static org.hamcrest.collection.IsEmptyIterable.emptyIterable;
|
||||
import static org.hamcrest.collection.IsIterableContainingInAnyOrder.containsInAnyOrder;
|
||||
import static org.hamcrest.collection.IsIterableContainingInOrder.contains;
|
||||
import static org.hamcrest.core.IsNot.not;
|
||||
|
||||
import org.baeldung.persistence.dao.MyUserPredicatesBuilder;
|
||||
import org.baeldung.persistence.dao.MyUserRepository;
|
||||
import org.baeldung.persistence.model.MyUser;
|
||||
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.test.annotation.Rollback;
|
||||
import org.springframework.test.context.ContextConfiguration;
|
||||
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
@RunWith(SpringJUnit4ClassRunner.class)
|
||||
@ContextConfiguration(classes = { PersistenceConfig.class })
|
||||
@Transactional
|
||||
@Rollback
|
||||
public class JPAQuerydslIntegrationTest {
|
||||
|
||||
@Autowired
|
||||
private MyUserRepository repo;
|
||||
|
||||
private MyUser userJohn;
|
||||
|
||||
private MyUser userTom;
|
||||
|
||||
@Before
|
||||
public void init() {
|
||||
userJohn = new MyUser();
|
||||
userJohn.setFirstName("john");
|
||||
userJohn.setLastName("doe");
|
||||
userJohn.setEmail("john@doe.com");
|
||||
userJohn.setAge(22);
|
||||
repo.save(userJohn);
|
||||
|
||||
userTom = new MyUser();
|
||||
userTom.setFirstName("tom");
|
||||
userTom.setLastName("doe");
|
||||
userTom.setEmail("tom@doe.com");
|
||||
userTom.setAge(26);
|
||||
repo.save(userTom);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void givenLast_whenGettingListOfUsers_thenCorrect() {
|
||||
final MyUserPredicatesBuilder builder = new MyUserPredicatesBuilder().with("lastName", ":", "doe");
|
||||
|
||||
final Iterable<MyUser> results = repo.findAll(builder.build());
|
||||
assertThat(results, containsInAnyOrder(userJohn, userTom));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void givenFirstAndLastName_whenGettingListOfUsers_thenCorrect() {
|
||||
final MyUserPredicatesBuilder builder = new MyUserPredicatesBuilder().with("firstName", ":", "john").with("lastName", ":", "doe");
|
||||
|
||||
final Iterable<MyUser> results = repo.findAll(builder.build());
|
||||
|
||||
assertThat(results, contains(userJohn));
|
||||
assertThat(results, not(contains(userTom)));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void givenLastAndAge_whenGettingListOfUsers_thenCorrect() {
|
||||
final MyUserPredicatesBuilder builder = new MyUserPredicatesBuilder().with("lastName", ":", "doe").with("age", ">", "25");
|
||||
|
||||
final Iterable<MyUser> results = repo.findAll(builder.build());
|
||||
|
||||
assertThat(results, contains(userTom));
|
||||
assertThat(results, not(contains(userJohn)));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void givenWrongFirstAndLast_whenGettingListOfUsers_thenCorrect() {
|
||||
final MyUserPredicatesBuilder builder = new MyUserPredicatesBuilder().with("firstName", ":", "adam").with("lastName", ":", "fox");
|
||||
|
||||
final Iterable<MyUser> results = repo.findAll(builder.build());
|
||||
assertThat(results, emptyIterable());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void givenPartialFirst_whenGettingListOfUsers_thenCorrect() {
|
||||
final MyUserPredicatesBuilder builder = new MyUserPredicatesBuilder().with("firstName", ":", "jo");
|
||||
|
||||
final Iterable<MyUser> results = repo.findAll(builder.build());
|
||||
|
||||
assertThat(results, contains(userJohn));
|
||||
assertThat(results, not(contains(userTom)));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,180 @@
|
||||
package org.baeldung.persistence.query;
|
||||
|
||||
import org.baeldung.persistence.dao.GenericSpecificationsBuilder;
|
||||
import org.baeldung.persistence.dao.UserRepository;
|
||||
import org.baeldung.persistence.dao.UserSpecification;
|
||||
import org.baeldung.persistence.dao.UserSpecificationsBuilder;
|
||||
import org.baeldung.persistence.model.User;
|
||||
import org.baeldung.spring.PersistenceConfig;
|
||||
import org.baeldung.web.util.CriteriaParser;
|
||||
import org.baeldung.web.util.SearchOperation;
|
||||
import org.baeldung.web.util.SpecSearchCriteria;
|
||||
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.data.jpa.domain.Specifications;
|
||||
import org.springframework.test.annotation.Rollback;
|
||||
import org.springframework.test.context.ContextConfiguration;
|
||||
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.function.Function;
|
||||
|
||||
import static org.hamcrest.MatcherAssert.assertThat;
|
||||
import static org.hamcrest.collection.IsCollectionWithSize.hasSize;
|
||||
import static org.hamcrest.collection.IsIn.isIn;
|
||||
import static org.hamcrest.core.IsNot.not;
|
||||
|
||||
@RunWith(SpringJUnit4ClassRunner.class)
|
||||
@ContextConfiguration(classes = { PersistenceConfig.class })
|
||||
@Transactional
|
||||
@Rollback
|
||||
public class JPASpecificationIntegrationTest {
|
||||
|
||||
@Autowired
|
||||
private UserRepository repository;
|
||||
|
||||
private User userJohn;
|
||||
|
||||
private User userTom;
|
||||
|
||||
private User userPercy;
|
||||
|
||||
@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);
|
||||
|
||||
userPercy = new User();
|
||||
userPercy.setFirstName("percy");
|
||||
userPercy.setLastName("blackney");
|
||||
userPercy.setEmail("percy@blackney.com");
|
||||
userPercy.setAge(30);
|
||||
repository.save(userPercy);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void givenFirstAndLastName_whenGettingListOfUsers_thenCorrect() {
|
||||
final UserSpecification spec = new UserSpecification(new SpecSearchCriteria("firstName", SearchOperation.EQUALITY, "john"));
|
||||
final UserSpecification spec1 = new UserSpecification(new SpecSearchCriteria("lastName", SearchOperation.EQUALITY, "doe"));
|
||||
final List<User> results = repository.findAll(Specifications
|
||||
.where(spec)
|
||||
.and(spec1));
|
||||
|
||||
assertThat(userJohn, isIn(results));
|
||||
assertThat(userTom, not(isIn(results)));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void givenFirstOrLastName_whenGettingListOfUsers_thenCorrect() {
|
||||
UserSpecificationsBuilder builder = new UserSpecificationsBuilder();
|
||||
|
||||
SpecSearchCriteria spec = new SpecSearchCriteria("firstName", SearchOperation.EQUALITY, "john");
|
||||
SpecSearchCriteria spec1 = new SpecSearchCriteria("'","lastName", SearchOperation.EQUALITY, "doe");
|
||||
|
||||
List<User> results = repository.findAll(builder
|
||||
.with(spec)
|
||||
.with(spec1)
|
||||
.build());
|
||||
|
||||
assertThat(results, hasSize(2));
|
||||
assertThat(userJohn, isIn(results));
|
||||
assertThat(userTom, isIn(results));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void givenFirstOrLastNameAndAgeGenericBuilder_whenGettingListOfUsers_thenCorrect() {
|
||||
GenericSpecificationsBuilder<User> builder = new GenericSpecificationsBuilder<>();
|
||||
Function<SpecSearchCriteria, Specification<User>> converter = UserSpecification::new;
|
||||
|
||||
CriteriaParser parser=new CriteriaParser();
|
||||
List<User> results = repository.findAll(builder.build(parser.parse("( lastName:doe OR firstName:john ) AND age:22"), converter));
|
||||
|
||||
assertThat(results, hasSize(1));
|
||||
assertThat(userJohn, isIn(results));
|
||||
assertThat(userTom, not(isIn(results)));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void givenFirstOrLastNameGenericBuilder_whenGettingListOfUsers_thenCorrect() {
|
||||
GenericSpecificationsBuilder<User> builder = new GenericSpecificationsBuilder<>();
|
||||
Function<SpecSearchCriteria, Specification<User>> converter = UserSpecification::new;
|
||||
|
||||
builder.with("firstName", ":", "john", null, null);
|
||||
builder.with("'", "lastName", ":", "doe", null, null);
|
||||
|
||||
List<User> results = repository.findAll(builder.build(converter));
|
||||
|
||||
assertThat(results, hasSize(2));
|
||||
assertThat(userJohn, isIn(results));
|
||||
assertThat(userTom, isIn(results));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void givenFirstNameInverse_whenGettingListOfUsers_thenCorrect() {
|
||||
final UserSpecification spec = new UserSpecification(new SpecSearchCriteria("firstName", SearchOperation.NEGATION, "john"));
|
||||
final List<User> results = repository.findAll(Specifications.where(spec));
|
||||
|
||||
assertThat(userTom, isIn(results));
|
||||
assertThat(userJohn, not(isIn(results)));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void givenMinAge_whenGettingListOfUsers_thenCorrect() {
|
||||
final UserSpecification spec = new UserSpecification(new SpecSearchCriteria("age", SearchOperation.GREATER_THAN, "25"));
|
||||
final List<User> results = repository.findAll(Specifications.where(spec));
|
||||
assertThat(userTom, isIn(results));
|
||||
assertThat(userJohn, not(isIn(results)));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void givenFirstNamePrefix_whenGettingListOfUsers_thenCorrect() {
|
||||
final UserSpecification spec = new UserSpecification(new SpecSearchCriteria("firstName", SearchOperation.STARTS_WITH, "jo"));
|
||||
final List<User> results = repository.findAll(spec);
|
||||
assertThat(userJohn, isIn(results));
|
||||
assertThat(userTom, not(isIn(results)));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void givenFirstNameSuffix_whenGettingListOfUsers_thenCorrect() {
|
||||
final UserSpecification spec = new UserSpecification(new SpecSearchCriteria("firstName", SearchOperation.ENDS_WITH, "n"));
|
||||
final List<User> results = repository.findAll(spec);
|
||||
assertThat(userJohn, isIn(results));
|
||||
assertThat(userTom, not(isIn(results)));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void givenFirstNameSubstring_whenGettingListOfUsers_thenCorrect() {
|
||||
final UserSpecification spec = new UserSpecification(new SpecSearchCriteria("firstName", SearchOperation.CONTAINS, "oh"));
|
||||
final List<User> results = repository.findAll(spec);
|
||||
|
||||
assertThat(userJohn, isIn(results));
|
||||
assertThat(userTom, not(isIn(results)));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void givenAgeRange_whenGettingListOfUsers_thenCorrect() {
|
||||
final UserSpecification spec = new UserSpecification(new SpecSearchCriteria("age", SearchOperation.GREATER_THAN, "20"));
|
||||
final UserSpecification spec1 = new UserSpecification(new SpecSearchCriteria("age", SearchOperation.LESS_THAN, "25"));
|
||||
final List<User> results = repository.findAll(Specifications
|
||||
.where(spec)
|
||||
.and(spec1));
|
||||
|
||||
assertThat(userJohn, isIn(results));
|
||||
assertThat(userTom, not(isIn(results)));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,146 @@
|
||||
package org.baeldung.persistence.query;
|
||||
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import io.restassured.RestAssured;
|
||||
import io.restassured.response.Response;
|
||||
|
||||
import org.baeldung.persistence.model.User;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.springframework.test.context.ActiveProfiles;
|
||||
|
||||
//@RunWith(SpringJUnit4ClassRunner.class)
|
||||
//@ContextConfiguration(classes = { ConfigTest.class,
|
||||
// PersistenceConfig.class }, loader = AnnotationConfigContextLoader.class)
|
||||
@ActiveProfiles("test")
|
||||
public class JPASpecificationLiveTest {
|
||||
|
||||
// @Autowired
|
||||
// private UserRepository repository;
|
||||
|
||||
private User userJohn;
|
||||
|
||||
private User userTom;
|
||||
|
||||
private final String URL_PREFIX = "http://localhost:8082/spring-rest-query-language/auth/users/spec?search=";
|
||||
|
||||
@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);
|
||||
}
|
||||
|
||||
private final String EURL_PREFIX = "http://localhost:8082/spring-rest-query-language/auth/users/espec?search=";
|
||||
|
||||
@Test
|
||||
public void givenFirstOrLastName_whenGettingListOfUsers_thenCorrect() {
|
||||
final Response response = RestAssured.get(EURL_PREFIX + "firstName:john,'lastName:doe");
|
||||
final String result = response.body()
|
||||
.asString();
|
||||
assertTrue(result.contains(userJohn.getEmail()));
|
||||
assertTrue(result.contains(userTom.getEmail()));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void givenFirstAndLastName_whenGettingListOfUsers_thenCorrect() {
|
||||
final Response response = RestAssured.get(URL_PREFIX + "firstName:john,lastName:doe");
|
||||
final String result = response.body()
|
||||
.asString();
|
||||
|
||||
assertTrue(result.contains(userJohn.getEmail()));
|
||||
assertFalse(result.contains(userTom.getEmail()));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void givenFirstNameInverse_whenGettingListOfUsers_thenCorrect() {
|
||||
final Response response = RestAssured.get(URL_PREFIX + "firstName!john");
|
||||
final String result = response.body()
|
||||
.asString();
|
||||
|
||||
assertTrue(result.contains(userTom.getEmail()));
|
||||
assertFalse(result.contains(userJohn.getEmail()));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void givenMinAge_whenGettingListOfUsers_thenCorrect() {
|
||||
final Response response = RestAssured.get(URL_PREFIX + "age>25");
|
||||
final String result = response.body()
|
||||
.asString();
|
||||
|
||||
assertTrue(result.contains(userTom.getEmail()));
|
||||
assertFalse(result.contains(userJohn.getEmail()));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void givenFirstNamePrefix_whenGettingListOfUsers_thenCorrect() {
|
||||
final Response response = RestAssured.get(URL_PREFIX + "firstName:jo*");
|
||||
final String result = response.body()
|
||||
.asString();
|
||||
|
||||
assertTrue(result.contains(userJohn.getEmail()));
|
||||
assertFalse(result.contains(userTom.getEmail()));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void givenFirstNameSuffix_whenGettingListOfUsers_thenCorrect() {
|
||||
final Response response = RestAssured.get(URL_PREFIX + "firstName:*n");
|
||||
final String result = response.body()
|
||||
.asString();
|
||||
|
||||
assertTrue(result.contains(userJohn.getEmail()));
|
||||
assertFalse(result.contains(userTom.getEmail()));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void givenFirstNameSubstring_whenGettingListOfUsers_thenCorrect() {
|
||||
final Response response = RestAssured.get(URL_PREFIX + "firstName:*oh*");
|
||||
final String result = response.body()
|
||||
.asString();
|
||||
|
||||
assertTrue(result.contains(userJohn.getEmail()));
|
||||
assertFalse(result.contains(userTom.getEmail()));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void givenAgeRange_whenGettingListOfUsers_thenCorrect() {
|
||||
final Response response = RestAssured.get(URL_PREFIX + "age>20,age<25");
|
||||
final String result = response.body()
|
||||
.asString();
|
||||
|
||||
assertTrue(result.contains(userJohn.getEmail()));
|
||||
assertFalse(result.contains(userTom.getEmail()));
|
||||
}
|
||||
|
||||
private final String ADV_URL_PREFIX = "http://localhost:8082/spring-rest-query-language/auth/users/spec/adv?search=";
|
||||
|
||||
@Test
|
||||
public void givenFirstOrLastName_whenGettingAdvListOfUsers_thenCorrect() {
|
||||
final Response response = RestAssured.get(ADV_URL_PREFIX + "firstName:john OR lastName:doe");
|
||||
final String result = response.body()
|
||||
.asString();
|
||||
assertTrue(result.contains(userJohn.getEmail()));
|
||||
assertTrue(result.contains(userTom.getEmail()));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void givenFirstOrFirstNameAndAge_whenGettingAdvListOfUsers_thenCorrect() {
|
||||
final Response response = RestAssured.get(ADV_URL_PREFIX + "( firstName:john OR firstName:tom ) AND age>22");
|
||||
final String result = response.body()
|
||||
.asString();
|
||||
assertFalse(result.contains(userJohn.getEmail()));
|
||||
assertTrue(result.contains(userTom.getEmail()));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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.annotation.Rollback;
|
||||
import org.springframework.test.context.ContextConfiguration;
|
||||
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
|
||||
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
|
||||
@Rollback
|
||||
public class RsqlIntegrationTest {
|
||||
|
||||
@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)));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
package org.baeldung.web;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import io.restassured.RestAssured;
|
||||
import io.restassured.response.Response;
|
||||
import io.restassured.specification.RequestSpecification;
|
||||
|
||||
import org.baeldung.persistence.model.MyUser;
|
||||
import org.junit.Test;
|
||||
import org.springframework.test.context.ActiveProfiles;
|
||||
|
||||
@ActiveProfiles("test")
|
||||
public class MyUserLiveTest {
|
||||
|
||||
private final MyUser userJohn = new MyUser("john", "doe", "john@doe.com", 11);
|
||||
private String URL_PREFIX = "http://localhost:8082/spring-rest-query-language/auth/api/myusers";
|
||||
|
||||
@Test
|
||||
public void whenGettingListOfUsers_thenCorrect() {
|
||||
final Response response = givenAuth().get(URL_PREFIX);
|
||||
final MyUser[] result = response.as(MyUser[].class);
|
||||
assertEquals(result.length, 2);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void givenFirstName_whenGettingListOfUsers_thenCorrect() {
|
||||
final Response response = givenAuth().get(URL_PREFIX + "?firstName=john");
|
||||
final MyUser[] result = response.as(MyUser[].class);
|
||||
assertEquals(result.length, 1);
|
||||
assertEquals(result[0].getEmail(), userJohn.getEmail());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void givenPartialLastName_whenGettingListOfUsers_thenCorrect() {
|
||||
final Response response = givenAuth().get(URL_PREFIX + "?lastName=do");
|
||||
final MyUser[] result = response.as(MyUser[].class);
|
||||
assertEquals(result.length, 2);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void givenEmail_whenGettingListOfUsers_thenIgnored() {
|
||||
final Response response = givenAuth().get(URL_PREFIX + "?email=john");
|
||||
final MyUser[] result = response.as(MyUser[].class);
|
||||
assertEquals(result.length, 2);
|
||||
}
|
||||
|
||||
private RequestSpecification givenAuth() {
|
||||
return RestAssured.given().auth().preemptive().basic("user1", "user1Pass");
|
||||
}
|
||||
}
|
||||
13
spring-rest-query-language/src/test/resources/.gitignore
vendored
Normal file
13
spring-rest-query-language/src/test/resources/.gitignore
vendored
Normal file
@@ -0,0 +1,13 @@
|
||||
*.class
|
||||
|
||||
#folders#
|
||||
/target
|
||||
/neoDb*
|
||||
/data
|
||||
/src/main/webapp/WEB-INF/classes
|
||||
*/META-INF/*
|
||||
|
||||
# Packaged files #
|
||||
*.jar
|
||||
*.war
|
||||
*.ear
|
||||
Reference in New Issue
Block a user