Skip to content

Instantly share code, notes, and snippets.

@jirutka
Last active September 21, 2020 08:28
Show Gist options
  • Star 3 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save jirutka/42a0f9bfea280b3c5dca to your computer and use it in GitHub Desktop.
Save jirutka/42a0f9bfea280b3c5dca to your computer and use it in GitHub Desktop.
Very simple, quick & dirty implementation of RSQL to JPA2 converter. See https://github.com/jirutka/rsql-parser.
import cz.jirutka.rsql.parser.ast.*;
import org.springframework.core.convert.ConversionService;
import org.springframework.core.convert.support.DefaultConversionService;
import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.Path;
import javax.persistence.criteria.Predicate;
import javax.persistence.criteria.Root;
import javax.persistence.metamodel.Attribute;
import java.util.List;
import static cz.jirutka.rsql.parser.ast.RSQLOperators.*;
// WARNING: This is very naive, quick & dirty implementation!
public class JpaRsqlConverter implements RSQLVisitor<Predicate, Root> {
private final CriteriaBuilder builder;
private final ConversionService conversionService = new DefaultConversionService();
public JpaRsqlConverter(CriteriaBuilder builder) {
this.builder = builder;
}
public Predicate visit(AndNode node, Root root) {
return builder.and(processNodes(node.getChildren(), root));
}
public Predicate visit(OrNode node, Root root) {
return builder.or(processNodes(node.getChildren(), root));
}
public Predicate visit(ComparisonNode node, Root root) {
ComparisonOperator op = node.getOperator();
Path attrPath = root.get(node.getSelector());
// RSQL guarantees that node has at least one argument
Object argument = node.getArguments().get(0);
if (op.equals(EQUAL)) {
return builder.equal(attrPath, argument);
}
if (op.equals(NOT_EQUAL)) {
return builder.notEqual(attrPath, argument);
}
Attribute attribute = root.getModel().getAttribute(node.getSelector());
Class type = attribute.getJavaType();
if (! Comparable.class.isAssignableFrom(type)) {
throw new IllegalArgumentException(String.format(
"Operator %s can be used only for Comparables", op));
}
Comparable comparable = (Comparable) conversionService.convert(argument, type);
if (op.equals(GREATER_THAN)) {
return builder.greaterThan(attrPath, comparable);
}
if (op.equals(GREATER_THAN_OR_EQUAL)) {
return builder.greaterThanOrEqualTo(attrPath, comparable);
}
if (op.equals(LESS_THAN)) {
return builder.lessThan(attrPath, comparable);
}
if (op.equals(LESS_THAN_OR_EQUAL)) {
return builder.lessThanOrEqualTo(attrPath, comparable);
}
throw new IllegalArgumentException("Unknown operator: " + op);
}
private Predicate[] processNodes(List<Node> nodes, Root root) {
Predicate[] predicates = new Predicate[nodes.size()];
for (int i = 0; i < nodes.size(); i++) {
predicates[i] = nodes.get(i).accept(this, root);
}
return predicates;
}
}
import org.springframework.data.domain.Pageable;
import org.springframework.web.bind.annotation.RequestParam;
import java.lang.Iterable;
/**
* Simple controller example.
*/
@RestController
@RequestMapping("/projects")
public class ProjectsController {
@RequestMapping
public Iterable<Project> findProjects(@RequestParam String query, Pageable pageable) {
return repository.findAll(RsqlSpecification.<Project>rsql(query), pageable);
}
}
import cz.jirutka.rsql.parser.ast.Node;
import cz.jirutka.rsql.parser.RSQLParser;
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 abstract class RsqlSpecification {
public static <T> Specification<T> rsql(final String rsqlQuery) {
return new Specification<T>() {
public Predicate toPredicate(Root<T> root, CriteriaQuery<?> query, CriteriaBuilder cb) {
Node rsql = new RSQLParser().parse(rsqlQuery);
return rsql.accept(new JpaRsqlConverter(cb), root);
}
};
}
}
@eduardoschmidtsantos
Copy link

eduardoschmidtsantos commented Mar 24, 2016

to support like operations

private static final String ASTERISK = "*";
private static final String PERCENTAGE = "%";

String argument = node.getArguments().get(0);
if (op.equals(EQUAL)) {
    if (argument.contains(ASTERISK)){
        argument = argument.replace(ASTERISK, PERCENTAGE);
        return builder.like(attrPath, argument);
    }
    return builder.equal(attrPath, argument);
}

@tksarul
Copy link

tksarul commented Feb 20, 2020

How do I get generic-type here?
Attribute attribute = root.getModel().getAttribute(node.getSelector());
Class type = attribute.getJavaType();

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment