CriteriaBuilder.java

package com.tradecloud.domain.infrastructure.persistence;

import com.tradecloud.domain.base.utils.DateRange;
import com.tradecloud.domain.base.utils.DateUtils;
import com.tradecloud.domain.search.SearchParams;
import org.hibernate.FetchMode;
import org.hibernate.criterion.DetachedCriteria;
import org.hibernate.criterion.MatchMode;
import org.hibernate.criterion.Order;
import org.hibernate.criterion.Restrictions;
import org.joda.time.ReadablePartial;

import javax.persistence.criteria.Join;
import javax.persistence.criteria.JoinType;
import javax.persistence.criteria.Predicate;
import javax.persistence.criteria.Root;
import java.util.Collections;
import java.util.Date;
import java.util.List;

/**
 * TODO. This needs to move to repository. There is a temp CriteriaBuilder2
 * there in the mean time.
 *
 * @author jon
 */
public class CriteriaBuilder {

    private final static String ACTIVE = "active";

    public static Order createOrdering(String orderBy, boolean ascending) {
        return ascending ? Order.asc(orderBy) : Order.desc(orderBy);
    }

    public static void applySearchParams(DetachedCriteria criteria, SearchParams searchParams) {
        // Can be null in the case of tests, where they don't care
        if (searchParams != null) {
            if (searchParams.getOrderBy() != null) {
                criteria.addOrder(createOrdering(searchParams.getOrderBy(), searchParams.isDescending()));
            }
            // Check if we are only searching for active entities
            if (searchParams.isActiveOnly()) {
                criteria.add(Restrictions.eq(ACTIVE, true));
            }
        }
    }

    public static void addDateRangeCriteria(DetachedCriteria criteria, String dateColumn, ReadablePartial dateFrom, ReadablePartial dateTo) {
        if (dateFrom != null) {
            criteria.add(Restrictions.ge(dateColumn, dateFrom));
        }
        if (dateTo != null) {
            criteria.add(Restrictions.le(dateColumn, dateTo));
        }
    }

    public static void addDateRangeCriteria(DetachedCriteria criteria, String dateColumn, Date dateFrom, Date dateTo) {
        addDateRangeCriteria(criteria, dateColumn, dateFrom, dateTo, false);
    }

    public static void addDateRangeCriteria(DetachedCriteria criteria, String dateColumn, Date dateFrom, Date dateTo, boolean outerBounds) {
        if (dateFrom != null || dateTo != null) {
            criteria.add(Restrictions.isNotNull(dateColumn));

            if (dateFrom != null) {
                criteria.add(Restrictions.ge(dateColumn, outerBounds ? DateUtils.getStartOfDay(dateFrom) : dateFrom));
            }
            if (dateTo != null) {
                criteria.add(Restrictions.le(dateColumn, outerBounds ? DateUtils.getEndOfDay(dateTo) : dateTo));
            }
        }
    }

    public static void addDateRangeCriteria2(javax.persistence.criteria.CriteriaBuilder cb, List<Predicate> predicates, Root root, String dateColumn,
                                             Date dateFrom, Date dateTo) {
        addDateRangeCriteria2(cb, predicates, root, dateColumn, dateFrom, dateTo, false);
    }

    public static void addDateRangeCriteria2(javax.persistence.criteria.CriteriaBuilder cb, List<Predicate> predicates, Root root, String dateColumn,
                                             Date dateFrom, Date dateTo, boolean outerBounds) {
        if (dateFrom != null || dateTo != null) {
            // Ensure the date column is not null
            Predicate isNotNull = cb.isNotNull(root.get(dateColumn));

            // Create predicates for date range
            Predicate dateFromPredicate = null;
            Predicate dateToPredicate = null;

            if (dateFrom != null) {
                Date startDate = outerBounds ? DateUtils.getStartOfDay(dateFrom) : dateFrom;
                dateFromPredicate = cb.greaterThanOrEqualTo(root.get(dateColumn), startDate);
            }

            if (dateTo != null) {
                Date endDate = outerBounds ? DateUtils.getEndOfDay(dateTo) : dateTo;
                dateToPredicate = cb.lessThanOrEqualTo(root.get(dateColumn), endDate);
            }

            // Combine the predicates using AND logic
            if (dateFromPredicate != null && dateToPredicate != null) {

                predicates.add(cb.and(isNotNull, dateFromPredicate, dateToPredicate));
            } else if (dateFromPredicate != null) {

                predicates.add(cb.and(isNotNull, dateFromPredicate));
            } else if (dateToPredicate != null) {

                predicates.add(cb.and(isNotNull, dateToPredicate));
            }
        }
    }

    public static void addDateRangeCriteria(DetachedCriteria criteria, String dateColumn, DateRange dateRange) {
        addDateRangeCriteria(criteria, dateColumn, dateRange, false);
        /*
        if (dateRange != null) {
            if (dateRange.getFrom() != null) {
                criteria.add(Restrictions.ge(dateColumn, dateRange.getFrom()));
            }
            if (dateRange.getTo() != null) {
                criteria.add(Restrictions.le(dateColumn, dateRange.getTo()));
            }
        }
         */
    }

    public static void addDateRangeCriteria(DetachedCriteria criteria, String dateColumn, DateRange dateRange, boolean outerBounds) {
        if (dateRange != null) {
            addDateRangeCriteria(criteria, dateColumn, dateRange.getFrom(), dateRange.getTo(), outerBounds);
            /*
            if (dateRange.getFrom() != null) {
                criteria.add(Restrictions.ge(dateColumn, outerBounds ? dateRange.getFromAsStartOfDay() : dateRange.getFrom()));
            }
            if (dateRange.getTo() != null) {
                criteria.add(Restrictions.le(dateColumn, outerBounds ? dateRange.getToAsEndOfDay() : dateRange.getTo()));
            }
             */
        }
    }

    public static void addDateLessOrEqualRangeCriteria(DetachedCriteria criteria, String dateColumn, Date date) {
        if (date != null) {
            criteria.add(Restrictions.le(dateColumn, date));
        }
    }

    public static void addDateGreaterOrEqualRangeCriteria(DetachedCriteria criteria, String dateColumn, Date date) {
        if (date != null) {
            criteria.add(Restrictions.ge(dateColumn, date));
        }
    }

    public static void addEventDateRangeSearchCriteria(DetachedCriteria criteria, DateRange dateRange, Enum eventType) {
        addEventDateRangeSearchCriteria(criteria, dateRange.getFrom(), dateRange.getTo(), eventType);
    }

    public static void addEventDateRangeSearchCriteria(DetachedCriteria criteria, Date fromDate, Date toDate, Enum eventType) {
        if (fromDate != null && toDate != null) {
            criteria.createAlias("events", "events");
            criteria.setFetchMode("events", FetchMode.SELECT);
            criteria.add(Restrictions.like("events.eventType", eventType));
            criteria.add(Restrictions.ge("events.createDateTime", DateUtils.getStartOfDay(fromDate)));
            criteria.add(Restrictions.le("events.createDateTime", DateUtils.getEndOfDay(toDate)));
        }
    }

    public static void addEventDateRangeSearchCriteria2(javax.persistence.criteria.CriteriaBuilder cb, List<Predicate> predicates, Root root, DateRange dateRange,
                                                        Enum eventType) {
        addEventDateRangeSearchCriteria2(cb, predicates, root, dateRange.getFrom(), dateRange.getTo(), eventType);
    }

    public static void addEventDateRangeSearchCriteria2(javax.persistence.criteria.CriteriaBuilder cb, List<Predicate> predicates, Root root, Date fromDate,
                                                        Date toDate, Enum eventType) {
        if (fromDate != null && toDate != null) {
            // Join the 'events' relationship
            Join<Object, Object> eventsJoin = root.join("events", JoinType.INNER);

            // Add predicates for event type, start and end dates
            Predicate eventTypePredicate = cb.equal(eventsJoin.get("eventType"), eventType);
            Predicate startDatePredicate = cb.greaterThanOrEqualTo(eventsJoin.get("createDateTime"), DateUtils.getStartOfDay(fromDate));
            Predicate endDatePredicate = cb.lessThanOrEqualTo(eventsJoin.get("createDateTime"), DateUtils.getEndOfDay(toDate));
            predicates.add(eventTypePredicate);
            predicates.add(startDatePredicate);
            predicates.add(endDatePredicate);
        }
    }

    /**
     * Adds a {@code Restrictions#like(String, Object)} to the {@code criteria}
     * supplied only if the supplied {@code value} is not null.
     *
     * @param criteria     The {@code DetachedCriteria} to apply the restriction to
     * @param propertyName The name of the property the restriction is on
     * @param value        The value of the applied restriction (if not null)
     */
    public static void addLikeRestriction(DetachedCriteria criteria, String propertyName, Object value) {
        if (value != null) {
            criteria.add(Restrictions.like(propertyName, value));
        }
    }

    /**
     * Adds a {@code Restrictions#ilike(String, Object)} to the {@code criteria}
     * supplied only if the supplied {@code value} is not null.
     *
     * TODO. Use addiLikeAnyRestriction. That is what all the callers are
     * currently doing by passing in %value% manually. Don't know how to easily
     * test ELC to make sure I haven't broken anything though.
     *
     * @param criteria     The {@code DetachedCriteria} to apply the restriction to
     * @param propertyName The name of the property the restriction is on
     * @param value        The value of the applied restriction (if not null)
     */
    @Deprecated
    public static void addiLikeRestriction(DetachedCriteria criteria, String propertyName, Object value) {
        if (value != null) {
            criteria.add(Restrictions.ilike(propertyName, value));
        }
    }

    public static void addiLikeAnyRestriction(DetachedCriteria criteria, String propertyName, String value) {
        if (value != null) {
            criteria.add(Restrictions.ilike(propertyName, value, MatchMode.ANYWHERE));
        }
    }

    /**
     * Adds a {@code Restrictions#eq(String, Object)} to the {@code criteria}
     * supplied only if the supplied {@code value} is not null.
     *
     * @param criteria     The {@code DetachedCriteria} to apply the restriction to
     * @param propertyName The name of the property the restriction is on
     * @param value        The value of the applied restriction (if not null)
     */
    public static void addEqRestriction(DetachedCriteria criteria, String propertyName, Object value) {
        if (value != null) {
            criteria.add(Restrictions.eq(propertyName, value));
        }
    }

    public static void addNotEqRestriction(DetachedCriteria criteria, String propertyName, Object value) {
        if (value != null) {
            criteria.add(Restrictions.not(Restrictions.eq(propertyName, value)));
        }
    }

    public static void addLeRestriction(DetachedCriteria criteria, String propertyName, Object value) {
        if (value != null) {
            criteria.add(Restrictions.le(propertyName, value));
        }
    }

    public static void addNestedEqRestriction(DetachedCriteria criteria, String nestedProperty, String nestedPropertyName, Object value) {
        if (value != null) {
            criteria.createCriteria(nestedProperty).add(Restrictions.eq(nestedPropertyName, value));
        }
    }

    public static void addEqOrNullRestriction(DetachedCriteria criteria, String propertyName, Object value) {
        if (value != null) {
            criteria.add(Restrictions.in(propertyName, new Object[]{value, null}));
        }
    }

    /**
     * Handle restrictions from the state parameter. The normal case is if
     * nothing is selected, you return everything in an active state. In
     * practice, the only inactive state is DELETED, but am being generic. If a
     * state is selected, you return that.
     *
     * @param <X>
     * @param criteria
     * @param propertyName   Should be 'state'
     * @param value          The state
     * @param inactiveStates The inactive states. Using negative logic, because
     *                       there is normally only one inactive.
     */
    public static <X extends Enum> void addEqActiveStateRestriction(DetachedCriteria criteria, String propertyName, X value, X... inactiveStates) {
        if (value != null) {
            criteria.add(Restrictions.eq(propertyName, value));
        } else {
            criteria.add(Restrictions.not(Restrictions.in(propertyName, inactiveStates)));
        }
    }

    /**
     * Adds a {@code Restrictions#eqProperty(String, String)} to the
     * {@code criteria}. This will be used in conjunction with a sql join. Both
     * {@code propertyName} and {@code otherPropertyName} must contain values.
     *
     * @param criteria          The {@code DetachedCriteria} to apply the restriction to
     * @param propertyName      The name of the first property the restriction is on
     * @param otherPropertyName The name of the second property the restriction
     *                          is on (on another table)
     */
    public static void addEqPropertyRestriction(DetachedCriteria criteria, String propertyName, String otherPropertyName) {
        if (propertyName != null && !propertyName.isEmpty() && otherPropertyName != null && !otherPropertyName.isEmpty()) {
            criteria.add(Restrictions.eqProperty(propertyName, otherPropertyName));
        }

    }

    public static void addExactMatch(DetachedCriteria criteria, String field, Object value) {
        if (value != null) {
            criteria.add(Restrictions.eq(field, value));
        } else {
            criteria.add(Restrictions.isNull(field));
        }
    }

    public static void addDisjunction(DetachedCriteria criteria, String field, Object value) {
        List<Object> values = value == null ? null : Collections.singletonList(value);
        addDisjunctionIn(criteria, field, values);
    }

    public static <X> void addDisjunctionIn(DetachedCriteria criteria, String field, List<X> values) {
        if (values != null && !values.isEmpty()) {
            //disjunction is the same as logical OR
            criteria.add(Restrictions.disjunction().add(Restrictions.isNull(field)).add(Restrictions.in(field, values)));
        } else {
            criteria.add(Restrictions.isNull(field));
        }
    }

    /**
     * only use when using sql restriction.
     *
     * @param s
     * @return
     */
    public static String ilikeSql(String s) {
        return " ilike '" + MatchMode.ANYWHERE.toMatchString(escape(s)) + "'";
    }

    public static String exactSql(String s) {

        return " ='" + MatchMode.EXACT.toMatchString(escape(s)) + "'";
    }

    private static String escape(String value) {
        return value
                .replace("%", "\\%")
                .replace("'", "''")
                .replace("_", "\\_");
    }
}