org.jboss.pnc.datastore.predicates.rsql.RSQLNodeTravellerPredicate.java Source code

Java tutorial

Introduction

Here is the source code for org.jboss.pnc.datastore.predicates.rsql.RSQLNodeTravellerPredicate.java

Source

/**
 * JBoss, Home of Professional Open Source.
 * Copyright 2014-2019 Red Hat, Inc., and individual contributors
 * as indicated by the @author tags.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.jboss.pnc.datastore.predicates.rsql;

import com.google.common.base.Preconditions;
import cz.jirutka.rsql.parser.RSQLParser;
import cz.jirutka.rsql.parser.RSQLParserException;
import cz.jirutka.rsql.parser.ast.AndNode;
import cz.jirutka.rsql.parser.ast.ComparisonNode;
import cz.jirutka.rsql.parser.ast.ComparisonOperator;
import cz.jirutka.rsql.parser.ast.LogicalNode;
import cz.jirutka.rsql.parser.ast.Node;
import cz.jirutka.rsql.parser.ast.OrNode;
import cz.jirutka.rsql.parser.ast.RSQLOperators;
import org.apache.commons.beanutils.BeanUtils;
import org.apache.commons.beanutils.NestedNullException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.Path;
import javax.persistence.criteria.Predicate;
import javax.persistence.criteria.Root;
import java.lang.invoke.MethodHandles;
import java.lang.reflect.InvocationTargetException;
import java.text.NumberFormat;
import java.text.ParseException;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import static org.jboss.pnc.datastore.predicates.rsql.AbstractTransformer.selectWithOperand;

public class RSQLNodeTravellerPredicate<Entity> {

    private static final Logger logger = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());

    private final Node rootNode;

    private final Class<Entity> selectingClass;

    private final Map<ComparisonOperator, Transformer<Entity>> operations = new HashMap<>();

    private final static Pattern likePattern = Pattern.compile("(\\%[a-zA-Z0-9\\s]+\\%)");
    private String UNKNOWN_PART_PLACEHOLDER = "_";

    private final static ComparisonOperator LIKE = new ComparisonOperator("=like=");
    private final static ComparisonOperator IS_NULL = new ComparisonOperator("=isnull=");

    public RSQLNodeTravellerPredicate(Class<Entity> entityClass, String rsql) throws RSQLParserException {
        operations.put(RSQLOperators.EQUAL, new AbstractTransformer<Entity>() {
            @Override
            Predicate transform(Root<Entity> r, Path<?> selectedPath, CriteriaBuilder cb, String operand,
                    List<Object> convertedArguments) {
                return cb.equal(selectedPath, convertedArguments.get(0));
            }
        });

        operations.put(RSQLOperators.NOT_EQUAL, new AbstractTransformer<Entity>() {
            @Override
            Predicate transform(Root<Entity> r, Path<?> selectedPath, CriteriaBuilder cb, String operand,
                    List<Object> convertedArguments) {
                return cb.notEqual(selectedPath, convertedArguments.get(0));
            }
        });

        operations.put(RSQLOperators.GREATER_THAN, (r, cb, clazz, operand, arguments) -> cb
                .greaterThan((Path) selectWithOperand(r, operand, clazz), arguments.get(0)));
        operations.put(RSQLOperators.GREATER_THAN_OR_EQUAL, (r, cb, clazz, operand, arguments) -> cb
                .greaterThanOrEqualTo((Path) selectWithOperand(r, operand, clazz), arguments.get(0)));
        operations.put(RSQLOperators.LESS_THAN, (r, cb, clazz, operand, arguments) -> cb
                .lessThan((Path) selectWithOperand(r, operand, clazz), arguments.get(0)));
        operations.put(RSQLOperators.LESS_THAN_OR_EQUAL, (r, cb, clazz, operand, arguments) -> cb
                .lessThanOrEqualTo((Path) selectWithOperand(r, operand, clazz), arguments.get(0)));
        operations.put(RSQLOperators.IN,
                (r, cb, clazz, operand, arguments) -> ((Path) selectWithOperand(r, operand, clazz)).in(arguments));
        operations.put(RSQLOperators.NOT_IN, (r, cb, clazz, operand, arguments) -> cb
                .not(((Path) selectWithOperand(r, operand, clazz)).in(arguments)));
        operations.put(LIKE,
                (r, cb, clazz, operand, arguments) -> cb.like(cb.lower((Path) selectWithOperand(r, operand, clazz)),
                        preprocessLikeOperatorArgument(arguments.get(0).toLowerCase())));
        operations.put(IS_NULL, (r, cb, clazz, operand, arguments) -> {
            if (Boolean.parseBoolean(arguments.get(0))) {
                return cb.isNull((Path) selectWithOperand(r, operand, clazz));
            } else {
                return cb.isNotNull((Path) selectWithOperand(r, operand, clazz));
            }
        });

        Set<ComparisonOperator> operators = RSQLOperators.defaultOperators();
        operators.add(LIKE);
        operators.add(IS_NULL);

        rootNode = new RSQLParser(operators).parse(preprocessRSQL(rsql));
        selectingClass = entityClass;
    }

    public org.jboss.pnc.spi.datastore.repositories.api.Predicate<Entity> getEntityPredicate() {
        return (root, query, cb) -> {
            RSQLNodeTraveller<Predicate> visitor = new RSQLNodeTraveller<Predicate>() {

                public Predicate visit(LogicalNode node) {
                    logger.trace("Parsing LogicalNode {}", node);
                    return proceedEmbeddedNodes(node);
                }

                public Predicate visit(ComparisonNode node) {
                    logger.trace("Parsing ComparisonNode {}", node);
                    return proceedSelection(node);
                }

                private Predicate proceedSelection(ComparisonNode node) {
                    Transformer<Entity> transformation = operations.get(node.getOperator());
                    Preconditions.checkArgument(transformation != null, "Operation not supported");
                    return transformation.transform(root, cb, selectingClass, node.getSelector(),
                            node.getArguments());
                }

                private Predicate proceedEmbeddedNodes(LogicalNode node) {
                    Iterator<Node> iterator = node.iterator();
                    Predicate p1 = visit(iterator.next());
                    Predicate p2 = visit(iterator.next());
                    if (node instanceof AndNode) {
                        Predicate pCombined = cb.and(p1, p2);
                        while (iterator.hasNext()) {
                            Predicate pNext = visit(iterator.next());
                            pCombined = cb.and(pCombined, pNext);
                        }
                        return pCombined;
                    } else if (node instanceof OrNode) {
                        Predicate pCombined = cb.or(p1, p2);
                        while (iterator.hasNext()) {
                            Predicate pNext = visit(iterator.next());
                            pCombined = cb.or(pCombined, pNext);
                        }
                        return pCombined;
                    } else {
                        throw new UnsupportedOperationException("Logical operation not supported");
                    }
                }
            };

            return rootNode.accept(visitor);
        };
    }

    public java.util.function.Predicate<Entity> getStreamPredicate() {
        return instance -> {
            RSQLNodeTraveller<Boolean> visitor = new RSQLNodeTraveller<Boolean>() {

                public Boolean visit(LogicalNode node) {
                    logger.trace("Parsing LogicalNode {}", node);
                    Iterator<Node> iterator = node.iterator();
                    if (node instanceof AndNode) {
                        boolean result = true;
                        while (iterator.hasNext()) {
                            Node next = iterator.next();
                            result &= visit(next);
                        }
                        return result;
                    } else if (node instanceof OrNode) {
                        boolean result = false;
                        while (iterator.hasNext()) {
                            Node next = iterator.next();
                            result |= visit(next);
                        }
                        return result;
                    } else {
                        throw new UnsupportedOperationException("Logical operation not supported");
                    }
                }

                public Boolean visit(ComparisonNode node) {
                    logger.trace("Parsing ComparisonNode {}", node);

                    String fieldName = node.getSelector();
                    String argument = node.getArguments().get(0);

                    try {
                        String propertyValue = BeanUtils.getProperty(instance, fieldName);

                        if (node.getOperator().equals(IS_NULL)) {
                            return Boolean.valueOf(propertyValue == null).equals(Boolean.valueOf(argument));
                        }

                        if (propertyValue == null) {
                            // Null values are considered not equal
                            return false;
                        }

                        if (node.getOperator().equals(RSQLOperators.EQUAL)) {
                            return propertyValue.equals(argument);
                        } else if (node.getOperator().equals(RSQLOperators.NOT_EQUAL)) {
                            return !propertyValue.equals(argument);
                        } else if (node.getOperator().equals(RSQLOperators.GREATER_THAN)) {
                            NumberFormat numberFormat = NumberFormat.getInstance();
                            Number argumentNumber = numberFormat.parse(argument);
                            return numberFormat.parse(propertyValue).intValue() < argumentNumber.intValue();
                        } else if (node.getOperator().equals(RSQLOperators.GREATER_THAN_OR_EQUAL)) {
                            NumberFormat numberFormat = NumberFormat.getInstance();
                            Number argumentNumber = numberFormat.parse(argument);
                            return numberFormat.parse(propertyValue).intValue() <= argumentNumber.intValue();
                        } else if (node.getOperator().equals(RSQLOperators.LESS_THAN)) {
                            NumberFormat numberFormat = NumberFormat.getInstance();
                            Number argumentNumber = numberFormat.parse(argument);
                            return numberFormat.parse(propertyValue).intValue() > argumentNumber.intValue();
                        } else if (node.getOperator().equals(RSQLOperators.GREATER_THAN_OR_EQUAL)) {
                            NumberFormat numberFormat = NumberFormat.getInstance();
                            Number argumentNumber = numberFormat.parse(argument);
                            return numberFormat.parse(propertyValue).intValue() >= argumentNumber.intValue();
                        } else if (node.getOperator().equals(LIKE)) {
                            argument = argument.replaceAll(UNKNOWN_PART_PLACEHOLDER, ".*").replaceAll("%", ".*");
                            return propertyValue.matches(argument);
                        } else if (node.getOperator().equals(RSQLOperators.IN)) {
                            return node.getArguments().contains(propertyValue);
                        } else if (node.getOperator().equals(RSQLOperators.NOT_IN)) {
                            return !node.getArguments().contains(propertyValue);
                        } else {
                            throw new UnsupportedOperationException("Not Implemented yet!");
                        }
                    } catch (NestedNullException e) {
                        // If a nested property is null (i.e. idRev.id is null), it is considered a false equality
                        return false;
                    } catch (NoSuchMethodException | InvocationTargetException | IllegalAccessException e) {
                        throw new IllegalStateException("Reflections exception", e);
                    } catch (ParseException e) {
                        throw new IllegalStateException("RSQL parse exception", e);
                    }
                }
            };

            return rootNode.accept(visitor);
        };
    }

    private String preprocessRSQL(String rsql) {
        String result = rsql;
        Matcher matcher = likePattern.matcher(rsql);
        while (matcher.find()) {
            result = rsql.replaceAll(matcher.group(1),
                    matcher.group(1).replaceAll("\\s", UNKNOWN_PART_PLACEHOLDER));
        }
        return result;
    }

    private String preprocessLikeOperatorArgument(String argument) {
        return argument.replaceAll("\\?", "_").replaceAll("\\*", "%");
    }
}