/* * Hibernate, Relational Persistence for Idiomatic Java * * Copyright (c) 2009 by Red Hat Inc and/or its affiliates or by * third-party contributors as indicated by either @author tags or express * copyright attribution statements applied by the authors. All * third-party contributions are distributed under license by Red Hat Inc. * * This copyrighted material is made available to anyone wishing to use, modify, * copy, or redistribute it subject to the terms and conditions of the GNU * Lesser General Public License, as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License * for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this distribution; if not, write to: * Free Software Foundation, Inc. * 51 Franklin Street, Fifth Floor * Boston, MA 02110-1301 USA */ package org.hibernate.ejb.criteria; import java.io.Serializable; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.HashSet; import java.util.LinkedHashSet; import java.util.List; import java.util.Set; import javax.persistence.criteria.AbstractQuery; import javax.persistence.criteria.Expression; import javax.persistence.criteria.Fetch; import javax.persistence.criteria.Join; import javax.persistence.criteria.JoinType; import javax.persistence.criteria.ParameterExpression; import javax.persistence.criteria.Predicate; import javax.persistence.criteria.Root; import javax.persistence.criteria.Selection; import javax.persistence.criteria.Subquery; import javax.persistence.metamodel.EntityType; import org.hibernate.ejb.criteria.path.RootImpl; /** * Models basic query structure. Used as a delegate in implementing both * {@link org.hibernate.criterion.CriteriaQuery} and * {@link javax.persistence.criteria.Subquery}. * <p/> * Note the <tt>ORDER BY</tt> specs are neglected here. That's because it is not valid * for a subquery to define an <tt>ORDER BY</tt> clause. So we just handle them on the * root query directly... * * @author Steve Ebersole */ public class QueryStructure<T> implements Serializable { private final AbstractQuery<T> owner; private final CriteriaBuilderImpl criteriaBuilder; private final boolean isSubQuery; public QueryStructure(AbstractQuery<T> owner, CriteriaBuilderImpl criteriaBuilder) { this.owner = owner; this.criteriaBuilder = criteriaBuilder; this.isSubQuery = Subquery.class.isInstance( owner ); } private boolean distinct; private Selection<? extends T> selection; private Set<Root<?>> roots = new LinkedHashSet<Root<?>>(); private Set<FromImplementor> correlationRoots; private Predicate restriction; private List<Expression<?>> groupings = Collections.emptyList(); private Predicate having; private List<Subquery<?>> subqueries; // PARAMETERS ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ public Set<ParameterExpression<?>> getParameters() { final Set<ParameterExpression<?>> parameters = new LinkedHashSet<ParameterExpression<?>>(); final ParameterRegistry registry = new ParameterRegistry() { public void registerParameter(ParameterExpression<?> parameter) { parameters.add( parameter ); } }; ParameterContainer.Helper.possibleParameter(selection, registry); ParameterContainer.Helper.possibleParameter(restriction, registry); ParameterContainer.Helper.possibleParameter(having, registry); if ( subqueries != null ) { for ( Subquery subquery : subqueries ) { ParameterContainer.Helper.possibleParameter(subquery, registry); } } // both group-by and having expressions can (though unlikely) contain parameters... ParameterContainer.Helper.possibleParameter(having, registry); if ( groupings != null ) { for ( Expression<?> grouping : groupings ) { ParameterContainer.Helper.possibleParameter(grouping, registry); } } return parameters; } // SELECTION ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ public boolean isDistinct() { return distinct; } public void setDistinct(boolean distinct) { this.distinct = distinct; } public Selection<? extends T> getSelection() { return selection; } public void setSelection(Selection<? extends T> selection) { this.selection = selection; } // ROOTS ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ public Set<Root<?>> getRoots() { return roots; } public <X> Root<X> from(Class<X> entityClass) { EntityType<X> entityType = criteriaBuilder.getEntityManagerFactory() .getMetamodel() .entity( entityClass ); if ( entityType == null ) { throw new IllegalArgumentException( entityClass + " is not an entity" ); } return from( entityType ); } public <X> Root<X> from(EntityType<X> entityType) { RootImpl<X> root = new RootImpl<X>( criteriaBuilder, entityType ); roots.add( root ); return root; } // CORRELATION ROOTS ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ public void addCorrelationRoot(FromImplementor fromImplementor) { if ( !isSubQuery ) { throw new IllegalStateException( "Query is not identified as sub-query" ); } if ( correlationRoots == null ) { correlationRoots = new HashSet<FromImplementor>(); } correlationRoots.add( fromImplementor ); } public Set<Join<?, ?>> collectCorrelatedJoins() { if ( !isSubQuery ) { throw new IllegalStateException( "Query is not identified as sub-query" ); } final Set<Join<?, ?>> correlatedJoins; if ( correlationRoots != null ) { correlatedJoins = new HashSet<Join<?,?>>(); for ( FromImplementor<?,?> correlationRoot : correlationRoots ) { correlatedJoins.addAll( correlationRoot.getJoins() ); } } else { correlatedJoins = Collections.emptySet(); } return correlatedJoins; } // RESTRICTIONS ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ public Predicate getRestriction() { return restriction; } public void setRestriction(Predicate restriction) { this.restriction = restriction; } // GROUPINGS ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ public List<Expression<?>> getGroupings() { return groupings; } public void setGroupings(List<Expression<?>> groupings) { this.groupings = groupings; } public void setGroupings(Expression<?>... groupings) { if ( groupings != null && groupings.length > 0 ) { this.groupings = Arrays.asList( groupings ); } else { this.groupings = Collections.emptyList(); } } public Predicate getHaving() { return having; } public void setHaving(Predicate having) { this.having = having; } // SUB-QUERIES ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ public List<Subquery<?>> getSubqueries() { return subqueries; } public List<Subquery<?>> internalGetSubqueries() { if ( subqueries == null ) { subqueries = new ArrayList<Subquery<?>>(); } return subqueries; } public <U> Subquery<U> subquery(Class<U> subqueryType) { CriteriaSubqueryImpl<U> subquery = new CriteriaSubqueryImpl<U>( criteriaBuilder, subqueryType, owner ); internalGetSubqueries().add( subquery ); return subquery; } @SuppressWarnings({ "unchecked" }) public void render(StringBuilder jpaqlQuery, CriteriaQueryCompiler.RenderingContext renderingContext) { jpaqlQuery.append( "select " ); if ( isDistinct() ) { jpaqlQuery.append( "distinct " ); } if ( getSelection() == null ) { jpaqlQuery.append( locateImplicitSelection().renderProjection( renderingContext ) ); } else { jpaqlQuery.append( ( (Renderable) getSelection() ).renderProjection( renderingContext ) ); } renderFromClause( jpaqlQuery, renderingContext ); if ( getRestriction() != null) { jpaqlQuery.append( " where " ) .append( ( (Renderable) getRestriction() ).render( renderingContext ) ); } if ( ! getGroupings().isEmpty() ) { jpaqlQuery.append( " group by " ); String sep = ""; for ( Expression grouping : getGroupings() ) { jpaqlQuery.append( sep ) .append( ( (Renderable) grouping ).render( renderingContext ) ); sep = ", "; } if ( getHaving() != null ) { jpaqlQuery.append( " having " ) .append( ( (Renderable) getHaving() ).render( renderingContext ) ); } } } private FromImplementor locateImplicitSelection() { FromImplementor implicitSelection = null; if ( ! isSubQuery ) { // we should have only a single root (query validation should have checked this...) implicitSelection = (FromImplementor) getRoots().iterator().next(); } else { // we should only have a single "root" which can act as the implicit selection final Set<Join<?, ?>> correlatedJoins = collectCorrelatedJoins(); if ( correlatedJoins != null ) { if ( correlatedJoins.size() == 1 ) { implicitSelection = (FromImplementor) correlatedJoins.iterator().next(); } } } if ( implicitSelection == null ) { throw new IllegalStateException( "No explicit selection and an implicit one cold not be determined" ); } return implicitSelection; } @SuppressWarnings({ "unchecked" }) private void renderFromClause(StringBuilder jpaqlQuery, CriteriaQueryCompiler.RenderingContext renderingContext) { jpaqlQuery.append( " from " ); String sep = ""; for ( Root root : getRoots() ) { ( (FromImplementor) root ).prepareAlias( renderingContext ); jpaqlQuery.append( sep ); jpaqlQuery.append( ( (FromImplementor) root ).renderTableExpression( renderingContext ) ); sep = ", "; } for ( Root root : getRoots() ) { renderJoins( jpaqlQuery, renderingContext, root.getJoins() ); renderFetches( jpaqlQuery, renderingContext, root.getFetches() ); } if ( isSubQuery ) { if ( correlationRoots != null ) { for ( FromImplementor<?,?> correlationRoot : correlationRoots ) { final FromImplementor correlationParent = correlationRoot.getCorrelationParent(); correlationParent.prepareAlias( renderingContext ); final String correlationRootAlias = correlationParent.getAlias(); for ( Join<?,?> correlationJoin : correlationRoot.getJoins() ) { final JoinImplementor correlationJoinImpl = (JoinImplementor) correlationJoin; // IMPL NOTE: reuse the sep from above! jpaqlQuery.append( sep ); correlationJoinImpl.prepareAlias( renderingContext ); jpaqlQuery.append( correlationRootAlias ) .append( '.' ) .append( correlationJoinImpl.getAttribute().getName() ) .append( " as " ) .append( correlationJoinImpl.getAlias() ); sep = ", "; renderJoins( jpaqlQuery, renderingContext, correlationJoinImpl.getJoins() ); } } } } } @SuppressWarnings({ "unchecked" }) private void renderJoins( StringBuilder jpaqlQuery, CriteriaQueryCompiler.RenderingContext renderingContext, Collection<Join<?,?>> joins) { if ( joins == null ) { return; } for ( Join join : joins ) { ( (FromImplementor) join ).prepareAlias( renderingContext ); jpaqlQuery.append( renderJoinType( join.getJoinType() ) ) .append( ( (FromImplementor) join ).renderTableExpression( renderingContext ) ); renderJoins( jpaqlQuery, renderingContext, join.getJoins() ); renderFetches( jpaqlQuery, renderingContext, join.getFetches() ); } } private String renderJoinType(JoinType joinType) { switch ( joinType ) { case INNER: { return " inner join "; } case LEFT: { return " left join "; } case RIGHT: { return " right join "; } } throw new IllegalStateException( "Unknown join type " + joinType ); } @SuppressWarnings({ "unchecked" }) private void renderFetches( StringBuilder jpaqlQuery, CriteriaQueryCompiler.RenderingContext renderingContext, Collection<Fetch> fetches) { if ( fetches == null ) { return; } for ( Fetch fetch : fetches ) { ( (FromImplementor) fetch ).prepareAlias( renderingContext ); jpaqlQuery.append( renderJoinType( fetch.getJoinType() ) ) .append( "fetch " ) .append( ( (FromImplementor) fetch ).renderTableExpression( renderingContext ) ); renderFetches( jpaqlQuery, renderingContext, fetch.getFetches() ); } } }