Java tutorial
/* * 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(); } }