org.hibernate.loader.criteria.CriteriaQueryTranslator.java Source code

Java tutorial

Introduction

Here is the source code for org.hibernate.loader.criteria.CriteriaQueryTranslator.java

Source

/*
 * Hibernate, Relational Persistence for Idiomatic Java
 *
 * License: GNU Lesser General Public License (LGPL), version 2.1 or later.
 * See the lgpl.txt file in the root directory or <http://www.gnu.org/licenses/lgpl-2.1.html>.
 */
package org.hibernate.loader.criteria;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.StringTokenizer;

import org.hibernate.Criteria;
import org.hibernate.HibernateException;
import org.hibernate.LockMode;
import org.hibernate.LockOptions;
import org.hibernate.MappingException;
import org.hibernate.QueryException;
import org.hibernate.criterion.CriteriaQuery;
import org.hibernate.criterion.Criterion;
import org.hibernate.criterion.EnhancedProjection;
import org.hibernate.criterion.ParameterInfoCollector;
import org.hibernate.criterion.Projection;
import org.hibernate.engine.query.spi.OrdinalParameterDescriptor;
import org.hibernate.engine.spi.QueryParameters;
import org.hibernate.engine.spi.RowSelection;
import org.hibernate.engine.spi.SessionFactoryImplementor;
import org.hibernate.engine.spi.TypedValue;
import org.hibernate.hql.internal.ast.util.SessionFactoryHelper;
import org.hibernate.internal.CriteriaImpl;
import org.hibernate.internal.util.StringHelper;
import org.hibernate.internal.util.collections.ArrayHelper;
import org.hibernate.persister.collection.CollectionPersister;
import org.hibernate.persister.entity.Loadable;
import org.hibernate.persister.entity.PropertyMapping;
import org.hibernate.persister.entity.Queryable;
import org.hibernate.sql.JoinType;
import org.hibernate.type.AssociationType;
import org.hibernate.type.CollectionType;
import org.hibernate.type.StringRepresentableType;
import org.hibernate.type.Type;

/**
 * @author Gavin King
 */
public class CriteriaQueryTranslator implements CriteriaQuery {

    public static final String ROOT_SQL_ALIAS = Criteria.ROOT_ALIAS + '_';

    private CriteriaQuery outerQueryTranslator;

    private final CriteriaImpl rootCriteria;
    private final String rootEntityName;
    private final String rootSQLAlias;

    private final Map<Criteria, CriteriaInfoProvider> criteriaInfoMap = new LinkedHashMap<Criteria, CriteriaInfoProvider>();
    private final Map<String, CriteriaInfoProvider> nameCriteriaInfoMap = new LinkedHashMap<String, CriteriaInfoProvider>();
    private final Map<Criteria, String> criteriaSQLAliasMap = new HashMap<Criteria, String>();
    private final Map<String, Criteria> aliasCriteriaMap = new HashMap<String, Criteria>();
    private final Map<String, Criteria> associationPathCriteriaMap = new LinkedHashMap<String, Criteria>();
    private final Map<String, JoinType> associationPathJoinTypesMap = new LinkedHashMap<String, JoinType>();
    private final Map<String, Criterion> withClauseMap = new HashMap<String, Criterion>();
    private Set<String> associations;

    private final SessionFactoryImplementor sessionFactory;
    private final SessionFactoryHelper helper;

    public CriteriaQueryTranslator(final SessionFactoryImplementor factory, final CriteriaImpl criteria,
            final String rootEntityName, final String rootSQLAlias, CriteriaQuery outerQuery)
            throws HibernateException {
        this(factory, criteria, rootEntityName, rootSQLAlias);
        outerQueryTranslator = outerQuery;
    }

    public CriteriaQueryTranslator(final SessionFactoryImplementor factory, final CriteriaImpl criteria,
            final String rootEntityName, final String rootSQLAlias) throws HibernateException {
        this.rootCriteria = criteria;
        this.rootEntityName = rootEntityName;
        this.sessionFactory = factory;
        this.rootSQLAlias = rootSQLAlias;
        this.helper = new SessionFactoryHelper(factory);
        createAliasCriteriaMap();
        createAssociationPathCriteriaMap();
        createCriteriaEntityNameMap();
        createCriteriaSQLAliasMap();
    }

    public void setAssociations(Set<String> associations) {
        this.associations = associations;
    }

    @Override
    public String generateSQLAlias() {
        int aliasCount = 0;
        return StringHelper.generateAlias(Criteria.ROOT_ALIAS, aliasCount) + '_';
    }

    public String getRootSQLALias() {
        return rootSQLAlias;
    }

    private Criteria getAliasedCriteria(String alias) {
        return aliasCriteriaMap.get(alias);
    }

    public boolean isJoin(String path) {
        return associationPathCriteriaMap.containsKey(path);
    }

    public JoinType getJoinType(String path) {
        JoinType result = associationPathJoinTypesMap.get(path);
        return (result == null ? JoinType.INNER_JOIN : result);
    }

    public Criteria getCriteria(String path) {
        return associationPathCriteriaMap.get(path);
    }

    public Set<Serializable> getQuerySpaces() {
        Set<Serializable> result = new HashSet<>();
        for (CriteriaInfoProvider info : criteriaInfoMap.values()) {
            result.addAll(Arrays.asList(info.getSpaces()));
        }
        for (final Map.Entry<String, Criteria> entry : associationPathCriteriaMap.entrySet()) {
            String path = entry.getKey();
            CriteriaImpl.Subcriteria crit = (CriteriaImpl.Subcriteria) entry.getValue();
            int index = path.lastIndexOf('.');
            if (index > 0) {
                path = path.substring(index + 1, path.length());
            }
            CriteriaInfoProvider info = criteriaInfoMap.get(crit.getParent());
            CollectionPersister persister = getFactory().getMetamodel().collectionPersisters()
                    .get(info.getName() + "." + path);
            if (persister != null) {
                result.addAll(Arrays.asList(persister.getCollectionSpaces()));
            }
        }
        return result;
    }

    private void createAliasCriteriaMap() {
        aliasCriteriaMap.put(rootCriteria.getAlias(), rootCriteria);
        Iterator<CriteriaImpl.Subcriteria> iter = rootCriteria.iterateSubcriteria();
        while (iter.hasNext()) {
            Criteria subcriteria = iter.next();
            if (subcriteria.getAlias() != null) {
                Object old = aliasCriteriaMap.put(subcriteria.getAlias(), subcriteria);
                if (old != null) {
                    throw new QueryException("duplicate alias: " + subcriteria.getAlias());
                }
            }
        }
    }

    private void createAssociationPathCriteriaMap() {
        final Iterator<CriteriaImpl.Subcriteria> iter = rootCriteria.iterateSubcriteria();
        while (iter.hasNext()) {
            CriteriaImpl.Subcriteria crit = iter.next();
            String wholeAssociationPath = getWholeAssociationPath(crit);
            Object old = associationPathCriteriaMap.put(wholeAssociationPath, crit);
            if (old != null) {
                throw new QueryException("duplicate association path: " + wholeAssociationPath);
            }
            JoinType joinType = crit.getJoinType();
            old = associationPathJoinTypesMap.put(wholeAssociationPath, joinType);
            if (old != null) {
                // TODO : not so sure this is needed...
                throw new QueryException("duplicate association path: " + wholeAssociationPath);
            }
            if (crit.getWithClause() != null) {
                this.withClauseMap.put(wholeAssociationPath, crit.getWithClause());
            }
        }
    }

    private String getWholeAssociationPath(CriteriaImpl.Subcriteria subcriteria) {
        String path = subcriteria.getPath();

        // some messy, complex stuff here, since createCriteria() can take an
        // aliased path, or a path rooted at the creating criteria instance
        Criteria parent = null;
        if (path.indexOf('.') > 0) {
            // if it is a compound path
            String testAlias = StringHelper.root(path);
            if (!testAlias.equals(subcriteria.getAlias())) {
                // and the qualifier is not the alias of this criteria
                // -> check to see if we belong to some criteria other
                //  than the one that created us
                parent = aliasCriteriaMap.get(testAlias);
            }
        }
        if (parent == null) {
            // otherwise assume the parent is the the criteria that created us
            parent = subcriteria.getParent();
        } else {
            path = StringHelper.unroot(path);
        }

        if (parent.equals(rootCriteria)) {
            // if its the root criteria, we are done
            return path;
        } else {
            // otherwise, recurse
            return getWholeAssociationPath((CriteriaImpl.Subcriteria) parent) + '.' + path;
        }
    }

    private void createCriteriaEntityNameMap() {
        // initialize the rootProvider first
        final CriteriaInfoProvider rootProvider = new EntityCriteriaInfoProvider(
                (Queryable) sessionFactory.getEntityPersister(rootEntityName));
        criteriaInfoMap.put(rootCriteria, rootProvider);
        nameCriteriaInfoMap.put(rootProvider.getName(), rootProvider);

        for (Map.Entry<String, Criteria> entry : associationPathCriteriaMap.entrySet()) {
            final String key = entry.getKey();
            final Criteria value = entry.getValue();
            final CriteriaInfoProvider info = getPathInfo(key);
            criteriaInfoMap.put(value, info);
            nameCriteriaInfoMap.put(info.getName(), info);
        }
    }

    private CriteriaInfoProvider getPathInfo(String path) {
        StringTokenizer tokens = new StringTokenizer(path, ".");
        String componentPath = "";

        // start with the 'rootProvider'
        CriteriaInfoProvider provider = nameCriteriaInfoMap.get(rootEntityName);

        while (tokens.hasMoreTokens()) {
            componentPath += tokens.nextToken();
            final Type type = provider.getType(componentPath);
            if (type.isAssociationType()) {
                // CollectionTypes are always also AssociationTypes - but there's not always an associated entity...
                final AssociationType atype = (AssociationType) type;
                final CollectionType ctype = type.isCollectionType() ? (CollectionType) type : null;
                final Type elementType = (ctype != null) ? ctype.getElementType(sessionFactory) : null;
                // is the association a collection of components or value-types? (i.e a colloction of valued types?)
                if (ctype != null && elementType.isComponentType()) {
                    provider = new ComponentCollectionCriteriaInfoProvider(
                            helper.getCollectionPersister(ctype.getRole()));
                } else if (ctype != null && !elementType.isEntityType()) {
                    provider = new ScalarCollectionCriteriaInfoProvider(helper, ctype.getRole());
                } else {
                    provider = new EntityCriteriaInfoProvider((Queryable) sessionFactory
                            .getEntityPersister(atype.getAssociatedEntityName(sessionFactory)));
                }

                componentPath = "";
            } else if (type.isComponentType()) {
                if (!tokens.hasMoreTokens()) {
                    throw new QueryException(
                            "Criteria objects cannot be created directly on components.  Create a criteria on "
                                    + "owning entity and use a dotted property to access component property: "
                                    + path);
                } else {
                    componentPath += '.';
                }
            } else {
                throw new QueryException("not an association: " + componentPath);
            }
        }

        return provider;
    }

    public int getSQLAliasCount() {
        return criteriaSQLAliasMap.size();
    }

    private void createCriteriaSQLAliasMap() {
        int i = 0;
        for (Map.Entry<Criteria, CriteriaInfoProvider> entry : criteriaInfoMap.entrySet()) {
            final Criteria crit = entry.getKey();
            final CriteriaInfoProvider value = entry.getValue();
            String alias = crit.getAlias();
            if (alias == null) {
                // the entity name
                alias = value.getName();
            }
            criteriaSQLAliasMap.put(crit, StringHelper.generateAlias(alias, i++));
        }

        criteriaSQLAliasMap.put(rootCriteria, rootSQLAlias);
    }

    public CriteriaImpl getRootCriteria() {
        return rootCriteria;
    }

    public QueryParameters getQueryParameters() {
        final RowSelection selection = new RowSelection();
        selection.setFirstRow(rootCriteria.getFirstResult());
        selection.setMaxRows(rootCriteria.getMaxResults());
        selection.setTimeout(rootCriteria.getTimeout());
        selection.setFetchSize(rootCriteria.getFetchSize());

        final LockOptions lockOptions = new LockOptions();
        final Map<String, LockMode> lockModeMap = rootCriteria.getLockModes();
        for (Map.Entry<String, LockMode> entry : lockModeMap.entrySet()) {
            final String key = entry.getKey();
            final LockMode value = entry.getValue();
            final Criteria subcriteria = getAliasedCriteria(key);
            lockOptions.setAliasSpecificLockMode(getSQLAlias(subcriteria), value);
        }

        final List<Object> values = new ArrayList<Object>();
        final List<Type> types = new ArrayList<Type>();

        final Iterator<CriteriaImpl.Subcriteria> subcriteriaIterator = rootCriteria.iterateSubcriteria();
        while (subcriteriaIterator.hasNext()) {
            final CriteriaImpl.Subcriteria subcriteria = subcriteriaIterator.next();
            final LockMode lm = subcriteria.getLockMode();
            if (lm != null) {
                lockOptions.setAliasSpecificLockMode(getSQLAlias(subcriteria), lm);
            }
            if (subcriteria.getWithClause() != null) {
                final TypedValue[] tv = subcriteria.getWithClause().getTypedValues(subcriteria, this);
                for (TypedValue aTv : tv) {
                    values.add(aTv.getValue());
                    types.add(aTv.getType());
                }
            }
        }

        // Type and value gathering for the WHERE clause needs to come AFTER lock mode gathering,
        // because the lock mode gathering loop now contains join clauses which can contain
        // parameter bindings (as in the HQL WITH clause).
        final Iterator<CriteriaImpl.CriterionEntry> iter = rootCriteria.iterateExpressionEntries();
        while (iter.hasNext()) {
            final CriteriaImpl.CriterionEntry ce = iter.next();
            final TypedValue[] tv = ce.getCriterion().getTypedValues(ce.getCriteria(), this);
            for (TypedValue aTv : tv) {
                values.add(aTv.getValue());
                types.add(aTv.getType());
            }
        }

        final Object[] valueArray = values.toArray();
        final Type[] typeArray = ArrayHelper.toTypeArray(types);
        return new QueryParameters(typeArray, valueArray, lockOptions, selection,
                rootCriteria.isReadOnlyInitialized(),
                (rootCriteria.isReadOnlyInitialized() && rootCriteria.isReadOnly()), rootCriteria.getCacheable(),
                rootCriteria.getCacheRegion(), rootCriteria.getComment(), rootCriteria.getQueryHints(),
                rootCriteria.isLookupByNaturalKey(), rootCriteria.getResultTransformer());
    }

    public boolean hasProjection() {
        return rootCriteria.getProjection() != null;
    }

    public String getGroupBy() {
        if (rootCriteria.getProjection().isGrouped()) {
            return rootCriteria.getProjection().toGroupSqlString(rootCriteria.getProjectionCriteria(), this);
        } else {
            return "";
        }
    }

    public String getSelect() {
        return rootCriteria.getProjection().toSqlString(rootCriteria.getProjectionCriteria(), 0, this);
    }

    /* package-protected */
    Type getResultType(Criteria criteria) {
        return getFactory().getTypeResolver().getTypeFactory().manyToOne(getEntityName(criteria));
    }

    public Type[] getProjectedTypes() {
        return rootCriteria.getProjection().getTypes(rootCriteria, this);
    }

    public String[] getProjectedColumnAliases() {
        return rootCriteria.getProjection() instanceof EnhancedProjection
                ? ((EnhancedProjection) rootCriteria.getProjection()).getColumnAliases(0, rootCriteria, this)
                : rootCriteria.getProjection().getColumnAliases(0);
    }

    public String[] getProjectedAliases() {
        return rootCriteria.getProjection().getAliases();
    }

    public String getWhereCondition() {
        StringBuilder condition = new StringBuilder(30);
        Iterator<CriteriaImpl.CriterionEntry> criterionIterator = rootCriteria.iterateExpressionEntries();
        while (criterionIterator.hasNext()) {
            CriteriaImpl.CriterionEntry entry = criterionIterator.next();
            String sqlString = entry.getCriterion().toSqlString(entry.getCriteria(), this);
            condition.append(sqlString);
            if (criterionIterator.hasNext()) {
                condition.append(" and ");
            }
        }
        return condition.toString();
    }

    public String getOrderBy() {
        StringBuilder orderBy = new StringBuilder(30);
        Iterator<CriteriaImpl.OrderEntry> criterionIterator = rootCriteria.iterateOrderings();
        while (criterionIterator.hasNext()) {
            CriteriaImpl.OrderEntry oe = criterionIterator.next();
            orderBy.append(oe.getOrder().toSqlString(oe.getCriteria(), this));
            if (criterionIterator.hasNext()) {
                orderBy.append(", ");
            }
        }
        return orderBy.toString();
    }

    @Override
    public SessionFactoryImplementor getFactory() {
        return sessionFactory;
    }

    @Override
    public String getSQLAlias(Criteria criteria) {
        return criteriaSQLAliasMap.get(criteria);
    }

    @Override
    public String getEntityName(Criteria criteria) {
        final CriteriaInfoProvider infoProvider = criteriaInfoMap.get(criteria);
        return infoProvider != null ? infoProvider.getName() : null;
    }

    @Override
    public String getColumn(Criteria criteria, String propertyName) {
        String[] cols = getColumns(propertyName, criteria);
        if (cols.length != 1) {
            throw new QueryException("property does not map to a single column: " + propertyName);
        }
        return cols[0];
    }

    /**
     * Get the names of the columns constrained
     * by this criterion.
     */
    @Override
    public String[] getColumnsUsingProjection(Criteria subcriteria, String propertyName) throws HibernateException {

        //first look for a reference to a projection alias
        final Projection projection = rootCriteria.getProjection();
        String[] projectionColumns = null;
        if (projection != null) {
            projectionColumns = (projection instanceof EnhancedProjection
                    ? ((EnhancedProjection) projection).getColumnAliases(propertyName, 0, rootCriteria, this)
                    : projection.getColumnAliases(propertyName, 0));
        }
        if (projectionColumns == null) {
            //it does not refer to an alias of a projection,
            //look for a property
            try {
                return getColumns(propertyName, subcriteria);
            } catch (HibernateException he) {
                //not found in inner query , try the outer query
                if (outerQueryTranslator != null) {
                    return outerQueryTranslator.getColumnsUsingProjection(subcriteria, propertyName);
                } else {
                    throw he;
                }
            }
        } else {
            //it refers to an alias of a projection
            return projectionColumns;
        }
    }

    @Override
    public String[] getIdentifierColumns(Criteria criteria) {
        String[] idcols = ((Loadable) getPropertyMapping(getEntityName(criteria))).getIdentifierColumnNames();
        return StringHelper.qualify(getSQLAlias(criteria), idcols);
    }

    @Override
    public Type getIdentifierType(Criteria criteria) {
        return ((Loadable) getPropertyMapping(getEntityName(criteria))).getIdentifierType();
    }

    @Override
    public TypedValue getTypedIdentifierValue(Criteria criteria, Object value) {
        final Loadable loadable = (Loadable) getPropertyMapping(getEntityName(criteria));
        return new TypedValue(loadable.getIdentifierType(), value);
    }

    @Override
    public String[] getColumns(String propertyName, Criteria subcriteria) throws HibernateException {
        return getPropertyMapping(getEntityName(subcriteria, propertyName))
                .toColumns(getSQLAlias(subcriteria, propertyName), getPropertyName(propertyName));
    }

    /**
     * Get the names of the columns mapped by a property path; if the
     * property path is not found in subcriteria, try the "outer" query.
     * Projection aliases are ignored.
     */
    @Override
    public String[] findColumns(String propertyName, Criteria subcriteria) throws HibernateException {
        try {
            return getColumns(propertyName, subcriteria);
        } catch (HibernateException he) {
            //not found in inner query, try the outer query
            if (outerQueryTranslator != null) {
                return outerQueryTranslator.findColumns(propertyName, subcriteria);
            } else {
                throw he;
            }
        }
    }

    @Override
    public Type getTypeUsingProjection(Criteria subcriteria, String propertyName) throws HibernateException {

        //first look for a reference to a projection alias
        final Projection projection = rootCriteria.getProjection();
        Type[] projectionTypes = projection == null ? null : projection.getTypes(propertyName, subcriteria, this);

        if (projectionTypes == null) {
            try {
                //it does not refer to an alias of a projection,
                //look for a property
                return getType(subcriteria, propertyName);
            } catch (HibernateException he) {
                //not found in inner query , try the outer query
                if (outerQueryTranslator != null) {
                    return outerQueryTranslator.getType(subcriteria, propertyName);
                } else {
                    throw he;
                }
            }
        } else {
            if (projectionTypes.length != 1) {
                //should never happen, i think
                throw new QueryException("not a single-length projection: " + propertyName);
            }
            return projectionTypes[0];
        }
    }

    @Override
    public Type getType(Criteria subcriteria, String propertyName) throws HibernateException {
        return getPropertyMapping(getEntityName(subcriteria, propertyName)).toType(getPropertyName(propertyName));
    }

    /**
     * Get the a typed value for the given property value.
     */
    @Override
    public TypedValue getTypedValue(Criteria subcriteria, String propertyName, Object value)
            throws HibernateException {
        // Detect discriminator values...
        if (value instanceof Class) {
            final Class entityClass = (Class) value;
            final Queryable q = SessionFactoryHelper.findQueryableUsingImports(sessionFactory,
                    entityClass.getName());
            if (q != null) {
                final Type type = q.getDiscriminatorType();
                String stringValue = q.getDiscriminatorSQLValue();
                if (stringValue != null && stringValue.length() > 2 && stringValue.startsWith("'")
                        && stringValue.endsWith("'")) {
                    // remove the single quotes
                    stringValue = stringValue.substring(1, stringValue.length() - 1);
                }

                // Convert the string value into the proper type.
                if (type instanceof StringRepresentableType) {
                    final StringRepresentableType nullableType = (StringRepresentableType) type;
                    value = nullableType.fromStringValue(stringValue);
                } else {
                    throw new QueryException("Unsupported discriminator type " + type);
                }
                return new TypedValue(type, value);
            }
        }
        // Otherwise, this is an ordinary value.
        return new TypedValue(getTypeUsingProjection(subcriteria, propertyName), value);
    }

    private PropertyMapping getPropertyMapping(String entityName) throws MappingException {
        final CriteriaInfoProvider info = nameCriteriaInfoMap.get(entityName);
        if (info == null) {
            throw new HibernateException("Unknown entity: " + entityName);
        }
        return info.getPropertyMapping();
    }

    //TODO: use these in methods above
    @Override
    public String getEntityName(Criteria subcriteria, String propertyName) {
        if (propertyName.indexOf('.') > 0) {
            final String root = StringHelper.root(propertyName);
            final Criteria crit = getAliasedCriteria(root);
            if (crit != null) {
                return getEntityName(crit);
            }
        }
        return getEntityName(subcriteria);
    }

    @Override
    public String getSQLAlias(Criteria criteria, String propertyName) {
        if (propertyName.indexOf('.') > 0) {
            final String root = StringHelper.root(propertyName);
            final Criteria subcriteria = getAliasedCriteria(root);
            if (subcriteria != null) {
                return getSQLAlias(subcriteria);
            }
        }
        return getSQLAlias(criteria);
    }

    @Override
    public String getPropertyName(String propertyName) {
        if (propertyName.indexOf('.') > 0) {
            final String root = StringHelper.root(propertyName);
            final Criteria criteria = getAliasedCriteria(root);
            if (criteria != null) {
                return propertyName.substring(root.length() + 1);
            }
        }
        return propertyName;
    }

    public String getWithClause(String path) {
        final Criterion criterion = withClauseMap.get(path);
        return criterion == null ? null : criterion.toSqlString(getCriteria(path), this);
    }

    public boolean hasRestriction(String path) {
        final CriteriaImpl.Subcriteria subcriteria = (CriteriaImpl.Subcriteria) getCriteria(path);
        return subcriteria != null && subcriteria.hasRestriction();
    }
}