org.niord.core.db.CriteriaHelper.java Source code

Java tutorial

Introduction

Here is the source code for org.niord.core.db.CriteriaHelper.java

Source

/*
 * Copyright 2016 Danish Maritime Authority.
 *
 * 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.niord.core.db;

import org.apache.commons.lang.StringUtils;

import javax.persistence.EntityManager;
import javax.persistence.Tuple;
import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.CriteriaQuery;
import javax.persistence.criteria.Expression;
import javax.persistence.criteria.Predicate;
import java.util.Collection;
import java.util.LinkedList;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * Helps initializing the criteria builders and queries and
 * helps building the list of "where" predicates
 * for a Criteria API query
 */
@SuppressWarnings("unused")
public class CriteriaHelper<T> {

    private static final Pattern MATCH_TEXT_PATTERN = Pattern.compile("\"([^\"]*)\"|(\\S+)");

    CriteriaBuilder cb;
    CriteriaQuery<T> q;
    List<Predicate> where = new LinkedList<>();

    /**
     * Initializes a CriteriaBuilder and CriteriaQuery with a Tuple query.
     * @param em the entity manager
     * @return the newly instantiated criteria builder
     */
    public static CriteriaHelper<Tuple> initWithTupleQuery(EntityManager em) {
        CriteriaBuilder builder = em.getCriteriaBuilder();
        CriteriaQuery<Tuple> tupleQuery = builder.createTupleQuery();
        return new CriteriaHelper<>(builder, tupleQuery);
    }

    /**
     * Initializes a CriteriaBuilder and CriteriaQuery with a the given result class.
     * @param em the entity manager
     * @param resultClass the result class
     * @return the newly instantiated criteria builder
     */
    public static <T> CriteriaHelper<T> initWithQuery(EntityManager em, Class<T> resultClass) {
        CriteriaBuilder builder = em.getCriteriaBuilder();
        CriteriaQuery<T> query = builder.createQuery(resultClass);
        return new CriteriaHelper<>(builder, query);
    }

    /**
     * Constructor
     *
     * @param cb the criteria builder
     * @param q the criteria query
     */
    public CriteriaHelper(CriteriaBuilder cb, CriteriaQuery<T> q) {
        this.cb = cb;
        this.q = q;
    }

    /** Returns the associated criteria builder */
    public CriteriaBuilder getCriteriaBuilder() {
        return cb;
    }

    /** Returns the associated criteria query */
    public CriteriaQuery<T> getCriteriaQuery() {
        return q;
    }

    /**
     * Adds a predicate to the list
     * @param predicate the predicate to add
     */
    public CriteriaHelper<T> add(Predicate predicate) {
        where.add(predicate);
        return this;
    }

    /**
     * If value is defined, matches the attribute with the value
     *
     * @param attr the attribute
     * @param value the value to match
     */
    public <V> CriteriaHelper<T> equals(Expression<V> attr, V value) {
        if (value != null) {
            where.add(cb.equal(attr, value));
        }
        return this;
    }

    /**
     * If value is defined, matches the attribute with the value in a case-agnostic manner
     *
     * @param attr the attribute
     * @param value the value to match
     */
    public CriteriaHelper<T> equalsIgnoreCase(Expression<String> attr, String value) {
        if (value != null) {
            where.add(cb.equal(cb.lower(attr), value.toLowerCase()));
        }
        return this;
    }

    /**
     * If value is defined, substring-matches the attribute with the value.
     *
     * @param attr the attribute
     * @param value the value to substring-match
     */
    public CriteriaHelper<T> like(Expression<String> attr, String value) {
        if (StringUtils.isNotBlank(value)) {
            where.add(cb.like(cb.lower(attr), "%" + value.toLowerCase() + "%"));
        }
        return this;
    }

    /**
     * If value is defined, matches the attribute with the value.
     *
     * @param attr the attribute
     * @param value the value to substring-match
     */
    public CriteriaHelper<T> startsWith(Expression<String> attr, String value) {
        if (StringUtils.isNotBlank(value)) {
            where.add(cb.like(cb.lower(attr), value.toLowerCase() + "%"));
        }
        return this;
    }

    /**
     * If values is defined, matches the attribute with any of the values.
     * If values is undefined (null or empty) this predicate yields false.
     *
     * @param attr the attribute
     * @param values the values to match
     */
    public <V> CriteriaHelper<T> in(Expression<V> attr, Collection<V> values) {
        if (values != null && values.size() > 0) {
            where.add(attr.in(values));
        } else {
            where.add(cb.disjunction()); // Always false
        }
        return this;
    }

    /**
     * If value1 is defined the attribute must be greater than or equal to this value.
     * If value2 is defined the attribute must be less than or equal to this value.
     *
     * @param attr the attribute
     * @param value1 the first value
     * @param value2 the second value
     */
    public <V extends Comparable<? super V>> CriteriaHelper<T> between(Expression<V> attr, V value1, V value2) {
        if (value1 != null) {
            where.add(cb.greaterThanOrEqualTo(attr, value1));
        }
        if (value2 != null) {
            where.add(cb.lessThanOrEqualTo(attr, value2));
        }
        return this;
    }

    /**
     * Performs a range check that cater with possibly open-ended attribute intervals and possibly open-ended
     * filter interval.
     *
     * @param attr1 the first attribute
     * @param attr2 the second attribute
     * @param value1 the first value
     * @param value2 the second value
     */
    public <V extends Comparable<? super V>> CriteriaHelper<T> overlaps(Expression<V> attr1, Expression<V> attr2,
            V value1, V value2) {

        if (value1 != null) {
            where.add(cb.or(cb.greaterThanOrEqualTo(attr2, value1), cb.isNull(attr2)));
        }
        if (value2 != null) {
            where.add(cb.or(cb.lessThanOrEqualTo(attr1, value2), cb.isNull(attr1)));
        }
        return this;
    }

    /**
     * If value is defined, attempts a pseudo google-style free text search of the value.
     * Supported format:
     * <ul>
     *     <li>ab cd: Matches either of the values.</li>
     *     <li>"ab cd": Quoted matches must be exact.</li>
     *     <li>+ab: Must contain value.</li>
     *     <li>-ab: Must not contain value.</li>
     *     <li>ab*: Must match the given wildcard value pattern.</li>
     * </ul>
     *
     * @param attr the attribute
     * @param value the free-text match
     */
    public CriteriaHelper<T> matchText(Expression<String> attr, String value) {
        if (StringUtils.isNotBlank(value)) {
            Predicate predicate = null;
            Matcher m = MATCH_TEXT_PATTERN.matcher(value);
            while (m.find()) {
                String quotedText = m.group(1);
                String plainText = m.group(2);

                if (quotedText != null) {
                    Predicate p = cb.like(cb.lower(attr), "%" + quotedText.toLowerCase() + "%");
                    predicate = predicate == null ? p : cb.or(predicate, p);

                } else if (plainText != null) {
                    plainText = plainText.replace('*', '%');
                    if (plainText.startsWith("-")) {
                        Predicate p = cb
                                .not(cb.like(cb.lower(attr), "%" + plainText.substring(1).toLowerCase() + "%"));
                        predicate = predicate == null ? p : cb.and(predicate, p);
                    } else if (plainText.startsWith("+")) {
                        Predicate p = cb.like(cb.lower(attr), "%" + plainText.substring(1).toLowerCase() + "%");
                        predicate = predicate == null ? p : cb.and(predicate, p);
                    } else if (!plainText.isEmpty()) {
                        Predicate p = cb.like(cb.lower(attr), "%" + plainText.toLowerCase() + "%");
                        predicate = predicate == null ? p : cb.or(predicate, p);
                    }
                }
            }
            if (predicate != null) {
                where.add(predicate);
            }
        }
        return this;
    }

    /**
     * Returns the collected list of predicates
     * @return the collected list of predicates
     */
    public Predicate[] where() {
        return where.toArray(new Predicate[where.size()]);
    }
}