/*
* Hibernate, Relational Persistence for Idiomatic Java
*
* License: Apache License, Version 2.0
* See the LICENSE file in the root directory or visit http://www.apache.org/licenses/LICENSE-2.0
*/
package org.hibernate.test.sqm.parser.criteria.tree;
import java.io.Serializable;
import java.util.Arrays;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
import javax.persistence.Tuple;
import javax.persistence.criteria.CriteriaQuery;
import javax.persistence.criteria.Expression;
import javax.persistence.criteria.Order;
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.query.sqm.produce.spi.criteria.JpaCriteriaQuery;
import org.hibernate.query.sqm.produce.spi.criteria.JpaExpression;
import org.hibernate.query.sqm.produce.spi.criteria.JpaOrder;
import org.hibernate.query.sqm.produce.spi.criteria.JpaQuerySpec;
import org.hibernate.query.sqm.produce.spi.criteria.select.JpaSelection;
import org.jboss.logging.Logger;
/**
* The Hibernate implementation of the JPA {@link CriteriaQuery} contract. Mostly a set of delegation to its
* internal {@link JpaQuerySpecImpl}.
*
* @author Steve Ebersole
*/
public class CriteriaQueryImpl<T> extends AbstractNode implements JpaCriteriaQuery<T>, Serializable {
private static final Logger log = Logger.getLogger( CriteriaQueryImpl.class );
private final Class<T> returnType;
private final JpaQuerySpecImpl<T> queryStructure;
public CriteriaQueryImpl(
CriteriaBuilderImpl criteriaBuilder,
Class<T> returnType) {
super( criteriaBuilder );
this.returnType = returnType;
this.queryStructure = new JpaQuerySpecImpl<T>( this, criteriaBuilder );
}
@Override
public Class<T> getResultType() {
return returnType;
}
// SELECTION ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@Override
public CriteriaQuery<T> distinct(boolean applyDistinction) {
queryStructure.setDistinct( applyDistinction );
return this;
}
@Override
public boolean isDistinct() {
return queryStructure.isDistinct();
}
@Override
@SuppressWarnings({ "unchecked" })
public Selection<T> getSelection() {
return ( Selection<T> ) queryStructure.getSelection();
}
public void applySelection(JpaSelection<? extends T> selection) {
queryStructure.setSelection( selection );
}
@Override
@SuppressWarnings("unchecked")
public CriteriaQuery<T> select(Selection<? extends T> selection) {
criteriaBuilder().check( selection );
applySelection( (JpaSelection<? extends T>) selection );
return this;
}
@Override
@SuppressWarnings({ "unchecked" })
public CriteriaQuery<T> multiselect(Selection<?>... selections) {
return multiselect( Arrays.asList( selections ) );
}
@Override
@SuppressWarnings({ "unchecked" })
public CriteriaQuery<T> multiselect(List<Selection<?>> selections) {
final JpaSelection<? extends T> selection;
if ( Tuple.class.isAssignableFrom( getResultType() ) ) {
selection = (JpaSelection<? extends T>) criteriaBuilder().tuple( selections );
}
else if ( getResultType().isArray() ) {
selection = ( JpaSelection<? extends T> ) criteriaBuilder().array(
(Class<Object[]>) getResultType(),
selections
);
}
else if ( Object.class.equals( getResultType() ) ) {
switch ( selections.size() ) {
case 0: {
throw new IllegalArgumentException(
"empty selections passed to criteria query typed as Object"
);
}
case 1: {
selection = (JpaSelection<? extends T>) selections.get( 0 );
break;
}
default: {
selection = (JpaSelection<? extends T>) criteriaBuilder().array( selections );
}
}
}
else {
selection = criteriaBuilder().construct( getResultType(), selections );
}
applySelection( selection );
return this;
}
// ROOTS ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@Override
public Set<Root<?>> getRoots() {
return queryStructure.getFromClause().getRoots().stream().collect( Collectors.toSet() );
}
@Override
public <X> Root<X> from(EntityType<X> entityType) {
return queryStructure.from( entityType );
}
@Override
public <X> Root<X> from(Class<X> entityClass) {
return queryStructure.from( entityClass );
}
public <X> Root<X> from(String entityName) {
return queryStructure.from( entityName );
}
// RESTRICTION ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@Override
public Predicate getRestriction() {
return queryStructure.getRestriction();
}
@Override
public CriteriaQuery<T> where(Expression<Boolean> expression) {
queryStructure.setRestriction( criteriaBuilder().wrap( expression ) );
return this;
}
@Override
public CriteriaQuery<T> where(Predicate... predicates) {
// TODO : assuming this should be a conjuntion, but the spec does not say specifically...
queryStructure.setRestriction( criteriaBuilder().and( predicates ) );
return this;
}
// GROUPING ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@Override
public List<Expression<?>> getGroupList() {
return queryStructure.getGroupings().stream().collect( Collectors.toList() );
}
@Override
public CriteriaQuery<T> groupBy(Expression<?>... groupings) {
queryStructure.setGroupings( (JpaExpression<?>[]) groupings );
return this;
}
@Override
@SuppressWarnings("unchecked")
public CriteriaQuery<T> groupBy(List<Expression<?>> groupings) {
groupings.forEach( expr -> {
criteriaBuilder().check( expr );
queryStructure.setGroupings( (List<JpaExpression<?>>) expr );
} );
return this;
}
@Override
public Predicate getGroupRestriction() {
return queryStructure.getHaving();
}
@Override
public CriteriaQuery<T> having(Expression<Boolean> expression) {
queryStructure.setHaving( criteriaBuilder().wrap( expression ) );
return this;
}
@Override
public CriteriaQuery<T> having(Predicate... predicates) {
queryStructure.setHaving( criteriaBuilder().and( predicates ) );
return this;
}
// ORDERING ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@Override
public List<Order> getOrderList() {
return queryStructure.getOrderList().stream().collect( Collectors.toList() );
}
@Override
public CriteriaQuery<T> orderBy(Order... orders) {
queryStructure.clearOrderList();
for ( Order order : orders ) {
criteriaBuilder().check( order );
queryStructure.addOrderBy( (JpaOrder) order );
}
return this;
}
@Override
public CriteriaQuery<T> orderBy(List<Order> orders) {
queryStructure.clearOrderList();
orders.forEach( order -> {
criteriaBuilder().check( order );
queryStructure.addOrderBy( (JpaOrder) order );
} );
return this;
}
@Override
public Set<ParameterExpression<?>> getParameters() {
return queryStructure.collectParameters();
}
@Override
public <U> Subquery<U> subquery(Class<U> subqueryType) {
return queryStructure.subquery( subqueryType );
}
public void validate() {
// getRoots() is explicitly supposed to return empty if none defined, no need to check for null
if ( getRoots().isEmpty() ) {
throw new IllegalStateException( "No criteria query roots were specified" );
}
// if there is not an explicit selection, there is an *implicit* selection of the root entity provided only
// a single query root was defined.
if ( getSelection() == null && !hasImplicitSelection() ) {
throw new IllegalStateException( "No explicit selection and an implicit one could not be determined" );
}
}
/**
* If no explicit selection was defined, we have a condition called an implicit selection if the query specified
* a single {@link Root} and the java type of that {@link Root root's} model is the same as this criteria's
* {@link #getResultType() result type}.
*
* @return True if there is an explicit selection; false otherwise.
*/
private boolean hasImplicitSelection() {
if ( getRoots().size() != 1 ) {
return false;
}
Root root = getRoots().iterator().next();
Class<?> javaType = root.getModel().getJavaType();
if ( javaType != null && javaType != returnType ) {
return false;
}
// if we get here, the query defined no selection but defined a single root of the same type as the
// criteria query return, so we use that as the implicit selection
//
// todo : should we put an implicit marker in the selection to this fact to make later processing easier?
return true;
}
@Override
public JpaQuerySpec getQuerySpec() {
return queryStructure;
}
}