org.grails.orm.hibernate.query.AbstractHibernateQuery.java Source code

Java tutorial

Introduction

Here is the source code for org.grails.orm.hibernate.query.AbstractHibernateQuery.java

Source

/* Copyright (C) 2011 SpringSource
 *
 * 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.grails.orm.hibernate.query;

import java.lang.reflect.Field;
import java.util.Arrays;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

import javax.persistence.FetchType;

import org.grails.datastore.mapping.proxy.ProxyHandler;
import org.grails.orm.hibernate.AbstractHibernateSession;
import org.grails.orm.hibernate.IHibernateTemplate;
import org.grails.orm.hibernate.cfg.AbstractGrailsDomainBinder;
import org.grails.orm.hibernate.cfg.Mapping;
import org.grails.orm.hibernate.proxy.SimpleHibernateProxyHandler;
import org.grails.datastore.gorm.finders.DynamicFinder;
import org.grails.datastore.gorm.query.criteria.DetachedAssociationCriteria;
import org.grails.datastore.mapping.model.PersistentEntity;
import org.grails.datastore.mapping.model.PersistentProperty;
import org.grails.datastore.mapping.model.types.Association;
import org.grails.datastore.mapping.model.types.Embedded;
import org.grails.datastore.mapping.query.AssociationQuery;
import org.grails.datastore.mapping.query.Query;
import org.grails.datastore.mapping.query.api.QueryableCriteria;
import org.grails.datastore.mapping.query.criteria.FunctionCallingCriterion;
import org.hibernate.*;
import org.hibernate.criterion.*;
import org.hibernate.dialect.Dialect;
import org.hibernate.dialect.function.SQLFunction;
import org.hibernate.persister.entity.PropertyMapping;
import org.hibernate.type.BasicType;
import org.hibernate.type.TypeResolver;
import org.springframework.core.convert.ConversionService;
import org.springframework.core.convert.support.DefaultConversionService;
import org.springframework.dao.InvalidDataAccessApiUsageException;
import org.springframework.dao.InvalidDataAccessResourceUsageException;
import org.springframework.util.ReflectionUtils;

/**
 * Bridges the Query API with the Hibernate Criteria API
 *
 * @author Graeme Rocher
 * @since 1.0
 */
@SuppressWarnings("rawtypes")
public abstract class AbstractHibernateQuery extends Query {

    public static final String SIZE_CONSTRAINT_PREFIX = "Size";

    protected static final String ALIAS = "_alias";
    protected static ConversionService conversionService = new DefaultConversionService();
    protected static Field opField = ReflectionUtils.findField(SimpleExpression.class, "op");
    private static final Map<String, Boolean> JOIN_STATUS_CACHE = new ConcurrentHashMap<String, Boolean>();
    static {
        ReflectionUtils.makeAccessible(opField);
    }

    protected Criteria criteria;
    protected org.hibernate.criterion.DetachedCriteria detachedCriteria;
    protected AbstractHibernateQuery.HibernateProjectionList hibernateProjectionList;
    protected String alias;
    protected int aliasCount;
    protected Map<String, CriteriaAndAlias> createdAssociationPaths = new HashMap<String, CriteriaAndAlias>();
    protected LinkedList<String> aliasStack = new LinkedList<String>();
    protected LinkedList<PersistentEntity> entityStack = new LinkedList<PersistentEntity>();
    protected LinkedList<Association> associationStack = new LinkedList<Association>();
    protected LinkedList aliasInstanceStack = new LinkedList();
    private boolean hasJoins = false;
    protected ProxyHandler proxyHandler = new SimpleHibernateProxyHandler();
    protected final AbstractHibernateCriterionAdapter abstractHibernateCriterionAdapter;

    protected AbstractHibernateQuery(Criteria criteria, AbstractHibernateSession session, PersistentEntity entity) {
        super(session, entity);
        this.criteria = criteria;
        if (entity != null) {
            initializeJoinStatus();
        }
        this.abstractHibernateCriterionAdapter = createHibernateCriterionAdapter();
    }

    protected AbstractHibernateQuery(DetachedCriteria criteria, PersistentEntity entity) {
        super(null, entity);
        this.detachedCriteria = criteria;
        this.abstractHibernateCriterionAdapter = createHibernateCriterionAdapter();
    }

    @Override
    protected Object resolveIdIfEntity(Object value) {
        // for Hibernate queries, the object itself is used in queries, not the id
        return value;
    }

    private void initializeJoinStatus() {
        Boolean cachedStatus = JOIN_STATUS_CACHE.get(entity.getName());
        if (cachedStatus != null)
            hasJoins = cachedStatus;
        else {
            for (Association a : entity.getAssociations()) {
                if (a.getFetchStrategy() == FetchType.EAGER)
                    hasJoins = true;
            }
        }
    }

    protected AbstractHibernateQuery(Criteria subCriteria, AbstractHibernateSession session,
            PersistentEntity associatedEntity, String newAlias) {
        this(subCriteria, session, associatedEntity);
        alias = newAlias;
    }

    @Override
    public Query isEmpty(String property) {
        org.hibernate.criterion.Criterion criterion = Restrictions.isEmpty(calculatePropertyName(property));
        addToCriteria(criterion);
        return this;
    }

    @Override
    public Query isNotEmpty(String property) {
        addToCriteria(Restrictions.isNotEmpty(calculatePropertyName(property)));
        return this;
    }

    @Override
    public Query isNull(String property) {
        addToCriteria(Restrictions.isNull(calculatePropertyName(property)));
        return this;
    }

    @Override
    public Query isNotNull(String property) {
        addToCriteria(Restrictions.isNotNull(calculatePropertyName(property)));
        return this;
    }

    @Override
    public void add(Criterion criterion) {
        if (criterion instanceof FunctionCallingCriterion) {
            org.hibernate.criterion.Criterion sqlRestriction = getRestrictionForFunctionCall(
                    (FunctionCallingCriterion) criterion, getEntity());
            if (sqlRestriction != null) {
                addToCriteria(sqlRestriction);
            }
        } else if (criterion instanceof PropertyCriterion) {
            PropertyCriterion pc = (PropertyCriterion) criterion;
            Object value = pc.getValue();
            if (value instanceof QueryableCriteria) {
                setDetachedCriteriaValue((QueryableCriteria) value, pc);
            }
            // ignore Size related constraints
            else {
                doTypeConversionIfNeccessary(getEntity(), pc);
            }
        }
        if (criterion instanceof DetachedAssociationCriteria) {
            DetachedAssociationCriteria associationCriteria = (DetachedAssociationCriteria) criterion;

            Association association = associationCriteria.getAssociation();

            CriteriaAndAlias criteriaAndAlias = getCriteriaAndAlias(associationCriteria);

            if (criteriaAndAlias.criteria != null)
                aliasInstanceStack.add(criteriaAndAlias.criteria);
            else if (criteriaAndAlias.detachedCriteria != null)
                aliasInstanceStack.add(criteriaAndAlias.detachedCriteria);
            aliasStack.add(criteriaAndAlias.alias);
            associationStack.add(association);
            entityStack.add(association.getAssociatedEntity());

            try {
                @SuppressWarnings("unchecked")
                List<Criterion> associationCriteriaList = associationCriteria.getCriteria();
                for (Criterion c : associationCriteriaList) {
                    add(c);
                }
            } finally {
                aliasInstanceStack.removeLast();
                aliasStack.removeLast();
                entityStack.removeLast();
                associationStack.removeLast();
            }
        } else {

            final org.hibernate.criterion.Criterion hibernateCriterion = getHibernateCriterionAdapter()
                    .toHibernateCriterion(this, criterion, getCurrentAlias());
            if (hibernateCriterion != null) {
                addToCriteria(hibernateCriterion);
            }
        }
    }

    @Override
    public PersistentEntity getEntity() {
        if (!entityStack.isEmpty()) {
            return entityStack.getLast();
        }
        return super.getEntity();
    }

    protected String getAssociationPath(String propertyName) {
        if (propertyName.indexOf('.') > -1) {
            return propertyName;
        } else {

            StringBuilder fullPath = new StringBuilder();
            for (Association association : associationStack) {
                fullPath.append(association.getName());
                fullPath.append('.');
            }
            fullPath.append(propertyName);
            return fullPath.toString();
        }
    }

    protected String getCurrentAlias() {
        if (alias != null) {
            return alias;
        }

        if (aliasStack.isEmpty()) {
            return null;
        }

        return aliasStack.getLast();
    }

    @SuppressWarnings("unchecked")
    static void doTypeConversionIfNeccessary(PersistentEntity entity, PropertyCriterion pc) {
        if (pc.getClass().getSimpleName().startsWith(SIZE_CONSTRAINT_PREFIX)) {
            return;
        }

        String property = pc.getProperty();
        Object value = pc.getValue();
        PersistentProperty p = entity.getPropertyByName(property);
        if (p != null && !p.getType().isInstance(value)) {
            pc.setValue(conversionService.convert(value, p.getType()));
        }
    }

    org.hibernate.criterion.Criterion getRestrictionForFunctionCall(FunctionCallingCriterion criterion,
            PersistentEntity entity) {
        org.hibernate.criterion.Criterion sqlRestriction;

        SessionFactory sessionFactory = ((IHibernateTemplate) session.getNativeInterface()).getSessionFactory();
        String property = criterion.getProperty();
        Criterion datastoreCriterion = criterion.getPropertyCriterion();
        PersistentProperty pp = entity.getPropertyByName(property);

        if (pp == null)
            throw new InvalidDataAccessResourceUsageException(
                    "Cannot execute function defined in query [" + criterion.getFunctionName()
                            + "] on non-existent property [" + property + "] of [" + entity.getJavaClass() + "]");

        String functionName = criterion.getFunctionName();

        Dialect dialect = getDialect(sessionFactory);
        SQLFunction sqlFunction = dialect.getFunctions().get(functionName);
        if (sqlFunction != null) {
            TypeResolver typeResolver = getTypeResolver(sessionFactory);
            BasicType basic = typeResolver.basic(pp.getType().getName());
            if (basic != null && datastoreCriterion instanceof PropertyCriterion) {

                PropertyCriterion pc = (PropertyCriterion) datastoreCriterion;
                final org.hibernate.criterion.Criterion hibernateCriterion = getHibernateCriterionAdapter()
                        .toHibernateCriterion(this, datastoreCriterion, alias);
                if (hibernateCriterion instanceof SimpleExpression) {
                    SimpleExpression expr = (SimpleExpression) hibernateCriterion;
                    Object op = ReflectionUtils.getField(opField, expr);
                    PropertyMapping mapping = getEntityPersister(entity.getJavaClass().getName(), sessionFactory);
                    String[] columns;
                    if (alias != null) {
                        columns = mapping.toColumns(alias, property);
                    } else {
                        columns = mapping.toColumns(property);
                    }
                    String root = render(basic, Arrays.asList(columns), sessionFactory, sqlFunction);
                    Object value = pc.getValue();
                    if (value != null) {
                        sqlRestriction = Restrictions.sqlRestriction(root + op + "?", value,
                                typeResolver.basic(value.getClass().getName()));
                    } else {
                        sqlRestriction = Restrictions.sqlRestriction(root + op + "?", value, basic);
                    }
                } else {
                    throw new InvalidDataAccessResourceUsageException(
                            "Unsupported function [" + functionName + "] defined in query for property [" + property
                                    + "] with type [" + pp.getType() + "]");
                }
            } else {
                throw new InvalidDataAccessResourceUsageException("Unsupported function [" + functionName
                        + "] defined in query for property [" + property + "] with type [" + pp.getType() + "]");
            }
        } else {
            throw new InvalidDataAccessResourceUsageException(
                    "Unsupported function defined in query [" + functionName + "]");
        }
        return sqlRestriction;
    }

    protected abstract String render(BasicType basic, List<String> asList, SessionFactory sessionFactory,
            SQLFunction sqlFunction);

    protected abstract PropertyMapping getEntityPersister(String name, SessionFactory sessionFactory);

    protected abstract TypeResolver getTypeResolver(SessionFactory sessionFactory);

    protected abstract Dialect getDialect(SessionFactory sessionFactory);

    @Override
    public Junction disjunction() {
        final org.hibernate.criterion.Disjunction disjunction = Restrictions.disjunction();
        addToCriteria(disjunction);
        return new HibernateJunction(disjunction, alias);
    }

    @Override
    public Junction negation() {
        final org.hibernate.criterion.Disjunction disjunction = Restrictions.disjunction();
        addToCriteria(Restrictions.not(disjunction));
        return new HibernateJunction(disjunction, alias);
    }

    @Override
    public Query eq(String property, Object value) {
        addToCriteria(Restrictions.eq(calculatePropertyName(property), value));
        return this;
    }

    @Override
    public Query idEq(Object value) {
        addToCriteria(Restrictions.idEq(value));
        return this;
    }

    @Override
    public Query gt(String property, Object value) {
        addToCriteria(Restrictions.gt(calculatePropertyName(property), value));
        return this;
    }

    @Override
    public Query and(Criterion a, Criterion b) {
        AbstractHibernateCriterionAdapter adapter = getHibernateCriterionAdapter();
        addToCriteria(Restrictions.and(adapter.toHibernateCriterion(this, a, alias),
                adapter.toHibernateCriterion(this, a, alias)));
        return this;
    }

    @Override
    public Query or(Criterion a, Criterion b) {
        AbstractHibernateCriterionAdapter adapter = getHibernateCriterionAdapter();
        addToCriteria(Restrictions.or(adapter.toHibernateCriterion(this, a, alias),
                adapter.toHibernateCriterion(this, b, alias)));
        return this;
    }

    @Override
    public Query allEq(Map<String, Object> values) {
        addToCriteria(Restrictions.allEq(values));
        return this;
    }

    @Override
    public Query ge(String property, Object value) {
        addToCriteria(Restrictions.ge(calculatePropertyName(property), value));
        return this;
    }

    @Override
    public Query le(String property, Object value) {
        addToCriteria(Restrictions.le(calculatePropertyName(property), value));
        return this;
    }

    @Override
    public Query gte(String property, Object value) {
        addToCriteria(Restrictions.ge(calculatePropertyName(property), value));
        return this;
    }

    @Override
    public Query lte(String property, Object value) {
        addToCriteria(Restrictions.le(calculatePropertyName(property), value));
        return this;
    }

    @Override
    public Query lt(String property, Object value) {
        addToCriteria(Restrictions.lt(calculatePropertyName(property), value));
        return this;
    }

    @Override
    public Query in(String property, List values) {
        addToCriteria(Restrictions.in(calculatePropertyName(property), values));
        return this;
    }

    @Override
    public Query between(String property, Object start, Object end) {
        addToCriteria(Restrictions.between(calculatePropertyName(property), start, end));
        return this;
    }

    @Override
    public Query like(String property, String expr) {
        addToCriteria(Restrictions.like(calculatePropertyName(property), calculatePropertyName(expr)));
        return this;
    }

    @Override
    public Query ilike(String property, String expr) {
        addToCriteria(Restrictions.ilike(calculatePropertyName(property), calculatePropertyName(expr)));
        return this;
    }

    @Override
    public Query rlike(String property, String expr) {
        addToCriteria(createRlikeExpression(calculatePropertyName(property), calculatePropertyName(expr)));
        return this;
    }

    @Override
    public AssociationQuery createQuery(String associationName) {
        final PersistentProperty property = entity.getPropertyByName(calculatePropertyName(associationName));
        if (property != null && (property instanceof Association)) {
            String alias = generateAlias(associationName);
            CriteriaAndAlias subCriteria = getOrCreateAlias(associationName, alias);

            Association association = (Association) property;
            if (subCriteria.criteria != null) {
                return new HibernateAssociationQuery(subCriteria.criteria, (AbstractHibernateSession) getSession(),
                        association.getAssociatedEntity(), association, alias);
            } else if (subCriteria.detachedCriteria != null) {
                return new HibernateAssociationQuery(subCriteria.detachedCriteria,
                        (AbstractHibernateSession) getSession(), association.getAssociatedEntity(), association,
                        alias);
            }
        }
        throw new InvalidDataAccessApiUsageException(
                "Cannot query association [" + calculatePropertyName(associationName) + "] of entity [" + entity
                        + "]. Property is not an association!");
    }

    protected CriteriaAndAlias getCriteriaAndAlias(DetachedAssociationCriteria associationCriteria) {
        String associationPath = associationCriteria.getAssociationPath();
        String alias = associationCriteria.getAlias();

        if (associationPath == null) {
            associationPath = associationCriteria.getAssociation().getName();
        }
        return getOrCreateAlias(associationPath, alias);
    }

    protected CriteriaAndAlias getOrCreateAlias(String associationName, String alias) {
        CriteriaAndAlias subCriteria = null;
        String associationPath = getAssociationPath(associationName);
        Criteria parentCriteria = criteria;
        if (alias == null) {
            alias = generateAlias(associationName);
        } else {
            CriteriaAndAlias criteriaAndAlias = createdAssociationPaths.get(alias);
            if (criteriaAndAlias != null) {
                parentCriteria = criteriaAndAlias.criteria;
                if (parentCriteria != null) {

                    alias = associationName + '_' + alias;
                    associationPath = criteriaAndAlias.associationPath + '.' + associationPath;
                }
            }
        }
        if (createdAssociationPaths.containsKey(associationName)) {
            subCriteria = createdAssociationPaths.get(associationPath);
        } else {
            if (parentCriteria != null) {
                Criteria sc = parentCriteria.createAlias(associationPath, alias);
                subCriteria = new CriteriaAndAlias(sc, alias, associationPath);
            } else if (detachedCriteria != null) {
                DetachedCriteria sc = detachedCriteria.createAlias(associationPath, alias);
                subCriteria = new CriteriaAndAlias(sc, alias, associationPath);
            }
            if (subCriteria != null) {

                createdAssociationPaths.put(associationPath, subCriteria);
                createdAssociationPaths.put(alias, subCriteria);
            }
        }
        return subCriteria;
    }

    @Override
    public ProjectionList projections() {
        if (hibernateProjectionList == null) {
            hibernateProjectionList = new HibernateProjectionList();
        }
        return hibernateProjectionList;
    }

    @Override
    public Query max(int max) {
        if (criteria != null)
            criteria.setMaxResults(max);
        return this;
    }

    @Override
    public Query maxResults(int max) {
        if (criteria != null)
            criteria.setMaxResults(max);
        return this;
    }

    @Override
    public Query offset(int offset) {
        if (criteria != null)
            criteria.setFirstResult(offset);
        return this;
    }

    @Override
    public Query firstResult(int offset) {
        offset(offset);
        return this;
    }

    @Override
    public Query cache(boolean cache) {
        criteria.setCacheable(true);

        return super.cache(cache);
    }

    @Override
    public Query lock(boolean lock) {
        criteria.setCacheable(false);
        criteria.setLockMode(LockMode.PESSIMISTIC_WRITE);
        return super.lock(lock);
    }

    @Override
    public Query order(Order order) {
        super.order(order);

        String property = order.getProperty();

        int i = property.indexOf('.');
        if (i > -1) {

            String sortHead = property.substring(0, i);
            String sortTail = property.substring(i + 1);

            if (createdAssociationPaths.containsKey(sortHead)) {
                CriteriaAndAlias criteriaAndAlias = createdAssociationPaths.get(sortHead);
                Criteria criteria = criteriaAndAlias.criteria;
                org.hibernate.criterion.Order hibernateOrder = order.getDirection() == Order.Direction.ASC
                        ? org.hibernate.criterion.Order.asc(property)
                        : org.hibernate.criterion.Order.desc(property);

                criteria.addOrder(order.isIgnoreCase() ? hibernateOrder.ignoreCase() : hibernateOrder);
            } else {

                PersistentProperty persistentProperty = entity.getPropertyByName(sortHead);

                if (persistentProperty instanceof Association) {
                    Association a = (Association) persistentProperty;
                    if (persistentProperty instanceof Embedded) {
                        addSimpleOrder(order, property);
                    } else {
                        if (criteria != null) {
                            Criteria subCriteria = criteria.createCriteria(sortHead);
                            addOrderToCriteria(subCriteria, sortTail, order);
                        } else if (detachedCriteria != null) {
                            DetachedCriteria subDetachedCriteria = detachedCriteria.createCriteria(sortHead);
                            addOrderToDetachedCriteria(subDetachedCriteria, sortTail, order);
                        }
                    }
                }
            }

        } else {
            addSimpleOrder(order, property);
        }

        return this;
    }

    private void addSimpleOrder(Order order, String property) {
        Criteria c = criteria;
        if (c != null) {
            addOrderToCriteria(c, property, order);
        } else {
            DetachedCriteria dc = detachedCriteria;
            addOrderToDetachedCriteria(dc, property, order);
        }
    }

    private void addOrderToDetachedCriteria(DetachedCriteria dc, String property, Order order) {
        if (dc != null) {
            org.hibernate.criterion.Order hibernateOrder = order.getDirection() == Order.Direction.ASC
                    ? org.hibernate.criterion.Order.asc(calculatePropertyName(property))
                    : org.hibernate.criterion.Order.desc(calculatePropertyName(property));
            dc.addOrder(order.isIgnoreCase() ? hibernateOrder.ignoreCase() : hibernateOrder);

        }
    }

    private void addOrderToCriteria(Criteria c, String property, Order order) {
        org.hibernate.criterion.Order hibernateOrder = order.getDirection() == Order.Direction.ASC
                ? org.hibernate.criterion.Order.asc(calculatePropertyName(property))
                : org.hibernate.criterion.Order.desc(calculatePropertyName(property));

        c.addOrder(order.isIgnoreCase() ? hibernateOrder.ignoreCase() : hibernateOrder);
    }

    @Override
    public Query join(String property) {
        this.hasJoins = true;
        if (criteria != null)
            criteria.setFetchMode(property, FetchMode.JOIN);
        else if (detachedCriteria != null)
            detachedCriteria.setFetchMode(property, FetchMode.JOIN);
        return this;
    }

    @Override
    public Query select(String property) {
        this.hasJoins = true;
        if (criteria != null)
            criteria.setFetchMode(property, FetchMode.SELECT);
        else if (detachedCriteria != null)
            detachedCriteria.setFetchMode(property, FetchMode.SELECT);
        return this;
    }

    @Override
    public List list() {
        if (criteria == null)
            throw new IllegalStateException("Cannot execute query using a detached criteria instance");

        int projectionLength = 0;
        if (hibernateProjectionList != null) {
            org.hibernate.criterion.ProjectionList projectionList = hibernateProjectionList
                    .getHibernateProjectionList();
            projectionLength = projectionList.getLength();
            if (projectionLength > 0) {
                criteria.setProjection(projectionList);
            }
        }

        if (projectionLength < 2) {
            criteria.setResultTransformer(CriteriaSpecification.DISTINCT_ROOT_ENTITY);
        }

        applyDefaultSortOrderAndCaching();
        applyFetchStrategies();

        return criteria.list();
    }

    protected void applyDefaultSortOrderAndCaching() {
        if (this.orderBy.isEmpty() && entity != null) {
            // don't apply default sorting, if projections present
            if (hibernateProjectionList != null && !hibernateProjectionList.isEmpty())
                return;

            Mapping mapping = AbstractGrailsDomainBinder.getMapping(entity.getJavaClass());
            if (mapping != null) {
                if (queryCache == null && mapping.getCache() != null && mapping.getCache().isEnabled()) {
                    criteria.setCacheable(true);
                }

                Map sortMap = mapping.getSort().getNamesAndDirections();
                DynamicFinder.applySortForMap(this, sortMap, true);

            }
        }
    }

    protected void applyFetchStrategies() {
        for (Map.Entry<String, FetchType> entry : fetchStrategies.entrySet()) {
            switch (entry.getValue()) {
            case EAGER:
                if (criteria != null)
                    criteria.setFetchMode(entry.getKey(), FetchMode.JOIN);
                else if (detachedCriteria != null)
                    detachedCriteria.setFetchMode(entry.getKey(), FetchMode.JOIN);
                break;
            case LAZY:
                if (criteria != null)
                    criteria.setFetchMode(entry.getKey(), FetchMode.SELECT);
                else if (detachedCriteria != null)
                    detachedCriteria.setFetchMode(entry.getKey(), FetchMode.SELECT);
                break;
            }
        }
    }

    @Override
    protected void flushBeforeQuery() {
        // do nothing
    }

    @Override
    public Object singleResult() {
        if (criteria == null)
            throw new IllegalStateException("Cannot execute query using a detached criteria instance");

        if (hibernateProjectionList != null) {
            criteria.setProjection(hibernateProjectionList.getHibernateProjectionList());
        }
        criteria.setResultTransformer(CriteriaSpecification.DISTINCT_ROOT_ENTITY);
        applyDefaultSortOrderAndCaching();
        applyFetchStrategies();

        if (hasJoins) {
            try {
                return proxyHandler.unwrap(criteria.uniqueResult());
            } catch (NonUniqueResultException e) {
                return singleResultViaListCall();
            }
        } else {
            return singleResultViaListCall();
        }
    }

    private Object singleResultViaListCall() {
        criteria.setMaxResults(1);
        List results = criteria.list();
        if (results.size() > 0) {
            return proxyHandler.unwrap(results.get(0));
        }
        return null;
    }

    @Override
    protected List executeQuery(PersistentEntity entity, Junction criteria) {
        return list();
    }

    String handleAssociationQuery(Association<?> association, List<Criterion> criteriaList) {
        return getCriteriaAndAlias(association).alias;
    }

    String handleAssociationQuery(Association<?> association, List<Criterion> criteriaList, String alias) {
        String associationName = calculatePropertyName(association.getName());
        return getOrCreateAlias(associationName, alias).alias;
    }

    protected CriteriaAndAlias getCriteriaAndAlias(Association<?> association) {
        String associationName = calculatePropertyName(association.getName());
        String newAlias = generateAlias(associationName);
        return getOrCreateAlias(associationName, newAlias);
    }

    protected void addToCriteria(org.hibernate.criterion.Criterion criterion) {
        if (criterion == null) {
            return;
        }

        if (aliasInstanceStack.isEmpty()) {
            if (criteria != null) {
                criteria.add(criterion);

            } else if (detachedCriteria != null) {
                detachedCriteria.add(criterion);
            }
        } else {
            Object criteriaObject = aliasInstanceStack.getLast();
            if (criteriaObject instanceof Criteria)
                ((Criteria) criteriaObject).add(criterion);
            else if (criteriaObject instanceof DetachedCriteria) {
                ((DetachedCriteria) criteriaObject).add(criterion);
            }
        }
    }

    protected String calculatePropertyName(String property) {
        if (alias == null) {
            return property;
        }
        return alias + '.' + property;
    }

    protected String generateAlias(String associationName) {
        return calculatePropertyName(associationName) + calculatePropertyName(ALIAS) + aliasCount++;
    }

    protected abstract void setDetachedCriteriaValue(QueryableCriteria value, PropertyCriterion pc);

    protected AbstractHibernateCriterionAdapter getHibernateCriterionAdapter() {
        return this.abstractHibernateCriterionAdapter;
    }

    protected abstract AbstractHibernateCriterionAdapter createHibernateCriterionAdapter();

    protected abstract org.hibernate.criterion.Criterion createRlikeExpression(String propertyName, String value);

    protected class HibernateJunction extends Junction {

        protected org.hibernate.criterion.Junction hibernateJunction;
        protected String alias;

        public HibernateJunction(org.hibernate.criterion.Junction junction, String alias) {
            hibernateJunction = junction;
            this.alias = alias;
        }

        @Override
        public Junction add(Criterion c) {
            if (c != null) {
                if (c instanceof FunctionCallingCriterion) {
                    org.hibernate.criterion.Criterion sqlRestriction = getRestrictionForFunctionCall(
                            (FunctionCallingCriterion) c, entity);
                    if (sqlRestriction != null) {
                        hibernateJunction.add(sqlRestriction);
                    }
                } else {
                    AbstractHibernateCriterionAdapter adapter = getHibernateCriterionAdapter();
                    org.hibernate.criterion.Criterion criterion = adapter
                            .toHibernateCriterion(AbstractHibernateQuery.this, c, alias);
                    if (criterion != null) {
                        hibernateJunction.add(criterion);
                    }
                }
            }
            return this;
        }
    }

    protected class HibernateProjectionList extends ProjectionList {

        org.hibernate.criterion.ProjectionList projectionList = Projections.projectionList();

        public org.hibernate.criterion.ProjectionList getHibernateProjectionList() {
            return projectionList;
        }

        @Override
        public boolean isEmpty() {
            return projectionList.getLength() == 0;
        }

        @Override
        public ProjectionList add(Projection p) {
            projectionList.add(new HibernateProjectionAdapter(p).toHibernateProjection());
            return this;
        }

        @Override
        public org.grails.datastore.mapping.query.api.ProjectionList countDistinct(String property) {
            projectionList.add(Projections.countDistinct(calculatePropertyName(property)));
            return this;
        }

        @Override
        public org.grails.datastore.mapping.query.api.ProjectionList distinct(String property) {
            projectionList.add(Projections.distinct(Projections.property(calculatePropertyName(property))));
            return this;
        }

        @Override
        public org.grails.datastore.mapping.query.api.ProjectionList rowCount() {
            projectionList.add(Projections.rowCount());
            return this;
        }

        @Override
        public ProjectionList id() {
            projectionList.add(Projections.id());
            return this;
        }

        @Override
        public ProjectionList count() {
            projectionList.add(Projections.rowCount());
            return this;
        }

        @Override
        public ProjectionList property(String name) {
            projectionList.add(Projections.property(calculatePropertyName(name)));
            return this;
        }

        @Override
        public ProjectionList sum(String name) {
            projectionList.add(Projections.sum(calculatePropertyName(name)));
            return this;
        }

        @Override
        public ProjectionList min(String name) {
            projectionList.add(Projections.min(calculatePropertyName(name)));
            return this;
        }

        @Override
        public ProjectionList max(String name) {
            projectionList.add(Projections.max(calculatePropertyName(name)));
            return this;
        }

        @Override
        public ProjectionList avg(String name) {
            projectionList.add(Projections.avg(calculatePropertyName(name)));
            return this;
        }

        @Override
        public ProjectionList distinct() {
            if (criteria != null)
                criteria.setResultTransformer(Criteria.DISTINCT_ROOT_ENTITY);
            else if (detachedCriteria != null)
                detachedCriteria.setResultTransformer(Criteria.DISTINCT_ROOT_ENTITY);
            return this;
        }
    }

    protected class HibernateAssociationQuery extends AssociationQuery {

        protected String alias;
        protected org.hibernate.criterion.Junction hibernateJunction;
        protected Criteria assocationCriteria;
        protected DetachedCriteria detachedAssocationCriteria;

        public HibernateAssociationQuery(Criteria criteria, AbstractHibernateSession session,
                PersistentEntity associatedEntity, Association association, String alias) {
            super(session, associatedEntity, association);
            this.alias = alias;
            assocationCriteria = criteria;
        }

        public HibernateAssociationQuery(DetachedCriteria criteria, AbstractHibernateSession session,
                PersistentEntity associatedEntity, Association association, String alias) {
            super(session, associatedEntity, association);
            this.alias = alias;
            detachedAssocationCriteria = criteria;
        }

        @Override
        public Query order(Order order) {

            Order.Direction direction = order.getDirection();
            switch (direction) {
            case ASC:
                assocationCriteria.addOrder(org.hibernate.criterion.Order.asc(order.getProperty()));
            case DESC:
                assocationCriteria.addOrder(org.hibernate.criterion.Order.desc(order.getProperty()));
            }
            return super.order(order);
        }

        @Override
        public Query isEmpty(String property) {
            org.hibernate.criterion.Criterion criterion = Restrictions.isEmpty(calculatePropertyName(property));
            addToCriteria(criterion);
            return this;
        }

        protected void addToCriteria(org.hibernate.criterion.Criterion criterion) {
            if (hibernateJunction != null) {
                hibernateJunction.add(criterion);
            } else if (assocationCriteria != null) {
                assocationCriteria.add(criterion);
            } else if (detachedAssocationCriteria != null) {
                detachedAssocationCriteria.add(criterion);
            }
        }

        @Override
        public Query isNotEmpty(String property) {
            addToCriteria(Restrictions.isNotEmpty(calculatePropertyName(property)));
            return this;
        }

        @Override
        public Query isNull(String property) {
            addToCriteria(Restrictions.isNull(calculatePropertyName(property)));
            return this;
        }

        @Override
        public Query isNotNull(String property) {
            addToCriteria(Restrictions.isNotNull(calculatePropertyName(property)));
            return this;
        }

        @Override
        public void add(Criterion criterion) {
            final org.hibernate.criterion.Criterion hibernateCriterion = getHibernateCriterionAdapter()
                    .toHibernateCriterion(AbstractHibernateQuery.this, criterion, alias);
            if (hibernateCriterion != null) {
                addToCriteria(hibernateCriterion);
            }
        }

        @Override
        public Junction disjunction() {
            final org.hibernate.criterion.Disjunction disjunction = Restrictions.disjunction();
            addToCriteria(disjunction);
            return new HibernateJunction(disjunction, alias);
        }

        @Override
        public Junction negation() {
            final org.hibernate.criterion.Disjunction disjunction = Restrictions.disjunction();
            addToCriteria(Restrictions.not(disjunction));
            return new HibernateJunction(disjunction, alias);
        }

        @Override
        public Query eq(String property, Object value) {
            addToCriteria(Restrictions.eq(calculatePropertyName(property), value));
            return this;
        }

        @Override
        public Query idEq(Object value) {
            addToCriteria(Restrictions.idEq(value));
            return this;
        }

        @Override
        public Query gt(String property, Object value) {
            addToCriteria(Restrictions.gt(calculatePropertyName(property), value));
            return this;
        }

        @Override
        public Query and(Criterion a, Criterion b) {
            AbstractHibernateCriterionAdapter adapter = getHibernateCriterionAdapter();
            addToCriteria(Restrictions.and(adapter.toHibernateCriterion(AbstractHibernateQuery.this, a, alias),
                    adapter.toHibernateCriterion(AbstractHibernateQuery.this, b, alias)));
            return this;
        }

        @Override
        public Query or(Criterion a, Criterion b) {
            AbstractHibernateCriterionAdapter adapter = getHibernateCriterionAdapter();
            addToCriteria(Restrictions.or(adapter.toHibernateCriterion(AbstractHibernateQuery.this, a, alias),
                    adapter.toHibernateCriterion(AbstractHibernateQuery.this, b, alias)));
            return this;
        }

        @Override
        public Query allEq(Map<String, Object> values) {
            addToCriteria(Restrictions.allEq(values));
            return this;
        }

        @Override
        public Query ge(String property, Object value) {
            addToCriteria(Restrictions.ge(calculatePropertyName(property), value));
            return this;
        }

        @Override
        public Query le(String property, Object value) {
            addToCriteria(Restrictions.le(calculatePropertyName(property), value));
            return this;
        }

        @Override
        public Query gte(String property, Object value) {
            addToCriteria(Restrictions.ge(calculatePropertyName(property), value));
            return this;
        }

        @Override
        public Query lte(String property, Object value) {
            addToCriteria(Restrictions.le(calculatePropertyName(property), value));
            return this;
        }

        @Override
        public Query lt(String property, Object value) {
            addToCriteria(Restrictions.lt(calculatePropertyName(property), value));
            return this;
        }

        @Override
        public Query in(String property, List values) {
            addToCriteria(Restrictions.in(calculatePropertyName(property), values));
            return this;
        }

        @Override
        public Query between(String property, Object start, Object end) {
            addToCriteria(Restrictions.between(calculatePropertyName(property), start, end));
            return this;
        }

        @Override
        public Query like(String property, String expr) {
            addToCriteria(Restrictions.like(calculatePropertyName(property), calculatePropertyName(expr)));
            return this;
        }

        @Override
        public Query ilike(String property, String expr) {
            addToCriteria(Restrictions.ilike(calculatePropertyName(property), calculatePropertyName(expr)));
            return this;
        }

        @Override
        public Query rlike(String property, String expr) {
            addToCriteria(createRlikeExpression(calculatePropertyName(property), calculatePropertyName(expr)));
            return this;
        }
    }

    protected class CriteriaAndAlias {
        protected DetachedCriteria detachedCriteria;
        protected Criteria criteria;
        protected String alias;
        protected String associationPath;

        public CriteriaAndAlias(DetachedCriteria detachedCriteria, String alias, String associationPath) {
            this.detachedCriteria = detachedCriteria;
            this.alias = alias;
            this.associationPath = associationPath;
        }

        public CriteriaAndAlias(Criteria criteria, String alias, String associationPath) {
            this.criteria = criteria;
            this.alias = alias;
            this.associationPath = associationPath;
        }
    }
}