edu.harvard.med.screensaver.db.hqlbuilder.HqlBuilder.java Source code

Java tutorial

Introduction

Here is the source code for edu.harvard.med.screensaver.db.hqlbuilder.HqlBuilder.java

Source

// $HeadURL$
// $Id$
//
// Copyright  2006, 2010, 2011, 2012 by the President and Fellows of Harvard College.
//
// Screensaver is an open-source project developed by the ICCB-L and NSRB labs
// at Harvard Medical School. This software is distributed under the terms of
// the GNU General Public License.

package edu.harvard.med.screensaver.db.hqlbuilder;

import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

import com.google.common.base.Joiner;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
import org.apache.log4j.Logger;
import org.hibernate.Query;
import org.hibernate.Session;
import org.hibernate.transform.DistinctRootEntityResultTransformer;

import edu.harvard.med.screensaver.db.Criterion.Operator;
import edu.harvard.med.screensaver.db.SortDirection;
import edu.harvard.med.screensaver.model.Entity;
import edu.harvard.med.screensaver.model.meta.PropertyPath;
import edu.harvard.med.screensaver.model.meta.RelationshipPath;
import edu.harvard.med.screensaver.util.StringUtils;

// TODO: would be beneficial to refactor this class such that query args are not permanently
// set in the query that is built; instead the built query should allow different arg values
// to be applied each time toQuery() is invoked
public class HqlBuilder {
    // static members

    private static Logger log = Logger.getLogger(HqlBuilder.class);

    static final String SET_ARG_SUFFIX = "Set";

    // inner classes

    private boolean _isDistinct = false;
    private boolean _isDistinctRootEntities = false;
    private StringBuilder _select = new StringBuilder();
    private StringBuilder _from = new StringBuilder();
    private CompositePredicate _where = new Conjunction();
    private CompositePredicate _having = new Conjunction();
    private List<String> _orderBy = Lists.newArrayList();
    private List<String> _groupBy = Lists.newArrayList();
    private Map<String, Object> _args = new HashMap<String, Object>();
    private Set<String> _aliases = new HashSet<String>();
    private Set<String> _selectAliases = new HashSet<String>();

    // public constructors & methods

    public HqlBuilder() {
    }

    public HqlBuilder distinctProjectionValues() {
        _isDistinct = true;
        return this;
    }

    public HqlBuilder distinctRootEntities() {
        _isDistinctRootEntities = true;
        return this;
    }

    /**
     * Select a full entity to retrieved.
     * @param alias
     */
    public HqlBuilder select(String alias) {
        return select(alias, null);
    }

    public HqlBuilder selectExpression(String expression) {
        if (_select.length() > 0) {
            _select.append(", ");
        }
        _select.append(expression);
        return this;
    }

    public HqlBuilder select(String alias, String property) {
        _selectAliases.add(alias);
        if (_select.length() > 0) {
            _select.append(", ");
        }
        _select.append(alias);
        if (property != null) {
            _select.append('.').append(property);
        } // else, select the full entity
        return this;
    }

    public HqlBuilder from(Class<?> entityClass, String alias) {
        checkAliasIsUnique(alias);
        if (_from.length() > 0) {
            _from.append(", ");
        }
        _from.append(entityName(entityClass)).append(' ').append(alias);
        return this;
    }

    public HqlBuilder fromFetch(String joinAlias, RelationshipPath joinRelationship, String alias) {
        return from(joinAlias, joinRelationship, alias, JoinType.LEFT_FETCH);
    }

    public HqlBuilder from(String joinAlias, RelationshipPath joinRelationship, String alias) {
        return from(joinAlias, joinRelationship, alias, JoinType.LEFT);
    }

    public HqlBuilder from(String joinAlias, RelationshipPath joinRelationship, String alias, JoinType joinType) {
        //    if (joinRelationship.getPathLength() > 1) {
        //      throw new IllegalArgumentException("joinRelationship length must be 1");
        //    }
        if (joinRelationship instanceof PropertyPath) {
            if (!((PropertyPath) joinRelationship).isCollectionOfValues()) {
                throw new IllegalArgumentException(
                        "joinRelationships that are PropertyPaths must be a 'collection of values'");
            }
            return from(joinAlias, ((PropertyPath) joinRelationship).getPropertyName(), alias, joinType);
        }
        return from(joinAlias, joinRelationship.getLeaf(), alias, joinType);
    }

    private HqlBuilder from(String joinAlias, String joinRelationship, String alias, JoinType joinType) {
        checkAliasExists(joinAlias);
        checkAliasIsUnique(alias);
        if (_from.length() == 0) {
            throw new IllegalStateException("must call from(Class, String) first");
        }
        if (joinType == JoinType.LEFT) {
            _from.append(" left join ");
        } else if (joinType == JoinType.LEFT_FETCH) {
            _from.append(" left join fetch ");
        } else {
            _from.append(" join ");
        }
        _from.append(joinAlias).append('.').append(joinRelationship).append(' ').append(alias);
        return this;
    }

    public HqlBuilder restrictFrom(String alias, String property, Operator operator, Object value) {
        checkAliasExists(alias);
        _from.append(" with ").append(new SimplePredicate(this, makeRef(alias, property), operator, value).toHql());
        return this;
    }

    public HqlBuilder where(String alias, String property, Operator operator, Object value) {
        checkAliasExists(alias);
        _where.add(simplePredicate(makeRef(alias, property), operator, value));
        return this;
    }

    /**
     * Use when lhs of where expression is a collection of values (which
     * themselves have no internal property other than the value itself).
     */
    public HqlBuilder where(String alias, Operator operator, Object value) {
        return where(alias, null, operator, value);
    }

    public HqlBuilder where(String alias1, String property1, Operator operator, String alias2, String property2) {
        checkAliasExists(alias1);
        checkAliasExists(alias2);
        _where.add(simplePredicate(makeRef(alias1, property1), makeRef(alias2, property2), operator));
        return this;
    }

    /**
     * Use when lhs and rhs of where expression is a collection of values (which
     * themselves have no internal property other than the value itself).
     */
    public HqlBuilder where(String alias1, Operator operator, String alias2) {
        return where(alias1, null, operator, alias2, null);
    }

    /**
     * Use when the rhs of where expression is an entity object, in which case
     * Hibernate will use the entity's ID.
     */
    public HqlBuilder where(String alias, Entity entity) {
        checkAliasExists(alias);
        _where.add(simplePredicate(alias, Operator.EQUAL, entity));
        return this;
    }

    public HqlBuilder whereIn(String alias, String property, Set<?> values) {
        checkAliasExists(alias);
        if (values.size() == 0) {
            _where.add(SimplePredicate.FALSE);
        } else {
            _where.add(new SimplePredicate(this, makeRef(alias, property), values));
        }
        return this;
    }

    /**
     * Use when the lhs of where..in expression is a collection of values (which
     * themselves have no internal property other than the value itself).
     */
    public HqlBuilder whereIn(String alias, Set<?> values) {
        return whereIn(alias, null, values);
    }

    public HqlBuilder where(Predicate predicate) {
        _where.add(predicate);
        return this;
    }

    public Disjunction disjunction() {
        return new Disjunction();
    }

    public Conjunction conjunction() {
        return new Conjunction();
    }

    public SimplePredicate simplePredicate(String lhs, Operator operator, Object value) {
        return new SimplePredicate(this, lhs, operator, value);
    }

    public SimplePredicate simplePredicate(String lhs, String property, Operator operator, Object value) {
        return new SimplePredicate(this, makeRef(lhs, property), operator, value);
    }

    public SimplePredicate simplePredicate(String lhs, String rhs, Operator operator) {
        return new SimplePredicate(this, lhs, rhs, operator);
    }

    public HqlBuilder orderBy(String alias, String property) {
        return orderBy(alias, property, SortDirection.ASCENDING);
    }

    /**
     * Use when you need to order by a collection of values (which
     * themselves have no internal property other than the value itself).
     */
    public HqlBuilder orderBy(String alias, SortDirection sortDirection) {
        return orderBy(alias, null, sortDirection);
    }

    public HqlBuilder orderBy(String alias, String property, SortDirection sortDirection) {
        checkAliasExists(alias);
        StringBuilder orderBy = new StringBuilder(alias);
        if (!!!StringUtils.isEmpty(property)) {
            orderBy.append('.').append(property);
        }
        if (sortDirection == SortDirection.DESCENDING) {
            orderBy.append(" desc");
        }
        _orderBy.add(orderBy.toString());
        return this;
    }

    public String toHql() {
        StringBuilder _hql = new StringBuilder();
        if (!_aliases.containsAll(_selectAliases)) {
            Set<String> _undefinedSelectAliases = new HashSet<String>(_selectAliases);
            _undefinedSelectAliases.removeAll(_aliases);
            throw new RuntimeException("select aliases " + _undefinedSelectAliases + " undefined");
        }
        if (_select.length() > 0) {
            _hql.append("select ");
            if (_isDistinct) {
                _hql.append("distinct ");
            }
            _hql.append(_select).append(' ');
        }
        if (_from.length() == 0) {
            throw new RuntimeException("empty from clause");
        }
        _hql.append("from ").append(_from);

        if (_where.size() > 0) {
            _hql.append(" where ").append(_where.toHql());
        }

        if (!!!_groupBy.isEmpty()) {
            // note: if we're using "group by", we also need to explicitly group by
            // any fields that we're ordering on, and these need to be first if
            // we're going to respect the requested ordering
            // TODO: as convenience, we could also group on any select fields that are non-aggregate expressions 
            List<String> nonRedundantGroupBy = Lists.newArrayList(_groupBy);
            nonRedundantGroupBy.removeAll(_orderBy);
            _hql.append(" group by ").append(Joiner.on(", ").join(Iterables.concat(_orderBy, nonRedundantGroupBy)));
        }
        if (_having.size() > 0) {
            _hql.append(" having ").append(_having.toHql());
        }
        if (!!!_orderBy.isEmpty()) {
            _hql.append(" order by ").append(Joiner.on(", ").join(_orderBy));
        }
        return _hql.toString();
    }

    public Map<String, Object> args() {
        return _args;
    }

    public Object arg(String name) {
        return _args.get(name);
    }

    public Query toQuery(Session session, boolean isReadOnly) {
        org.hibernate.Query query = session.createQuery(toHql());
        for (Map.Entry<String, Object> arg : args().entrySet()) {
            if (arg.getKey().endsWith(SET_ARG_SUFFIX)) {
                // HACK: handle 'list' type parameters, used with the 'IN (?)' operator
                query.setParameterList(arg.getKey(), (Set<?>) arg.getValue());
            } else {
                query.setParameter(arg.getKey(), arg.getValue());
            }
        }
        if (_isDistinctRootEntities) {
            query.setResultTransformer(DistinctRootEntityResultTransformer.INSTANCE);
        }
        query.setReadOnly(isReadOnly);
        return query;
    }

    @Override
    public String toString() {
        return toHql() + " " + args();
    }

    // private methods

    private String entityName(Class<?> entityClass) {
        return entityClass.getSimpleName();
    }

    private void checkAliasExists(String alias) {
        if (!_aliases.contains(alias)) {
            throw new IllegalArgumentException("alias " + alias + " not defined");
        }
    }

    private void checkAliasIsUnique(String alias) {
        if (!_aliases.add(alias)) {
            throw new IllegalArgumentException("alias " + alias + " is already used");
        }
    }

    private String makeRef(String alias, String property) {
        if (!!!StringUtils.isEmpty(property)) {
            return alias + "." + property;
        }
        return alias;
    }

    public HqlBuilder groupBy(String alias) {
        return groupBy(alias, null);
    }

    public HqlBuilder groupBy(String alias, String property) {
        checkAliasExists(alias);
        StringBuilder groupBy = new StringBuilder(alias);
        if (!!!StringUtils.isEmpty(property)) {
            groupBy.append('.').append(property);
        }
        _groupBy.add(groupBy.toString());
        return this;
    }

    public HqlBuilder having(Predicate havingPredicate) {
        _having.add(havingPredicate);
        return this;
    }
}