/*
 * Hibernate, Relational Persistence for Idiomatic Java
 *
 * Copyright (c) 2012, Red Hat Inc. or third-party contributors as
 * indicated by the @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.jpa.spi;

import java.util.Calendar;
import java.util.Collection;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import javax.persistence.CacheRetrieveMode;
import javax.persistence.CacheStoreMode;
import javax.persistence.FlushModeType;
import javax.persistence.LockModeType;
import javax.persistence.Parameter;
import javax.persistence.Query;
import javax.persistence.TemporalType;

import org.hibernate.CacheMode;
import org.hibernate.FlushMode;
import org.hibernate.HibernateException;
import org.hibernate.LockMode;
import org.hibernate.QueryParameterException;
import org.hibernate.engine.query.spi.EntityGraphQueryHint;
import org.hibernate.jpa.AvailableSettings;
import org.hibernate.jpa.QueryHints;
import org.hibernate.jpa.graph.internal.EntityGraphImpl;
import org.hibernate.jpa.internal.EntityManagerMessageLogger;
import org.hibernate.jpa.internal.util.CacheModeHelper;
import org.hibernate.jpa.internal.util.ConfigurationHelper;
import org.hibernate.jpa.internal.util.LockModeTypeHelper;
import org.hibernate.jpa.internal.util.PessimisticNumberParser;
import org.hibernate.procedure.NoSuchParameterException;
import org.hibernate.procedure.ParameterStrategyException;

import org.jboss.logging.Logger;

import static org.hibernate.jpa.QueryHints.HINT_CACHEABLE;
import static org.hibernate.jpa.QueryHints.HINT_CACHE_MODE;
import static org.hibernate.jpa.QueryHints.HINT_CACHE_REGION;
import static org.hibernate.jpa.QueryHints.HINT_COMMENT;
import static org.hibernate.jpa.QueryHints.HINT_FETCHGRAPH;
import static org.hibernate.jpa.QueryHints.HINT_FETCH_SIZE;
import static org.hibernate.jpa.QueryHints.HINT_FLUSH_MODE;
import static org.hibernate.jpa.QueryHints.HINT_LOADGRAPH;
import static org.hibernate.jpa.QueryHints.HINT_NATIVE_LOCKMODE;
import static org.hibernate.jpa.QueryHints.HINT_READONLY;
import static org.hibernate.jpa.QueryHints.HINT_TIMEOUT;
import static org.hibernate.jpa.QueryHints.SPEC_HINT_TIMEOUT;

Intended as the base class for all Query implementations, including TypedQuery and StoredProcedureQuery. Care should be taken that all changes here fit with all those usages.
Author:Steve Ebersole
/** * Intended as the base class for all {@link javax.persistence.Query} implementations, including * {@link javax.persistence.TypedQuery} and {@link javax.persistence.StoredProcedureQuery}. Care should be taken * that all changes here fit with all those usages. * * @author Steve Ebersole */
public abstract class BaseQueryImpl implements Query { private static final EntityManagerMessageLogger LOG = Logger.getMessageLogger( EntityManagerMessageLogger.class, AbstractQueryImpl.class.getName() ); private final HibernateEntityManagerImplementor entityManager; private int firstResult; private int maxResults = -1; private Map<String, Object> hints; private EntityGraphQueryHint entityGraphQueryHint; public BaseQueryImpl(HibernateEntityManagerImplementor entityManager) { this.entityManager = entityManager; } protected HibernateEntityManagerImplementor entityManager() { return entityManager; } protected void checkOpen(boolean markForRollbackIfClosed) { entityManager.checkOpen( markForRollbackIfClosed ); } // Limits (first and max results) ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Apply the given first-result value.
Params:
  • firstResult – The specified first-result value.
/** * Apply the given first-result value. * * @param firstResult The specified first-result value. */
protected abstract void applyFirstResult(int firstResult); @Override public BaseQueryImpl setFirstResult(int firstResult) { checkOpen( true ); if ( firstResult < 0 ) { throw new IllegalArgumentException( "Negative value (" + firstResult + ") passed to setFirstResult" ); } this.firstResult = firstResult; applyFirstResult( firstResult ); return this; } @Override public int getFirstResult() { checkOpen( false ); // technically should rollback return firstResult; }
Apply the given max results value.
Params:
  • maxResults – The specified max results
/** * Apply the given max results value. * * @param maxResults The specified max results */
protected abstract void applyMaxResults(int maxResults); @Override public BaseQueryImpl setMaxResults(int maxResult) { checkOpen( true ); if ( maxResult < 0 ) { throw new IllegalArgumentException( "Negative value (" + maxResult + ") passed to setMaxResults" ); } this.maxResults = maxResult; applyMaxResults( maxResult ); return this; } public int getSpecifiedMaxResults() { return maxResults; } @Override public int getMaxResults() { checkOpen( false ); // technically should rollback return maxResults == -1 ? Integer.MAX_VALUE // stupid spec... MAX_VALUE?? : maxResults; } // Hints ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @SuppressWarnings( {"UnusedDeclaration"}) public Set<String> getSupportedHints() { return QueryHints.getDefinedHints(); } @Override public Map<String, Object> getHints() { checkOpen( false ); // technically should rollback return hints; }
Apply the query timeout hint.
Params:
  • timeout – The timeout (in seconds!) specified as a hint
Returns:true if the hint was "applied"
/** * Apply the query timeout hint. * * @param timeout The timeout (in seconds!) specified as a hint * * @return {@code true} if the hint was "applied" */
protected abstract boolean applyTimeoutHint(int timeout);
Apply the lock timeout (in seconds!) hint
Params:
  • timeout – The timeout (in seconds!) specified as a hint
Returns:true if the hint was "applied"
/** * Apply the lock timeout (in seconds!) hint * * @param timeout The timeout (in seconds!) specified as a hint * * @return {@code true} if the hint was "applied" */
protected abstract boolean applyLockTimeoutHint(int timeout);
Apply the comment hint.
Params:
  • comment – The comment specified as a hint
Returns:true if the hint was "applied"
/** * Apply the comment hint. * * @param comment The comment specified as a hint * * @return {@code true} if the hint was "applied" */
protected abstract boolean applyCommentHint(String comment);
Apply the fetch size hint
Params:
  • fetchSize – The fetch size specified as a hint
Returns:true if the hint was "applied"
/** * Apply the fetch size hint * * @param fetchSize The fetch size specified as a hint * * @return {@code true} if the hint was "applied" */
protected abstract boolean applyFetchSizeHint(int fetchSize);
Apply the cacheable (true/false) hint.
Params:
  • isCacheable – The value specified as hint
Returns:true if the hint was "applied"
/** * Apply the cacheable (true/false) hint. * * @param isCacheable The value specified as hint * * @return {@code true} if the hint was "applied" */
protected abstract boolean applyCacheableHint(boolean isCacheable);
Apply the cache region hint
Params:
  • regionName – The name of the cache region specified as a hint
Returns:true if the hint was "applied"
/** * Apply the cache region hint * * @param regionName The name of the cache region specified as a hint * * @return {@code true} if the hint was "applied" */
protected abstract boolean applyCacheRegionHint(String regionName);
Apply the read-only (true/false) hint.
Params:
  • isReadOnly – The value specified as hint
Returns:true if the hint was "applied"
/** * Apply the read-only (true/false) hint. * * @param isReadOnly The value specified as hint * * @return {@code true} if the hint was "applied" */
protected abstract boolean applyReadOnlyHint(boolean isReadOnly);
Apply the CacheMode hint.
Params:
  • cacheMode – The CacheMode value specified as a hint.
Returns:true if the hint was "applied"
/** * Apply the CacheMode hint. * * @param cacheMode The CacheMode value specified as a hint. * * @return {@code true} if the hint was "applied" */
protected abstract boolean applyCacheModeHint(CacheMode cacheMode);
Apply the FlushMode hint.
Params:
  • flushMode – The FlushMode value specified as hint
Returns:true if the hint was "applied"
/** * Apply the FlushMode hint. * * @param flushMode The FlushMode value specified as hint * * @return {@code true} if the hint was "applied" */
protected abstract boolean applyFlushModeHint(FlushMode flushMode);
Can alias-specific lock modes be applied?
Returns:true indicates they can be applied, false otherwise.
/** * Can alias-specific lock modes be applied? * * @return {@code true} indicates they can be applied, {@code false} otherwise. */
protected abstract boolean canApplyAliasSpecificLockModeHints();
Apply the alias specific lock modes. Assumes canApplyAliasSpecificLockModeHints() has already been called and returned true.
Params:
  • alias – The alias to apply the 'lockMode' to.
  • lockMode – The LockMode to apply.
/** * Apply the alias specific lock modes. Assumes {@link #canApplyAliasSpecificLockModeHints()} has already been * called and returned {@code true}. * * @param alias The alias to apply the 'lockMode' to. * @param lockMode The LockMode to apply. */
protected abstract void applyAliasSpecificLockModeHint(String alias, LockMode lockMode); @Override @SuppressWarnings( {"deprecation"}) public BaseQueryImpl setHint(String hintName, Object value) { checkOpen( true ); boolean applied = false; try { if ( HINT_TIMEOUT.equals( hintName ) ) { applied = applyTimeoutHint( ConfigurationHelper.getInteger( value ) ); } else if ( SPEC_HINT_TIMEOUT.equals( hintName ) ) { // convert milliseconds to seconds int timeout = (int)Math.round(ConfigurationHelper.getInteger( value ).doubleValue() / 1000.0 ); applied = applyTimeoutHint( timeout ); } else if ( AvailableSettings.LOCK_TIMEOUT.equals( hintName ) ) { applied = applyLockTimeoutHint( ConfigurationHelper.getInteger( value ) ); } else if ( HINT_COMMENT.equals( hintName ) ) { applied = applyCommentHint( (String) value ); } else if ( HINT_FETCH_SIZE.equals( hintName ) ) { applied = applyFetchSizeHint( ConfigurationHelper.getInteger( value ) ); } else if ( HINT_CACHEABLE.equals( hintName ) ) { applied = applyCacheableHint( ConfigurationHelper.getBoolean( value ) ); } else if ( HINT_CACHE_REGION.equals( hintName ) ) { applied = applyCacheRegionHint( (String) value ); } else if ( HINT_READONLY.equals( hintName ) ) { applied = applyReadOnlyHint( ConfigurationHelper.getBoolean( value ) ); } else if ( HINT_CACHE_MODE.equals( hintName ) ) { applied = applyCacheModeHint( ConfigurationHelper.getCacheMode( value ) ); } else if ( HINT_FLUSH_MODE.equals( hintName ) ) { applied = applyFlushModeHint( ConfigurationHelper.getFlushMode( value ) ); } else if ( AvailableSettings.SHARED_CACHE_RETRIEVE_MODE.equals( hintName ) ) { final CacheRetrieveMode retrieveMode = value != null ? CacheRetrieveMode.valueOf( value.toString() ) : null; final CacheStoreMode storeMode = getHint( AvailableSettings.SHARED_CACHE_STORE_MODE, CacheStoreMode.class ); applied = applyCacheModeHint( CacheModeHelper.interpretCacheMode( storeMode, retrieveMode ) ); } else if ( AvailableSettings.SHARED_CACHE_STORE_MODE.equals( hintName ) ) { final CacheStoreMode storeMode = value != null ? CacheStoreMode.valueOf( value.toString () ) : null; final CacheRetrieveMode retrieveMode = getHint( AvailableSettings.SHARED_CACHE_RETRIEVE_MODE, CacheRetrieveMode.class ); applied = applyCacheModeHint( CacheModeHelper.interpretCacheMode( storeMode, retrieveMode ) ); } else if ( QueryHints.HINT_NATIVE_LOCKMODE.equals( hintName ) ) { if ( !isNativeSqlQuery() ) { throw new IllegalStateException( "Illegal attempt to set lock mode on non-native query via hint; use Query#setLockMode instead" ); } if ( LockMode.class.isInstance( value ) ) { internalApplyLockMode( LockModeTypeHelper.getLockModeType( (LockMode) value ) ); } else if ( LockModeType.class.isInstance( value ) ) { internalApplyLockMode( (LockModeType) value ); } else { throw new IllegalArgumentException( String.format( "Native lock-mode hint [%s] must specify %s or %s. Encountered type : %s", HINT_NATIVE_LOCKMODE, LockMode.class.getName(), LockModeType.class.getName(), value.getClass().getName() ) ); } applied = true; } else if ( hintName.startsWith( AvailableSettings.ALIAS_SPECIFIC_LOCK_MODE ) ) { if ( canApplyAliasSpecificLockModeHints() ) { // extract the alias final String alias = hintName.substring( AvailableSettings.ALIAS_SPECIFIC_LOCK_MODE.length() + 1 ); // determine the LockMode try { final LockMode lockMode = LockModeTypeHelper.interpretLockMode( value ); applyAliasSpecificLockModeHint( alias, lockMode ); } catch ( Exception e ) { LOG.unableToDetermineLockModeValue( hintName, value ); applied = false; } } else { applied = false; } } else if ( HINT_FETCHGRAPH.equals( hintName ) || HINT_LOADGRAPH.equals( hintName ) ) { if (value instanceof EntityGraphImpl) { entityGraphQueryHint = new EntityGraphQueryHint( (EntityGraphImpl) value ); } else { LOG.warnf( "The %s hint was set, but the value was not an EntityGraph!", hintName ); } applied = true; } else { LOG.ignoringUnrecognizedQueryHint( hintName ); } } catch ( ClassCastException e ) { throw new IllegalArgumentException( "Value for hint" ); } if ( applied ) { if ( hints == null ) { hints = new HashMap<String,Object>(); } hints.put( hintName, value ); } else { LOG.debugf( "Skipping unsupported query hint [%s]", hintName ); } return this; } private <T extends Enum<T>> T getHint(String key, Class<T> hintClass) { Object hint = hints != null ? hints.get( key ) : null; if ( hint == null ) { hint = entityManager.getProperties().get( key ); } return hint != null ? Enum.valueOf( hintClass, hint.toString() ) : null; }
Is the query represented here a native SQL query?
Returns:true if it is a native SQL query; false otherwise
/** * Is the query represented here a native SQL query? * * @return {@code true} if it is a native SQL query; {@code false} otherwise */
protected abstract boolean isNativeSqlQuery();
Is the query represented here a SELECT query?
Returns:true if the query is a SELECT; false otherwise.
/** * Is the query represented here a SELECT query? * * @return {@code true} if the query is a SELECT; {@code false} otherwise. */
protected abstract boolean isSelectQuery(); protected abstract void internalApplyLockMode(javax.persistence.LockModeType lockModeType); // FlushMode ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ private FlushModeType jpaFlushMode; @Override public BaseQueryImpl setFlushMode(FlushModeType jpaFlushMode) { checkOpen( true ); this.jpaFlushMode = jpaFlushMode; // TODO : treat as hint? if ( jpaFlushMode == FlushModeType.AUTO ) { applyFlushModeHint( FlushMode.AUTO ); } else if ( jpaFlushMode == FlushModeType.COMMIT ) { applyFlushModeHint( FlushMode.COMMIT ); } return this; } @SuppressWarnings( {"UnusedDeclaration"}) protected FlushModeType getSpecifiedFlushMode() { return jpaFlushMode; } @Override public FlushModeType getFlushMode() { checkOpen( false ); return jpaFlushMode != null ? jpaFlushMode : entityManager.getFlushMode(); } // Parameters ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ private Set<ParameterRegistration<?>> parameterRegistrations; protected <X> ParameterRegistration<X> findParameterRegistration(Parameter<X> parameter) { if ( ParameterRegistration.class.isInstance( parameter ) ) { final ParameterRegistration<X> reg = (ParameterRegistration<X>) parameter; // validate the parameter source if ( reg.getQuery() != this ) { throw new IllegalArgumentException( "Passed Parameter was from different Query" ); } return reg; } else { if ( parameter.getName() != null ) { return findParameterRegistration( parameter.getName() ); } else if ( parameter.getPosition() != null ) { return findParameterRegistration( parameter.getPosition() ); } } throw new IllegalArgumentException( "Unable to resolve incoming parameter [" + parameter + "] to registration" ); } @SuppressWarnings("unchecked") protected <X> ParameterRegistration<X> findParameterRegistration(String parameterName) { if ( parameterRegistrations != null ) { for ( ParameterRegistration<?> param : parameterRegistrations ) { if ( parameterName.equals( param.getName() ) ) { return (ParameterRegistration<X>) param; } } // legacy allowance of the application to access the parameter using the position as a String final Integer jpaPositionalParameter = PessimisticNumberParser.toNumberOrNull( parameterName ); if ( jpaPositionalParameter != null ) { for ( ParameterRegistration<?> param : parameterRegistrations ) { if ( param.isJpaPositionalParameter() && jpaPositionalParameter.equals( param.getPosition() ) ) { LOG.deprecatedJpaPositionalParameterAccess( jpaPositionalParameter ); return (ParameterRegistration<X>) param; } } } } throw new IllegalArgumentException( "Parameter with that name [" + parameterName + "] did not exist" ); } @SuppressWarnings("unchecked") protected <X> ParameterRegistration<X> findParameterRegistration(int parameterPosition) { if ( parameterRegistrations != null ) { for ( ParameterRegistration<?> param : parameterRegistrations ) { if ( param.getPosition() == null ) { continue; } if ( parameterPosition == param.getPosition() ) { return (ParameterRegistration<X>) param; } } } throw new IllegalArgumentException( "Parameter with that position [" + parameterPosition + "] did not exist" ); } protected static class ParameterBindImpl<T> implements ParameterBind<T> { private final T value; private final TemporalType specifiedTemporalType; public ParameterBindImpl(T value, TemporalType specifiedTemporalType) { this.value = value; this.specifiedTemporalType = specifiedTemporalType; } public T getValue() { return value; } public TemporalType getSpecifiedTemporalType() { return specifiedTemporalType; } } private Set<ParameterRegistration<?>> parameterRegistrations() { if ( parameterRegistrations == null ) { // todo : could se use an identity set here? parameterRegistrations = new HashSet<ParameterRegistration<?>>(); } return parameterRegistrations; } protected void registerParameter(ParameterRegistration parameter) { if ( parameter == null ) { throw new IllegalArgumentException( "parameter cannot be null" ); } if ( parameterRegistrations().contains( parameter ) ) { LOG.debug( "Parameter registered multiple times : " + parameter ); return; } parameterRegistrations().add( parameter ); } @Override public <T> BaseQueryImpl setParameter(Parameter<T> param, T value) { checkOpen( true ); try { findParameterRegistration( param ).bindValue( value ); } catch (QueryParameterException e) { entityManager().markForRollbackOnly(); throw new IllegalArgumentException( e.getMessage(), e ); } catch (HibernateException he) { throw entityManager.convert( he ); } return this; } @Override public BaseQueryImpl setParameter(Parameter<Calendar> param, Calendar value, TemporalType temporalType) { checkOpen( true ); try { findParameterRegistration( param ).bindValue( value, temporalType ); } catch (QueryParameterException e) { entityManager().markForRollbackOnly(); throw new IllegalArgumentException( e.getMessage(), e ); } catch (HibernateException he) { throw entityManager.convert( he ); } return this; } @Override public BaseQueryImpl setParameter(Parameter<Date> param, Date value, TemporalType temporalType) { checkOpen( true ); try { findParameterRegistration( param ).bindValue( value, temporalType ); } catch (QueryParameterException e) { entityManager().markForRollbackOnly(); throw new IllegalArgumentException( e.getMessage(), e ); } catch (HibernateException he) { throw entityManager.convert( he ); } return this; } @Override @SuppressWarnings("unchecked") public BaseQueryImpl setParameter(String name, Object value) { checkOpen( true ); try { findParameterRegistration( name ).bindValue( value ); } catch (QueryParameterException e) { entityManager().markForRollbackOnly(); throw new IllegalArgumentException( e.getMessage(), e ); } catch (HibernateException he) { throw entityManager.convert( he ); } return this; } @Override public BaseQueryImpl setParameter(String name, Calendar value, TemporalType temporalType) { checkOpen( true ); try { findParameterRegistration( name ).bindValue( value, temporalType ); } catch (QueryParameterException e) { entityManager().markForRollbackOnly(); throw new IllegalArgumentException( e.getMessage(), e ); } catch (HibernateException he) { throw entityManager.convert( he ); } return this; } @Override public BaseQueryImpl setParameter(String name, Date value, TemporalType temporalType) { checkOpen( true ); try { findParameterRegistration( name ).bindValue( value, temporalType ); } catch (QueryParameterException e) { entityManager().markForRollbackOnly(); throw new IllegalArgumentException( e.getMessage(), e ); } catch (HibernateException he) { throw entityManager.convert( he ); } return this; } @Override public BaseQueryImpl setParameter(int position, Object value) { checkOpen( true ); try { findParameterRegistration( position ).bindValue( value ); } catch (QueryParameterException e) { entityManager().markForRollbackOnly(); throw new IllegalArgumentException( e.getMessage(), e ); } catch (HibernateException he) { throw entityManager.convert( he ); } return this; } @Override public BaseQueryImpl setParameter(int position, Calendar value, TemporalType temporalType) { checkOpen( true ); try { findParameterRegistration( position ).bindValue( value, temporalType ); } catch (QueryParameterException e) { entityManager().markForRollbackOnly(); throw new IllegalArgumentException( e.getMessage(), e ); } catch (HibernateException he) { throw entityManager.convert( he ); } return this; } @Override public BaseQueryImpl setParameter(int position, Date value, TemporalType temporalType) { checkOpen( true ); try { findParameterRegistration( position ).bindValue( value, temporalType ); } catch (ParameterStrategyException e) { entityManager().markForRollbackOnly(); throw new IllegalArgumentException( "Invalid mix of named and positional parameters", e ); } catch (NoSuchParameterException e) { entityManager().markForRollbackOnly(); throw new IllegalArgumentException( e.getMessage(), e ); } catch (QueryParameterException e) { entityManager().markForRollbackOnly(); throw new IllegalArgumentException( e.getMessage(), e ); } catch (HibernateException he) { throw entityManager.convert( he ); } return this; } @Override @SuppressWarnings("unchecked") public Set getParameters() { checkOpen( false ); return parameterRegistrations(); } @Override public Parameter<?> getParameter(String name) { checkOpen( false ); return findParameterRegistration( name ); } @Override @SuppressWarnings("unchecked") public <T> Parameter<T> getParameter(String name, Class<T> type) { checkOpen( false ); Parameter param = findParameterRegistration( name ); if ( param.getParameterType() != null ) { // we were able to determine the expected type during analysis, so validate it here if ( ! param.getParameterType().isAssignableFrom( type ) ) { throw new IllegalArgumentException( String.format( "Parameter type [%s] is not assignment compatible with requested type [%s] for parameter named [%s]", param.getParameterType().getName(), type.getName(), name ) ); } } return (Parameter<T>) param; } @Override public Parameter<?> getParameter(int position) { checkOpen( false ); return findParameterRegistration( position ); } @Override @SuppressWarnings("unchecked") public <T> Parameter<T> getParameter(int position, Class<T> type) { checkOpen( false ); Parameter param = findParameterRegistration( position ); if ( param.getParameterType() != null ) { // we were able to determine the expected type during analysis, so validate it here if ( ! param.getParameterType().isAssignableFrom( type ) ) { throw new IllegalArgumentException( String.format( "Parameter type [%s] is not assignment compatible with requested type [%s] for parameter at position [%s]", param.getParameterType().getName(), type.getName(), position ) ); } } return (Parameter<T>) param; } @Override public boolean isBound(Parameter<?> param) { checkOpen( false ); final ParameterRegistration registration = findParameterRegistration( param ); return registration != null && registration.isBindable() && registration.getBind() != null; } @Override @SuppressWarnings("unchecked") public <T> T getParameterValue(Parameter<T> param) { checkOpen( false ); final ParameterRegistration<T> registration = findParameterRegistration( param ); if ( registration == null ) { throw new IllegalArgumentException( "Passed parameter [" + param + "] is not a (registered) parameter of this query" ); } if ( ! registration.isBindable() ) { throw new IllegalStateException( "Passed parameter [" + param + "] is not bindable" ); } final ParameterBind<T> bind = registration.getBind(); if ( bind == null ) { throw new IllegalStateException( "Parameter [" + param + "] has not yet been bound" ); } return bind.getValue(); } @Override public Object getParameterValue(String name) { checkOpen( false ); return getParameterValue( getParameter( name ) ); } @Override public Object getParameterValue(int position) { checkOpen( false ); return getParameterValue( getParameter( position ) ); } protected EntityGraphQueryHint getEntityGraphQueryHint() { return entityGraphQueryHint; } protected static void validateBinding(Class parameterType, Object bind, TemporalType temporalType) { if ( bind == null || parameterType == null ) { // nothing we can check return; } if ( Collection.class.isInstance( bind ) && ! Collection.class.isAssignableFrom( parameterType ) ) { // we have a collection passed in where we are expecting a non-collection. // NOTE : this can happen in Hibernate's notion of "parameter list" binding // NOTE2 : the case of a collection value and an expected collection (if that can even happen) // will fall through to the main check. validateCollectionValuedParameterBinding( parameterType, (Collection) bind, temporalType ); } else if ( bind.getClass().isArray() ) { validateArrayValuedParameterBinding( parameterType, bind, temporalType ); } else { if ( ! isValidBindValue( parameterType, bind, temporalType ) ) { throw new IllegalArgumentException( String.format( "Parameter value [%s] did not match expected type [%s (%s)]", bind, parameterType.getName(), extractName( temporalType ) ) ); } } } private static String extractName(TemporalType temporalType) { return temporalType == null ? "n/a" : temporalType.name(); } private static void validateCollectionValuedParameterBinding( Class parameterType, Collection value, TemporalType temporalType) { // validate the elements... for ( Object element : value ) { if ( ! isValidBindValue( parameterType, element, temporalType ) ) { throw new IllegalArgumentException( String.format( "Parameter value element [%s] did not match expected type [%s (%s)]", element, parameterType.getName(), extractName( temporalType ) ) ); } } } private static void validateArrayValuedParameterBinding( Class parameterType, Object value, TemporalType temporalType) { if ( ! parameterType.isArray() ) { throw new IllegalArgumentException( String.format( "Encountered array-valued parameter binding, but was expecting [%s (%s)]", parameterType.getName(), extractName( temporalType ) ) ); } if ( value.getClass().getComponentType().isPrimitive() ) { // we have a primitive array. we validate that the actual array has the component type (type of elements) // we expect based on the component type of the parameter specification if ( ! parameterType.getComponentType().isAssignableFrom( value.getClass().getComponentType() ) ) { throw new IllegalArgumentException( String.format( "Primitive array-valued parameter bind value type [%s] did not match expected type [%s (%s)]", value.getClass().getComponentType().getName(), parameterType.getName(), extractName( temporalType ) ) ); } } else { // we have an object array. Here we loop over the array and physically check each element against // the type we expect based on the component type of the parameter specification final Object[] array = (Object[]) value; for ( Object element : array ) { if ( ! isValidBindValue( parameterType.getComponentType(), element, temporalType ) ) { throw new IllegalArgumentException( String.format( "Array-valued parameter value element [%s] did not match expected type [%s (%s)]", element, parameterType.getName(), extractName( temporalType ) ) ); } } } } private static boolean isValidBindValue(Class expectedType, Object value, TemporalType temporalType) { if ( expectedType.isInstance( value ) ) { return true; } if ( temporalType != null ) { final boolean parameterDeclarationIsTemporal = Date.class.isAssignableFrom( expectedType ) || Calendar.class.isAssignableFrom( expectedType ); final boolean bindIsTemporal = Date.class.isInstance( value ) || Calendar.class.isInstance( value ); if ( parameterDeclarationIsTemporal && bindIsTemporal ) { return true; } } return false; } }