From 2f40949719fdb770e12716e8cb705a96046d24e3 Mon Sep 17 00:00:00 2001 From: Steve Ebersole Date: Wed, 3 Apr 2013 10:51:43 -0500 Subject: [PATCH] HHH-8129 - Unify BaseQueryImpl and AbstractQueryImpl hierarchies --- .../procedure/ParameterRegistration.java | 2 +- .../AbstractParameterRegistrationImpl.java | 2 +- .../org/hibernate/jpa/internal/QueryImpl.java | 580 +++++------------- .../internal/StoredProcedureQueryImpl.java | 120 +++- .../hibernate/jpa/spi/AbstractQueryImpl.java | 446 +++----------- .../org/hibernate/jpa/spi/BaseQueryImpl.java | 520 ++++++++++++---- .../hibernate/jpa/test/query/QueryTest.java | 7 +- 7 files changed, 748 insertions(+), 929 deletions(-) diff --git a/hibernate-core/src/main/java/org/hibernate/procedure/ParameterRegistration.java b/hibernate-core/src/main/java/org/hibernate/procedure/ParameterRegistration.java index 174c31cc92..5d8c8d493b 100644 --- a/hibernate-core/src/main/java/org/hibernate/procedure/ParameterRegistration.java +++ b/hibernate-core/src/main/java/org/hibernate/procedure/ParameterRegistration.java @@ -78,7 +78,7 @@ public interface ParameterRegistration { * * @return The parameter binding */ - public ParameterBind getParameterBind(); + public ParameterBind getBind(); /** * Bind a value to the parameter. How this value is bound to the underlying JDBC CallableStatement is diff --git a/hibernate-core/src/main/java/org/hibernate/procedure/internal/AbstractParameterRegistrationImpl.java b/hibernate-core/src/main/java/org/hibernate/procedure/internal/AbstractParameterRegistrationImpl.java index 5a75e403d4..5af4950331 100644 --- a/hibernate-core/src/main/java/org/hibernate/procedure/internal/AbstractParameterRegistrationImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/procedure/internal/AbstractParameterRegistrationImpl.java @@ -129,7 +129,7 @@ public abstract class AbstractParameterRegistrationImpl implements ParameterR } @Override - public ParameterBind getParameterBind() { + public ParameterBind getBind() { return bind; } diff --git a/hibernate-entitymanager/src/main/java/org/hibernate/jpa/internal/QueryImpl.java b/hibernate-entitymanager/src/main/java/org/hibernate/jpa/internal/QueryImpl.java index b17c94fe8c..80d37d0633 100755 --- a/hibernate-entitymanager/src/main/java/org/hibernate/jpa/internal/QueryImpl.java +++ b/hibernate-entitymanager/src/main/java/org/hibernate/jpa/internal/QueryImpl.java @@ -25,11 +25,10 @@ package org.hibernate.jpa.internal; import javax.persistence.NoResultException; import javax.persistence.NonUniqueResultException; -import javax.persistence.Parameter; +import javax.persistence.ParameterMode; import javax.persistence.PersistenceException; import javax.persistence.Query; import javax.persistence.TemporalType; -import javax.persistence.TransactionRequiredException; import javax.persistence.TypedQuery; import java.util.Calendar; import java.util.Collection; @@ -46,10 +45,10 @@ import org.hibernate.CacheMode; import org.hibernate.FlushMode; import org.hibernate.HibernateException; import org.hibernate.LockMode; -import org.hibernate.QueryParameterException; import org.hibernate.TypeMismatchException; import org.hibernate.engine.query.spi.NamedParameterDescriptor; import org.hibernate.engine.query.spi.OrdinalParameterDescriptor; +import org.hibernate.engine.query.spi.ParameterMetadata; import org.hibernate.engine.spi.SessionFactoryImplementor; import org.hibernate.hql.internal.QueryExecutionRequestException; import org.hibernate.internal.SQLQueryImpl; @@ -77,7 +76,6 @@ public class QueryImpl extends AbstractQueryImpl implements TypedQuery, private org.hibernate.Query query; private Set jpaPositionalIndices; - private Set> parameters; public QueryImpl(org.hibernate.Query query, AbstractEntityManagerImpl em) { this( query, em, Collections.emptyMap() ); @@ -98,13 +96,11 @@ public class QueryImpl extends AbstractQueryImpl implements TypedQuery, throw new IllegalStateException( "Unknown query type for parameter extraction" ); } - HashSet> parameters = new HashSet>(); - org.hibernate.internal.AbstractQueryImpl queryImpl = org.hibernate.internal.AbstractQueryImpl.class.cast( query ); + final ParameterMetadata parameterMetadata = org.hibernate.internal.AbstractQueryImpl.class.cast( query ).getParameterMetadata(); // extract named params - for ( String name : (Set) queryImpl.getParameterMetadata().getNamedParameterNames() ) { - final NamedParameterDescriptor descriptor = - queryImpl.getParameterMetadata().getNamedParameterDescriptor( name ); + for ( String name : (Set) parameterMetadata.getNamedParameterNames() ) { + final NamedParameterDescriptor descriptor = parameterMetadata.getNamedParameterDescriptor( name ); Class javaType = namedParameterTypeRedefinition.get( name ); if ( javaType != null && mightNeedRedefinition( javaType ) ) { descriptor.resetExpectedType( @@ -114,8 +110,7 @@ public class QueryImpl extends AbstractQueryImpl implements TypedQuery, else if ( descriptor.getExpectedType() != null ) { javaType = descriptor.getExpectedType().getReturnedClass(); } - final ParameterImpl parameter = new ParameterImpl( name, javaType ); - parameters.add( parameter ); + registerParameter( new ParameterRegistrationImpl( query, name, javaType ) ); if ( descriptor.isJpaStyle() ) { if ( jpaPositionalIndices == null ) { jpaPositionalIndices = new HashSet(); @@ -125,21 +120,15 @@ public class QueryImpl extends AbstractQueryImpl implements TypedQuery, } // extract positional parameters - for ( int i = 0, max = queryImpl.getParameterMetadata().getOrdinalParameterCount(); i < max; i++ ) { - final OrdinalParameterDescriptor descriptor = - queryImpl.getParameterMetadata().getOrdinalParameterDescriptor( i+1 ); - ParameterImpl parameter = new ParameterImpl( - i + 1, - descriptor.getExpectedType() == null - ? null - : descriptor.getExpectedType().getReturnedClass() - ); - parameters.add( parameter ); + for ( int i = 0, max = parameterMetadata.getOrdinalParameterCount(); i < max; i++ ) { + final OrdinalParameterDescriptor descriptor = parameterMetadata.getOrdinalParameterDescriptor( i + 1 ); + Class javaType = descriptor.getExpectedType() == null ? null : descriptor.getExpectedType().getReturnedClass(); + registerParameter( new ParameterRegistrationImpl( query, i+1, javaType ) ); Integer position = descriptor.getOrdinalPosition(); - if (jpaPositionalIndices != null && jpaPositionalIndices.contains(position)) LOG.parameterPositionOccurredAsBothJpaAndHibernatePositionalParameter(position); + if ( jpaPositionalIndices != null && jpaPositionalIndices.contains(position) ) { + LOG.parameterPositionOccurredAsBothJpaAndHibernatePositionalParameter(position); + } } - - this.parameters = java.util.Collections.unmodifiableSet( parameters ); } private SessionFactoryImplementor sfi() { @@ -151,149 +140,142 @@ public class QueryImpl extends AbstractQueryImpl implements TypedQuery, return java.util.Date.class.isAssignableFrom( javaType ); } - private static class ParameterImpl implements ParameterImplementor { + + + private static class ParameterRegistrationImpl implements ParameterRegistration { + private final org.hibernate.Query query; + private final String name; private final Integer position; - private final Class javaType; + private final Class javaType; - private ParameterImpl(String name, Class javaType) { + private ParameterBind bind; + + private ParameterRegistrationImpl(org.hibernate.Query query, String name, Class javaType) { + this.query = query; this.name = name; this.javaType = javaType; this.position = null; } - private ParameterImpl(Integer position, Class javaType) { + private ParameterRegistrationImpl(org.hibernate.Query query, Integer position, Class javaType) { + this.query = query; this.position = position; this.javaType = javaType; this.name = null; } + @Override public String getName() { return name; } + @Override public Integer getPosition() { return position; } - public Class getParameterType() { + @Override + public Class getParameterType() { return javaType; } @Override - public void validateBinding(Object bind, TemporalType temporalType) { - if ( bind == null || getParameterType() == null ) { - // nothing we can check - return; - } - - if ( Collection.class.isInstance( bind ) && ! Collection.class.isAssignableFrom( getParameterType() ) ) { - // 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( (Collection) bind, temporalType ); - } - else if ( bind.getClass().isArray() ) { - validateArrayValuedParameterBinding( bind, temporalType ); - } - else { - if ( ! isValidBindValue( getParameterType(), bind, temporalType ) ) { - throw new IllegalArgumentException( - String.format( - "Parameter value [%s] did not match expected type [%s (%s)]", - bind, - getParameterType().getName(), - extractName( temporalType ) - ) - ); - } - } + public ParameterMode getMode() { + // implicitly + return ParameterMode.IN; } - private String extractName(TemporalType temporalType) { - return temporalType == null ? "n/a" : temporalType.name(); - } - - private void validateCollectionValuedParameterBinding(Collection value, TemporalType temporalType) { - // validate the elements... - for ( Object element : value ) { - if ( ! isValidBindValue( getParameterType(), element, temporalType ) ) { - throw new IllegalArgumentException( - String.format( - "Parameter value element [%s] did not match expected type [%s (%s)]", - element, - getParameterType().getName(), - extractName( temporalType ) - ) - ); - } - } - } - - private void validateArrayValuedParameterBinding(Object value, TemporalType temporalType) { - if ( ! getParameterType().isArray() ) { - throw new IllegalArgumentException( - String.format( - "Encountered array-valued parameter binding, but was expecting [%s (%s)]", - getParameterType().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 ( ! getParameterType().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(), - getParameterType().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( getParameterType().getComponentType(), element, temporalType ) ) { - throw new IllegalArgumentException( - String.format( - "Array-valued parameter value element [%s] did not match expected type [%s (%s)]", - element, - getParameterType().getName(), - extractName( temporalType ) - ) - ); - } - } - } - } - } - - - private static boolean isValidBindValue(Class expectedType, Object value, TemporalType temporalType) { - if ( expectedType.isInstance( value ) ) { + @Override + public boolean isBindable() { + // again, implicitly 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 ); + @Override + public void bindValue(T value) { + validateBinding( getParameterType(), value, null ); - if ( parameterDeclarationIsTemporal && bindIsTemporal ) { - return true; + if ( name != null ) { + if ( value instanceof Collection ) { + query.setParameterList( name, (Collection) value ); + } + else { + query.setParameter( name, value ); + } } + else { + query.setParameter( position-1, value ); + } + + bind = new ParameterBindImpl( value, null ); } - return false; + @Override + public void bindValue(T value, TemporalType specifiedTemporalType) { + validateBinding( getParameterType(), value, specifiedTemporalType ); + + if ( Date.class.isInstance( value ) ) { + if ( name != null ) { + if ( specifiedTemporalType == DATE ) { + query.setDate( name, (Date) value ); + } + else if ( specifiedTemporalType == TIME ) { + query.setTime( name, (Date) value ); + } + else if ( specifiedTemporalType == TIMESTAMP ) { + query.setTimestamp( name, (Date) value ); + } + } + else { + if ( specifiedTemporalType == DATE ) { + query.setDate( position-1, (Date) value ); + } + else if ( specifiedTemporalType == TIME ) { + query.setTime( position-1, (Date) value ); + } + else if ( specifiedTemporalType == TIMESTAMP ) { + query.setTimestamp( position-1, (Date) value ); + } + } + } + else if ( Calendar.class.isInstance( value ) ) { + if ( name != null ) { + if ( specifiedTemporalType == DATE ) { + query.setCalendarDate( name, (Calendar) value ); + } + else if ( specifiedTemporalType == TIME ) { + throw new IllegalArgumentException( "not yet implemented" ); + } + else if ( specifiedTemporalType == TIMESTAMP ) { + query.setCalendar( name, (Calendar) value ); + } + } + else { + if ( specifiedTemporalType == DATE ) { + query.setCalendarDate( position-1, (Calendar) value ); + } + else if ( specifiedTemporalType == TIME ) { + throw new IllegalArgumentException( "not yet implemented" ); + } + else if ( specifiedTemporalType == TIMESTAMP ) { + query.setCalendar( position-1, (Calendar) value ); + } + } + } + else { + throw new IllegalArgumentException( + "Unexpected type [" + value + "] passed with TemporalType; expecting Date or Calendar" + ); + } + + bind = new ParameterBindImpl( value, specifiedTemporalType ); + } + + @Override + public ParameterBind getBind() { + return bind; + } } public org.hibernate.Query getHibernateQuery() { @@ -316,53 +298,60 @@ public class QueryImpl extends AbstractQueryImpl implements TypedQuery, } @Override - protected void applyTimeout(int timeout) { + protected boolean applyTimeoutHint(int timeout) { query.setTimeout( timeout ); + return true; } @Override - protected void applyComment(String comment) { + protected boolean applyCommentHint(String comment) { query.setComment( comment ); + return true; } @Override - protected void applyFetchSize(int fetchSize) { + protected boolean applyFetchSizeHint(int fetchSize) { query.setFetchSize( fetchSize ); + return true; } @Override - protected void applyCacheable(boolean isCacheable) { + protected boolean applyCacheableHint(boolean isCacheable) { query.setCacheable( isCacheable ); + return true; } @Override - protected void applyCacheRegion(String regionName) { + protected boolean applyCacheRegionHint(String regionName) { query.setCacheRegion( regionName ); + return true; } @Override - protected void applyReadOnly(boolean isReadOnly) { + protected boolean applyReadOnlyHint(boolean isReadOnly) { query.setReadOnly( isReadOnly ); + return true; } @Override - protected void applyCacheMode(CacheMode cacheMode) { + protected boolean applyCacheModeHint(CacheMode cacheMode) { query.setCacheMode( cacheMode ); + return true; } @Override - protected void applyFlushMode(FlushMode flushMode) { + protected boolean applyFlushModeHint(FlushMode flushMode) { query.setFlushMode( flushMode ); + return true; } @Override - protected boolean canApplyLockModes() { - return org.hibernate.internal.QueryImpl.class.isInstance( query ) - || SQLQueryImpl.class.isInstance( query ); + protected boolean canApplyAliasSpecificLockModeHints() { + return org.hibernate.internal.QueryImpl.class.isInstance( query ) || SQLQueryImpl.class.isInstance( query ); } @Override - protected void applyAliasSpecificLockMode(String alias, LockMode lockMode) { + protected void applyAliasSpecificLockModeHint(String alias, LockMode lockMode) { query.getLockOptions().setAliasSpecificLockMode( alias, lockMode ); } @@ -423,285 +412,10 @@ public class QueryImpl extends AbstractQueryImpl implements TypedQuery, } @Override - public TypedQuery setParameter(Parameter param, T value) { - getEntityManager().checkOpen( true ); - if ( ! parameters.contains( param ) ) { - throw new IllegalArgumentException( "Specified parameter was not found in query" ); - } - if ( param.getName() != null ) { - // a named param, for not delegate out. Eventually delegate *into* this method... - setParameter( param.getName(), value ); - } - else { - setParameter( param.getPosition(), value ); - } - return this; - } - - @Override - public TypedQuery setParameter(Parameter param, Date value, TemporalType temporalType) { - getEntityManager().checkOpen( true ); - if ( ! parameters.contains( param ) ) { - throw new IllegalArgumentException( "Specified parameter was not found in query" ); - } - if ( param.getName() != null ) { - // a named param, for not delegate out. Eventually delegate *into* this method... - setParameter( param.getName(), value, temporalType ); - } - else { - setParameter( param.getPosition(), value, temporalType ); - } - return this; - } - - @Override - public TypedQuery setParameter(Parameter param, Calendar value, TemporalType temporalType) { - getEntityManager().checkOpen( true ); - if ( ! parameters.contains( param ) ) { - throw new IllegalArgumentException( "Specified parameter was not found in query" ); - } - if ( param.getName() != null ) { - // a named param, for not delegate out. Eventually delegate *into* this method... - setParameter( param.getName(), value, temporalType ); - } - else { - setParameter( param.getPosition(), value, temporalType ); - } - return this; - } - - @Override - public TypedQuery setParameter(String name, Object value) { - getEntityManager().checkOpen( true ); - try { - if ( value instanceof Collection ) { - query.setParameterList( name, (Collection) value ); - } - else { - query.setParameter( name, value ); - } - registerParameterBinding( getParameter( name ), value, null ); - return this; - } - catch (QueryParameterException e) { - throw new IllegalArgumentException( e ); - } - catch (HibernateException he) { - throw getEntityManager().convert( he ); - } - } - - @Override - public TypedQuery setParameter(String name, Date value, TemporalType temporalType) { - getEntityManager().checkOpen( true ); - try { - if ( temporalType == DATE ) { - query.setDate( name, value ); - } - else if ( temporalType == TIME ) { - query.setTime( name, value ); - } - else if ( temporalType == TIMESTAMP ) { - query.setTimestamp( name, value ); - } - registerParameterBinding( getParameter( name ), value, temporalType ); - return this; - } - catch (QueryParameterException e) { - throw new IllegalArgumentException( e ); - } - catch (HibernateException he) { - throw getEntityManager().convert( he ); - } - } - - @Override - public TypedQuery setParameter(String name, Calendar value, TemporalType temporalType) { - getEntityManager().checkOpen( true ); - try { - if ( temporalType == DATE ) { - query.setCalendarDate( name, value ); - } - else if ( temporalType == TIME ) { - throw new IllegalArgumentException( "not yet implemented" ); - } - else if ( temporalType == TIMESTAMP ) { - query.setCalendar( name, value ); - } - registerParameterBinding( getParameter(name), value, temporalType ); - return this; - } - catch (QueryParameterException e) { - throw new IllegalArgumentException( e ); - } - catch (HibernateException he) { - throw getEntityManager().convert( he ); - } - } - - @Override - public TypedQuery setParameter(int position, Object value) { - getEntityManager().checkOpen( true ); - try { - if ( isJpaPositionalParameter( position ) ) { - this.setParameter( Integer.toString( position ), value ); - } - else { - query.setParameter( position - 1, value ); - registerParameterBinding( getParameter( position ), value, null ); - } - return this; - } - catch (QueryParameterException e) { - throw new IllegalArgumentException( e ); - } - catch (HibernateException he) { - throw getEntityManager().convert( he ); - } - } - - private boolean isJpaPositionalParameter(int position) { + protected boolean isJpaPositionalParameter(int position) { return jpaPositionalIndices != null && jpaPositionalIndices.contains( position ); } - @Override - public TypedQuery setParameter(int position, Date value, TemporalType temporalType) { - getEntityManager().checkOpen( true ); - try { - if ( isJpaPositionalParameter( position ) ) { - String name = Integer.toString( position ); - this.setParameter( name, value, temporalType ); - } - else { - if ( temporalType == DATE ) { - query.setDate( position - 1, value ); - } - else if ( temporalType == TIME ) { - query.setTime( position - 1, value ); - } - else if ( temporalType == TIMESTAMP ) { - query.setTimestamp( position - 1, value ); - } - registerParameterBinding( getParameter( position ), value, temporalType ); - } - return this; - } - catch (QueryParameterException e) { - throw new IllegalArgumentException( e ); - } - catch (HibernateException he) { - throw getEntityManager().convert( he ); - } - } - - @Override - public TypedQuery setParameter(int position, Calendar value, TemporalType temporalType) { - getEntityManager().checkOpen( true ); - try { - if ( isJpaPositionalParameter( position ) ) { - String name = Integer.toString( position ); - this.setParameter( name, value, temporalType ); - } - else { - if ( temporalType == DATE ) { - query.setCalendarDate( position - 1, value ); - } - else if ( temporalType == TIME ) { - throw new IllegalArgumentException( "not yet implemented" ); - } - else if ( temporalType == TIMESTAMP ) { - query.setCalendar( position - 1, value ); - } - registerParameterBinding( getParameter( position ), value, temporalType ); - } - return this; - } - catch (QueryParameterException e) { - throw new IllegalArgumentException( e ); - } - catch (HibernateException he) { - throw getEntityManager().convert( he ); - } - } - - @Override - public Set> getParameters() { - getEntityManager().checkOpen( false ); - return parameters; - } - - @Override - public Parameter getParameter(String name) { - getEntityManager().checkOpen( false ); - if ( name == null ) { - throw new IllegalArgumentException( "Name of parameter to locate cannot be null" ); - } - for ( Parameter parameter : parameters ) { - if ( name.equals( parameter.getName() ) ) { - return parameter; - } - } - throw new IllegalArgumentException( "Unable to locate parameter named [" + name + "]" ); - } - - @Override - public Parameter getParameter(int position) { - getEntityManager().checkOpen( false ); - if ( isJpaPositionalParameter( position ) ) { - return getParameter( Integer.toString( position ) ); - } - else { - for ( Parameter parameter : parameters ) { - if ( parameter.getPosition() != null && position == parameter.getPosition() ) { - return parameter; - } - } - throw new IllegalArgumentException( "Unable to locate parameter with position [" + position + "]" ); - } - } - - @Override - @SuppressWarnings({ "unchecked" }) - public Parameter getParameter(String name, Class type) { - getEntityManager().checkOpen( false ); - Parameter param = getParameter( 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 param; - } - - @Override - @SuppressWarnings({ "unchecked" }) - public Parameter getParameter(int position, Class type) { - getEntityManager().checkOpen( false ); - Parameter param = getParameter( 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 position [%s]", - param.getParameterType().getName(), - type.getName(), - position - ) - ); - } - } - return param; - } - @Override @SuppressWarnings({ "unchecked" }) public T unwrap(Class tClass) { @@ -723,35 +437,19 @@ public class QueryImpl extends AbstractQueryImpl implements TypedQuery, } } - private javax.persistence.LockModeType jpaLockMode = javax.persistence.LockModeType.NONE; - @Override - @SuppressWarnings({ "unchecked" }) - public TypedQuery setLockMode(javax.persistence.LockModeType lockModeType) { - getEntityManager().checkOpen( true ); - if (! getEntityManager().isTransactionInProgress()) { - throw new TransactionRequiredException( "no transaction is in progress" ); - } - if ( ! canApplyLockModes() ) { - throw new IllegalStateException( "Not a JPAQL/Criteria query" ); - } - this.jpaLockMode = lockModeType; + protected void internalApplyLockMode(javax.persistence.LockModeType lockModeType) { query.getLockOptions().setLockMode( LockModeTypeHelper.getLockMode( lockModeType ) ); if ( getHints() != null && getHints().containsKey( AvailableSettings.LOCK_TIMEOUT ) ) { - applyLockTimeout( ConfigurationHelper.getInteger( getHints().get( AvailableSettings.LOCK_TIMEOUT ) ) ); + applyLockTimeoutHint( ConfigurationHelper.getInteger( getHints().get( AvailableSettings.LOCK_TIMEOUT ) ) ); } - return this; } @Override - protected void applyLockTimeout(int timeout) { + protected boolean applyLockTimeoutHint(int timeout) { query.getLockOptions().setTimeOut( timeout ); + return true; } - @Override - public javax.persistence.LockModeType getLockMode() { - getEntityManager().checkOpen( false ); - return jpaLockMode; - } } diff --git a/hibernate-entitymanager/src/main/java/org/hibernate/jpa/internal/StoredProcedureQueryImpl.java b/hibernate-entitymanager/src/main/java/org/hibernate/jpa/internal/StoredProcedureQueryImpl.java index 53808f9573..aa9ddd912b 100644 --- a/hibernate-entitymanager/src/main/java/org/hibernate/jpa/internal/StoredProcedureQueryImpl.java +++ b/hibernate-entitymanager/src/main/java/org/hibernate/jpa/internal/StoredProcedureQueryImpl.java @@ -97,7 +97,11 @@ public class StoredProcedureQueryImpl extends BaseQueryImpl implements StoredPro @SuppressWarnings("unchecked") public StoredProcedureQuery registerStoredProcedureParameter(int position, Class type, ParameterMode mode) { entityManager().checkOpen( true ); - procedureCall.registerParameter( position, type, mode ); + registerParameter( + new ParameterRegistrationImpl( + procedureCall.registerParameter( position, type, mode ) + ) + ); return this; } @@ -105,27 +109,14 @@ public class StoredProcedureQueryImpl extends BaseQueryImpl implements StoredPro @SuppressWarnings("unchecked") public StoredProcedureQuery registerStoredProcedureParameter(String parameterName, Class type, ParameterMode mode) { entityManager().checkOpen( true ); - procedureCall.registerParameter( parameterName, type, mode ); + registerParameter( + new ParameterRegistrationImpl( + procedureCall.registerParameter( parameterName, type, mode ) + ) + ); return this; } - @Override - protected void validateParameterBindingTypes(ParameterImplementor parameter, ParameterValue bindValue) { - } - - - // covariant returns ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - - @Override - public StoredProcedureQueryImpl setFlushMode(FlushModeType jpaFlushMode) { - return (StoredProcedureQueryImpl) super.setFlushMode( jpaFlushMode ); - } - - @Override - public StoredProcedureQueryImpl setHint(String hintName, Object value) { - return (StoredProcedureQueryImpl) super.setHint( hintName, value ); - } - @Override public StoredProcedureQueryImpl setParameter(Parameter param, T value) { return (StoredProcedureQueryImpl) super.setParameter( param, value ); @@ -172,6 +163,19 @@ public class StoredProcedureQueryImpl extends BaseQueryImpl implements StoredPro } + // covariant returns ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + + @Override + public StoredProcedureQueryImpl setFlushMode(FlushModeType jpaFlushMode) { + return (StoredProcedureQueryImpl) super.setFlushMode( jpaFlushMode ); + } + + @Override + public StoredProcedureQueryImpl setHint(String hintName, Object value) { + return (StoredProcedureQueryImpl) super.setHint( hintName, value ); + } + + // outputs ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ private ProcedureResult outputs() { @@ -263,7 +267,7 @@ public class StoredProcedureQueryImpl extends BaseQueryImpl implements StoredPro } @Override - protected boolean canApplyLockModesHints() { + protected boolean canApplyAliasSpecificLockModeHints() { return false; } @@ -282,7 +286,81 @@ public class StoredProcedureQueryImpl extends BaseQueryImpl implements StoredPro } @Override - protected boolean applyFetchSize(int fetchSize) { + protected boolean applyFetchSizeHint(int fetchSize) { return false; } + + + // parameters ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + + @Override + protected boolean isJpaPositionalParameter(int position) { + return false; + } + + private static class ParameterRegistrationImpl implements ParameterRegistration { + private final org.hibernate.procedure.ParameterRegistration nativeParamRegistration; + + private ParameterBind bind; + + private ParameterRegistrationImpl(org.hibernate.procedure.ParameterRegistration nativeParamRegistration) { + this.nativeParamRegistration = nativeParamRegistration; + } + + @Override + public String getName() { + return nativeParamRegistration.getName(); + } + + @Override + public Integer getPosition() { + return nativeParamRegistration.getPosition(); + } + + @Override + public Class getParameterType() { + return nativeParamRegistration.getType(); + } + + @Override + public ParameterMode getMode() { + return nativeParamRegistration.getMode(); + } + + @Override + public boolean isBindable() { + return getMode() == ParameterMode.IN || getMode() == ParameterMode.INOUT; + } + + @Override + public void bindValue(T value) { + bindValue( value, null ); + } + + @Override + public void bindValue(T value, TemporalType specifiedTemporalType) { + validateBinding( getParameterType(), value, specifiedTemporalType ); + + nativeParamRegistration.bindValue( value,specifiedTemporalType ); + + if ( bind == null ) { + bind = new ParameterBind() { + @Override + public T getValue() { + return nativeParamRegistration.getBind().getValue(); + } + + @Override + public TemporalType getSpecifiedTemporalType() { + return nativeParamRegistration.getBind().getExplicitTemporalType(); + } + }; + } + } + + @Override + public ParameterBind getBind() { + return bind; + } + } } diff --git a/hibernate-entitymanager/src/main/java/org/hibernate/jpa/spi/AbstractQueryImpl.java b/hibernate-entitymanager/src/main/java/org/hibernate/jpa/spi/AbstractQueryImpl.java index bf90fbc8f3..ead3788b6e 100644 --- a/hibernate-entitymanager/src/main/java/org/hibernate/jpa/spi/AbstractQueryImpl.java +++ b/hibernate-entitymanager/src/main/java/org/hibernate/jpa/spi/AbstractQueryImpl.java @@ -23,65 +23,35 @@ */ package org.hibernate.jpa.spi; -import javax.persistence.CacheRetrieveMode; -import javax.persistence.CacheStoreMode; import javax.persistence.FlushModeType; import javax.persistence.Parameter; import javax.persistence.TemporalType; import javax.persistence.TransactionRequiredException; import javax.persistence.TypedQuery; -import java.util.Collection; -import java.util.HashMap; -import java.util.Map; +import java.util.Calendar; +import java.util.Date; import java.util.Set; -import org.jboss.logging.Logger; - -import org.hibernate.CacheMode; -import org.hibernate.FlushMode; import org.hibernate.HibernateException; -import org.hibernate.LockMode; import org.hibernate.TypeMismatchException; import org.hibernate.hql.internal.QueryExecutionRequestException; -import org.hibernate.jpa.AvailableSettings; import org.hibernate.jpa.QueryHints; -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 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_FETCH_SIZE; -import static org.hibernate.jpa.QueryHints.HINT_FLUSH_MODE; -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 a base class providing convenience in implementing both {@link javax.persistence.Query} and - * {@link javax.persistence.TypedQuery}. + * Base class for implementing both {@link javax.persistence.Query} and {@link javax.persistence.TypedQuery}, including + * query references built from criteria queries. *

- * IMPL NOTE : This issue, and the reason for this distinction, is that criteria and hl.sql queries share no - * commonality currently in Hibernate internals. + * Not intended as base for {@link javax.persistence.StoredProcedureQuery} * * @author Steve Ebersole */ -public abstract class AbstractQueryImpl implements TypedQuery { - - private static final EntityManagerMessageLogger LOG = Logger.getMessageLogger(EntityManagerMessageLogger.class, - AbstractQueryImpl.class.getName()); - - private final HibernateEntityManagerImplementor entityManager; - +public abstract class AbstractQueryImpl extends BaseQueryImpl implements TypedQuery { public AbstractQueryImpl(HibernateEntityManagerImplementor entityManager) { - this.entityManager = entityManager; + super( entityManager ); } protected HibernateEntityManagerImplementor getEntityManager() { - return entityManager; + return entityManager(); } /** @@ -94,10 +64,14 @@ public abstract class AbstractQueryImpl implements TypedQuery { @Override @SuppressWarnings({ "ThrowableInstanceNeverThrown" }) public int executeUpdate() { - entityManager.checkOpen( true ); + checkOpen( true ); try { - if ( ! entityManager.isTransactionInProgress() ) { - entityManager.throwPersistenceException( new TransactionRequiredException( "Executing an update/delete query" ) ); + if ( ! entityManager().isTransactionInProgress() ) { + entityManager().throwPersistenceException( + new TransactionRequiredException( + "Executing an update/delete query" + ) + ); return 0; } return internalExecuteUpdate(); @@ -109,203 +83,27 @@ public abstract class AbstractQueryImpl implements TypedQuery { throw new IllegalArgumentException(e); } catch ( HibernateException he) { - entityManager.throwPersistenceException( he ); + entityManager().throwPersistenceException( he ); return 0; } } - private int maxResults = -1; - - /** - * Apply the given max results value. - * - * @param maxResults The specified max results - */ - protected abstract void applyMaxResults(int maxResults); - @Override - public TypedQuery setMaxResults(int maxResult) { - entityManager.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; + @SuppressWarnings("unchecked") + public AbstractQueryImpl setMaxResults(int maxResults) { + return (AbstractQueryImpl) super.setMaxResults( maxResults ); } @Override - public int getMaxResults() { - entityManager.checkOpen( false ); // technically this should rollback the txn - return maxResults == -1 - ? Integer.MAX_VALUE // stupid spec... MAX_VALUE?? - : maxResults; + @SuppressWarnings("unchecked") + public AbstractQueryImpl setFirstResult(int firstResult) { + return (AbstractQueryImpl) super.setFirstResult( firstResult ); } - private int firstResult; - - /** - * Apply the given first-result value. - * - * @param firstResult The specified first-result value. - */ - protected abstract void applyFirstResult(int firstResult); - - @Override - public TypedQuery setFirstResult(int firstResult) { - entityManager.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() { - entityManager.checkOpen( false ); // technically this should rollback the txn - return firstResult; - } - - private Map hints; - - @Override - public Map getHints() { - entityManager.checkOpen( false ); // technically this should rollback the txn - return hints; - } - - protected abstract void applyTimeout(int timeout); - - protected abstract void applyLockTimeout(int timeout); - - protected abstract void applyComment(String comment); - - protected abstract void applyFetchSize(int fetchSize); - - protected abstract void applyCacheable(boolean isCacheable); - - protected abstract void applyCacheRegion(String regionName); - - protected abstract void applyReadOnly(boolean isReadOnly); - - protected abstract void applyCacheMode(CacheMode cacheMode); - - protected abstract void applyFlushMode(FlushMode flushMode); - - protected abstract boolean canApplyLockModes(); - - protected abstract void applyAliasSpecificLockMode(String alias, LockMode lockMode); - @Override @SuppressWarnings( {"deprecation"}) - public TypedQuery setHint(String hintName, Object value) { - entityManager.checkOpen( true ); - boolean skipped = false; - try { - if ( HINT_TIMEOUT.equals( hintName ) ) { - applyTimeout( 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 ); - applyTimeout( timeout ); - } - else if ( AvailableSettings.LOCK_TIMEOUT.equals( hintName ) ) { - applyLockTimeout( ConfigurationHelper.getInteger( value ) ); - } - else if ( HINT_COMMENT.equals( hintName ) ) { - applyComment( (String) value ); - } - else if ( HINT_FETCH_SIZE.equals( hintName ) ) { - applyFetchSize( ConfigurationHelper.getInteger( value ) ); - } - else if ( HINT_CACHEABLE.equals( hintName ) ) { - applyCacheable( ConfigurationHelper.getBoolean( value ) ); - } - else if ( HINT_CACHE_REGION.equals( hintName ) ) { - applyCacheRegion( (String) value ); - } - else if ( HINT_READONLY.equals( hintName ) ) { - applyReadOnly( ConfigurationHelper.getBoolean( value ) ); - } - else if ( HINT_CACHE_MODE.equals( hintName ) ) { - applyCacheMode( ConfigurationHelper.getCacheMode( value ) ); - } - else if ( HINT_FLUSH_MODE.equals( hintName ) ) { - applyFlushMode( ConfigurationHelper.getFlushMode( value ) ); - } - else if ( AvailableSettings.SHARED_CACHE_RETRIEVE_MODE.equals( hintName ) ) { - final CacheRetrieveMode retrieveMode = (CacheRetrieveMode) value; - - CacheStoreMode storeMode = hints != null - ? (CacheStoreMode) hints.get( AvailableSettings.SHARED_CACHE_STORE_MODE ) - : null; - if ( storeMode == null ) { - storeMode = (CacheStoreMode) entityManager.getProperties() - .get( AvailableSettings.SHARED_CACHE_STORE_MODE ); - } - applyCacheMode( - CacheModeHelper.interpretCacheMode( storeMode, retrieveMode ) - ); - } - else if ( AvailableSettings.SHARED_CACHE_STORE_MODE.equals( hintName ) ) { - final CacheStoreMode storeMode = (CacheStoreMode) value; - - CacheRetrieveMode retrieveMode = hints != null - ? (CacheRetrieveMode) hints.get( AvailableSettings.SHARED_CACHE_RETRIEVE_MODE ) - : null; - if ( retrieveMode == null ) { - retrieveMode = (CacheRetrieveMode) entityManager.getProperties() - .get( AvailableSettings.SHARED_CACHE_RETRIEVE_MODE ); - } - applyCacheMode( - CacheModeHelper.interpretCacheMode( storeMode, retrieveMode ) - ); - } - else if ( hintName.startsWith( AvailableSettings.ALIAS_SPECIFIC_LOCK_MODE ) ) { - if ( ! canApplyLockModes() ) { - skipped = true; - } - else { - // 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 ); - applyAliasSpecificLockMode( alias, lockMode ); - } - catch ( Exception e ) { - LOG.unableToDetermineLockModeValue(hintName, value); - skipped = true; - } - } - } - else { - skipped = true; - LOG.ignoringUnrecognizedQueryHint(hintName); - } - } - catch ( ClassCastException e ) { - throw new IllegalArgumentException( "Value for hint" ); - } - - if ( !skipped ) { - if ( hints == null ) { - hints = new HashMap(); - } - hints.put( hintName, value ); - } - + public AbstractQueryImpl setHint(String hintName, Object value) { + super.setHint( hintName, value ); return this; } @@ -314,137 +112,91 @@ public abstract class AbstractQueryImpl implements TypedQuery { return QueryHints.getDefinedHints(); } - @Override - public abstract TypedQuery setLockMode(javax.persistence.LockModeType lockModeType); - - @Override - public abstract javax.persistence.LockModeType getLockMode(); - - private FlushModeType jpaFlushMode; - - @Override - public TypedQuery setFlushMode(FlushModeType jpaFlushMode) { - entityManager.checkOpen( true ); - this.jpaFlushMode = jpaFlushMode; - // TODO : treat as hint? - if ( jpaFlushMode == FlushModeType.AUTO ) { - applyFlushMode( FlushMode.AUTO ); - } - else if ( jpaFlushMode == FlushModeType.COMMIT ) { - applyFlushMode( FlushMode.COMMIT ); - } - return this; - } - - @SuppressWarnings( {"UnusedDeclaration"}) - protected FlushModeType getSpecifiedFlushMode() { - return jpaFlushMode; - } - - @Override - public FlushModeType getFlushMode() { - entityManager.checkOpen( false ); // technically this should rollback the txn - return jpaFlushMode != null - ? jpaFlushMode - : entityManager.getFlushMode(); - } - - - private Map parameterBindings; - - protected static interface ParameterImplementor extends Parameter { - public void validateBinding(Object bind, TemporalType temporalType); - } - - @SuppressWarnings( {"unchecked"}) - protected void registerParameterBinding(Parameter parameter, Object value, TemporalType temporalType) { - if ( parameter == null ) { - throw new IllegalArgumentException( "parameter cannot be null" ); - } - - ( (ParameterImplementor) parameter ).validateBinding( value, temporalType ); - - if ( parameterBindings == null ) { - parameterBindings = new HashMap(); - } - parameterBindings.put( parameter, value ); - } - - private void validateArrayValuedParameterBinding(ParameterImplementor parameter, Object value) { - if ( ! parameter.getParameterType().isArray() ) { - throw new IllegalArgumentException( - String.format( - "Encountered array-valued parameter binding, but was expecting [%s]", - parameter.getParameterType().getName() - ) - ); - } - - if ( value.getClass().getComponentType().isPrimitive() ) { - // we have a primitive array. we validate that the actual array has the component type (type odf elements) - // we expect based on the component type of the parameter specification - if ( ! parameter.getParameterType().getComponentType().isAssignableFrom( value.getClass().getComponentType() ) ) { - throw new IllegalArgumentException( - String.format( - "Primitive array-valued parameter bind value type [%s] did not match expected type [%s]", - value.getClass().getComponentType().getName(), - parameter.getParameterType().getName() - ) - ); - } - } - 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 ( ! parameter.getParameterType().getComponentType().isInstance( element ) ) { - throw new IllegalArgumentException( - String.format( - "Array-valued parameter value element [%s] did not match expected type [%s]", - element, - parameter.getParameterType().getName() - ) - ); - } - } - } - } - - @Override - public boolean isBound(Parameter param) { - entityManager.checkOpen( false ); // technically this should rollback the txn - return parameterBindings != null && parameterBindings.containsKey( param ); - } + private javax.persistence.LockModeType jpaLockMode = javax.persistence.LockModeType.NONE; @Override @SuppressWarnings({ "unchecked" }) - public T getParameterValue(Parameter param) { - entityManager.checkOpen( false ); // technically this should rollback the txn - if ( parameterBindings == null ) { - throw new IllegalStateException( "No parameters have been bound" ); + public TypedQuery setLockMode(javax.persistence.LockModeType lockModeType) { + checkOpen( true ); + if (! getEntityManager().isTransactionInProgress()) { + throw new TransactionRequiredException( "no transaction is in progress" ); } - try { - T value = (T) parameterBindings.get( param ); - if ( value == null ) { - throw new IllegalStateException( "Parameter has not been bound" ); - } - return value; - } - catch ( ClassCastException cce ) { - throw new IllegalStateException( "Encountered a parameter value type exception" ); + if ( ! canApplyAliasSpecificLockModeHints() ) { + throw new IllegalStateException( "Not a JPAQL/Criteria query" ); } + this.jpaLockMode = lockModeType; + internalApplyLockMode( lockModeType ); + return this; + } + + protected abstract void internalApplyLockMode(javax.persistence.LockModeType lockModeType); + + @Override + public javax.persistence.LockModeType getLockMode() { + getEntityManager().checkOpen( false ); + return jpaLockMode; + } + + + // convariant return handling ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + + @Override + @SuppressWarnings("unchecked") + public AbstractQueryImpl setParameter(Parameter param, T value) { + return (AbstractQueryImpl) super.setParameter( param, value ); } @Override - public Object getParameterValue(String name) { - entityManager.checkOpen( false ); // technically this should rollback the txn - return getParameterValue( getParameter( name ) ); + @SuppressWarnings("unchecked") + public AbstractQueryImpl setParameter(Parameter param, Calendar value, TemporalType temporalType) { + return (AbstractQueryImpl) super.setParameter( param, value, temporalType ); } @Override - public Object getParameterValue(int position) { - entityManager.checkOpen( false ); // technically this should rollback the txn - return getParameterValue( getParameter( position ) ); + @SuppressWarnings("unchecked") + public AbstractQueryImpl setParameter(Parameter param, Date value, TemporalType temporalType) { + return (AbstractQueryImpl) super.setParameter( param, value, temporalType ); + } + + @Override + @SuppressWarnings("unchecked") + public AbstractQueryImpl setParameter(String name, Object value) { + return (AbstractQueryImpl) super.setParameter( name, value ); + } + + @Override + @SuppressWarnings("unchecked") + public AbstractQueryImpl setParameter(String name, Calendar value, TemporalType temporalType) { + return (AbstractQueryImpl) super.setParameter( name, value, temporalType ); + } + + @Override + @SuppressWarnings("unchecked") + public AbstractQueryImpl setParameter(String name, Date value, TemporalType temporalType) { + return (AbstractQueryImpl) super.setParameter( name, value, temporalType ); + } + + @Override + @SuppressWarnings("unchecked") + public AbstractQueryImpl setParameter(int position, Object value) { + return (AbstractQueryImpl) super.setParameter( position, value ); + } + + @Override + @SuppressWarnings("unchecked") + public AbstractQueryImpl setParameter(int position, Calendar value, TemporalType temporalType) { + return (AbstractQueryImpl) super.setParameter( position, value, temporalType ); + } + + @Override + @SuppressWarnings("unchecked") + public AbstractQueryImpl setParameter(int position, Date value, TemporalType temporalType) { + return (AbstractQueryImpl) super.setParameter( position, value, temporalType ); + } + + @Override + @SuppressWarnings("unchecked") + public AbstractQueryImpl setFlushMode(FlushModeType jpaFlushMode) { + return (AbstractQueryImpl) super.setFlushMode( jpaFlushMode ); } } diff --git a/hibernate-entitymanager/src/main/java/org/hibernate/jpa/spi/BaseQueryImpl.java b/hibernate-entitymanager/src/main/java/org/hibernate/jpa/spi/BaseQueryImpl.java index 8c62ef9c4a..6086503e43 100644 --- a/hibernate-entitymanager/src/main/java/org/hibernate/jpa/spi/BaseQueryImpl.java +++ b/hibernate-entitymanager/src/main/java/org/hibernate/jpa/spi/BaseQueryImpl.java @@ -27,12 +27,14 @@ import javax.persistence.CacheRetrieveMode; import javax.persistence.CacheStoreMode; import javax.persistence.FlushModeType; import javax.persistence.Parameter; +import javax.persistence.ParameterMode; import javax.persistence.Query; import javax.persistence.TemporalType; import java.util.Calendar; +import java.util.Collection; import java.util.Date; import java.util.HashMap; -import java.util.List; +import java.util.HashSet; import java.util.Map; import java.util.Set; @@ -40,7 +42,9 @@ import org.jboss.logging.Logger; import org.hibernate.CacheMode; import org.hibernate.FlushMode; +import org.hibernate.HibernateException; import org.hibernate.LockMode; +import org.hibernate.QueryParameterException; import org.hibernate.jpa.AvailableSettings; import org.hibernate.jpa.QueryHints; import org.hibernate.jpa.internal.EntityManagerMessageLogger; @@ -59,9 +63,9 @@ import static org.hibernate.jpa.QueryHints.HINT_TIMEOUT; import static org.hibernate.jpa.QueryHints.SPEC_HINT_TIMEOUT; /** - * 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. + * 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 */ @@ -86,6 +90,10 @@ public abstract class BaseQueryImpl implements Query { return entityManager; } + protected void checkOpen(boolean markForRollbackIfClosed) { + entityManager.checkOpen( markForRollbackIfClosed ); + } + // Limits (first and max results) ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -98,7 +106,7 @@ public abstract class BaseQueryImpl implements Query { @Override public BaseQueryImpl setFirstResult(int firstResult) { - entityManager().checkOpen( true ); + checkOpen( true ); if ( firstResult < 0 ) { throw new IllegalArgumentException( @@ -112,7 +120,7 @@ public abstract class BaseQueryImpl implements Query { @Override public int getFirstResult() { - entityManager().checkOpen( false ); // technically should rollback + checkOpen( false ); // technically should rollback return firstResult; } @@ -125,7 +133,7 @@ public abstract class BaseQueryImpl implements Query { @Override public BaseQueryImpl setMaxResults(int maxResult) { - entityManager().checkOpen( true ); + checkOpen( true ); if ( maxResult < 0 ) { throw new IllegalArgumentException( "Negative value (" + maxResult + ") passed to setMaxResults" @@ -142,7 +150,7 @@ public abstract class BaseQueryImpl implements Query { @Override public int getMaxResults() { - entityManager().checkOpen( false ); // technically should rollback + checkOpen( false ); // technically should rollback return maxResults == -1 ? Integer.MAX_VALUE // stupid spec... MAX_VALUE?? : maxResults; @@ -158,7 +166,7 @@ public abstract class BaseQueryImpl implements Query { @Override public Map getHints() { - entityManager().checkOpen( false ); // technically should rollback + checkOpen( false ); // technically should rollback return hints; } @@ -196,7 +204,7 @@ public abstract class BaseQueryImpl implements Query { * * @return {@code true} if the hint was "applied" */ - protected abstract boolean applyFetchSize(int fetchSize); + protected abstract boolean applyFetchSizeHint(int fetchSize); /** * Apply the cacheable (true/false) hint. @@ -248,11 +256,11 @@ public abstract class BaseQueryImpl implements Query { * * @return {@code true} indicates they can be applied, {@code false} otherwise. */ - protected abstract boolean canApplyLockModesHints(); + protected abstract boolean canApplyAliasSpecificLockModeHints(); /** - * Apply the alias specific lock modes. Assumes {@link #canApplyLockModesHints()} has already been called and - * returned {@code true}. + * 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. @@ -262,7 +270,7 @@ public abstract class BaseQueryImpl implements Query { @Override @SuppressWarnings( {"deprecation"}) public BaseQueryImpl setHint(String hintName, Object value) { - entityManager().checkOpen( true ); + checkOpen( true ); boolean applied = false; try { if ( HINT_TIMEOUT.equals( hintName ) ) { @@ -280,7 +288,7 @@ public abstract class BaseQueryImpl implements Query { applied = applyCommentHint( (String) value ); } else if ( HINT_FETCH_SIZE.equals( hintName ) ) { - applied = applyFetchSize( ConfigurationHelper.getInteger( value ) ); + applied = applyFetchSizeHint( ConfigurationHelper.getInteger( value ) ); } else if ( HINT_CACHEABLE.equals( hintName ) ) { applied = applyCacheableHint( ConfigurationHelper.getBoolean( value ) ); @@ -322,7 +330,7 @@ public abstract class BaseQueryImpl implements Query { ); } else if ( hintName.startsWith( AvailableSettings.ALIAS_SPECIFIC_LOCK_MODE ) ) { - if ( canApplyLockModesHints() ) { + if ( canApplyAliasSpecificLockModeHints() ) { // extract the alias final String alias = hintName.substring( AvailableSettings.ALIAS_SPECIFIC_LOCK_MODE.length() + 1 ); // determine the LockMode @@ -367,7 +375,7 @@ public abstract class BaseQueryImpl implements Query { @Override public BaseQueryImpl setFlushMode(FlushModeType jpaFlushMode) { - entityManager().checkOpen( true ); + checkOpen( true ); this.jpaFlushMode = jpaFlushMode; // TODO : treat as hint? if ( jpaFlushMode == FlushModeType.AUTO ) { @@ -386,7 +394,7 @@ public abstract class BaseQueryImpl implements Query { @Override public FlushModeType getFlushMode() { - entityManager().checkOpen( false ); + checkOpen( false ); return jpaFlushMode != null ? jpaFlushMode : entityManager.getFlushMode(); @@ -395,27 +403,78 @@ public abstract class BaseQueryImpl implements Query { // Parameters ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - private List parameters; + private Set> parameterRegistrations; + + protected ParameterRegistration findParameterRegistration(Parameter parameter) { + if ( ParameterRegistration.class.isInstance( parameter ) ) { + return (ParameterRegistration) parameter; + } + 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 ParameterRegistration findParameterRegistration(String parameterName) { + return (ParameterRegistration) getParameter( parameterName ); + } + + @SuppressWarnings("unchecked") + protected ParameterRegistration findParameterRegistration(int parameterPosition) { + if ( isJpaPositionalParameter( parameterPosition ) ) { + return findParameterRegistration( Integer.toString( parameterPosition ) ); + } + else { + return (ParameterRegistration) getParameter( parameterPosition ); + } + } + + protected abstract boolean isJpaPositionalParameter(int position); /** * Hibernate specific extension to the JPA {@link javax.persistence.Parameter} contract. */ - protected static interface ParameterImplementor extends Parameter { + protected static interface ParameterRegistration extends Parameter { + /** + * Retrieves the parameter "mode" which describes how the parameter is defined in the actual database procedure + * definition (is it an INPUT parameter? An OUTPUT parameter? etc). + * + * @return The parameter mode. + */ + public ParameterMode getMode(); + public boolean isBindable(); - public ParameterValue getBoundValue(); + public void bindValue(T value); + + public void bindValue(T value, TemporalType specifiedTemporalType); + + public ParameterBind getBind(); } - protected static class ParameterValue { - private final Object value; + protected static interface ParameterBind { + public T getValue(); + + public TemporalType getSpecifiedTemporalType(); + } + + protected static class ParameterBindImpl implements ParameterBind { + private final T value; private final TemporalType specifiedTemporalType; - public ParameterValue(Object value, TemporalType specifiedTemporalType) { + public ParameterBindImpl(T value, TemporalType specifiedTemporalType) { this.value = value; this.specifiedTemporalType = specifiedTemporalType; } - public Object getValue() { + public T getValue() { return value; } @@ -424,142 +483,193 @@ public abstract class BaseQueryImpl implements Query { } } - private Map,ParameterValue> parameterBindingMap; - - private Map,ParameterValue> parameterBindingMap() { - if ( parameterBindingMap == null ) { - parameterBindingMap = new HashMap, ParameterValue>(); + private Set> parameterRegistrations() { + if ( parameterRegistrations == null ) { + // todo : could se use an identity set here? + parameterRegistrations = new HashSet>(); } - return parameterBindingMap; + return parameterRegistrations; } - protected void registerParameter(ParameterImplementor parameter) { + protected void registerParameter(ParameterRegistration parameter) { if ( parameter == null ) { throw new IllegalArgumentException( "parameter cannot be null" ); } - if ( parameterBindingMap().containsKey( parameter ) ) { + if ( parameterRegistrations().contains( parameter ) ) { + LOG.debug( "Parameter registered multiple times : " + parameter ); return; } - parameterBindingMap().put( parameter, null ); - } - - @SuppressWarnings("unchecked") - protected void registerParameterBinding(Parameter parameter, ParameterValue bindValue) { - validateParameterBinding( (ParameterImplementor) parameter, bindValue ); - parameterBindingMap().put( (ParameterImplementor) parameter, bindValue ); - } - - protected void validateParameterBinding(ParameterImplementor parameter, ParameterValue bindValue) { - if ( parameter == null ) { - throw new IllegalArgumentException( "parameter cannot be null" ); - } - - if ( ! parameter.isBindable() ) { - throw new IllegalArgumentException( "Parameter [" + parameter + "] not valid for binding" ); - } - - if ( ! parameterBindingMap().containsKey( parameter ) ) { - throw new IllegalArgumentException( "Unknown parameter [" + parameter + "] specified for value binding" ); - } - - if ( isBound( parameter ) ) { - throw new IllegalArgumentException( "Parameter [" + parameter + "] already had bound value" ); - } - - validateParameterBindingTypes( parameter, bindValue ); - } - - protected abstract void validateParameterBindingTypes(ParameterImplementor parameter, ParameterValue bindValue); - - protected ParameterValue makeBindValue(Object value) { - return new ParameterValue( value, null ); - } - - protected ParameterValue makeBindValue(Calendar value, TemporalType temporalType) { - return new ParameterValue( value, temporalType ); - } - - protected ParameterValue makeBindValue(Date value, TemporalType temporalType) { - return new ParameterValue( value, temporalType ); + parameterRegistrations().add( parameter ); } @Override public BaseQueryImpl setParameter(Parameter param, T value) { - entityManager().checkOpen( true ); - registerParameterBinding( param, makeBindValue( value ) ); + checkOpen( true ); + + try { + findParameterRegistration( param ).bindValue( value ); + } + catch (QueryParameterException e) { + throw new IllegalArgumentException( e ); + } + catch (HibernateException he) { + throw entityManager.convert( he ); + } + return this; } @Override public BaseQueryImpl setParameter(Parameter param, Calendar value, TemporalType temporalType) { - entityManager().checkOpen( true ); - registerParameterBinding( param, makeBindValue( value, temporalType ) ); + checkOpen( true ); + + try { + findParameterRegistration( param ).bindValue( value, temporalType ); + } + catch (QueryParameterException e) { + throw new IllegalArgumentException( e ); + } + catch (HibernateException he) { + throw entityManager.convert( he ); + } + return this; } @Override public BaseQueryImpl setParameter(Parameter param, Date value, TemporalType temporalType) { - entityManager().checkOpen( true ); - registerParameterBinding( param, makeBindValue( value, temporalType ) ); + checkOpen( true ); + + try { + findParameterRegistration( param ).bindValue( value, temporalType ); + } + catch (QueryParameterException e) { + throw new IllegalArgumentException( e ); + } + catch (HibernateException he) { + throw entityManager.convert( he ); + } + return this; } @Override + @SuppressWarnings("unchecked") public BaseQueryImpl setParameter(String name, Object value) { - entityManager().checkOpen( true ); - registerParameterBinding( getParameter( name ), makeBindValue( value ) ); + checkOpen( true ); + + try { + findParameterRegistration( name ).bindValue( value ); + } + catch (QueryParameterException e) { + throw new IllegalArgumentException( e ); + } + catch (HibernateException he) { + throw entityManager.convert( he ); + } + return this; } @Override public BaseQueryImpl setParameter(String name, Calendar value, TemporalType temporalType) { - entityManager().checkOpen( true ); - registerParameterBinding( getParameter( name ), makeBindValue( value, temporalType ) ); + checkOpen( true ); + + try { + findParameterRegistration( name ).bindValue( value, temporalType ); + } + catch (QueryParameterException e) { + throw new IllegalArgumentException( e ); + } + catch (HibernateException he) { + throw entityManager.convert( he ); + } + return this; } @Override public BaseQueryImpl setParameter(String name, Date value, TemporalType temporalType) { - entityManager().checkOpen( true ); - registerParameterBinding( getParameter( name ), makeBindValue( value, temporalType ) ); + checkOpen( true ); + + try { + findParameterRegistration( name ).bindValue( value, temporalType ); + } + catch (QueryParameterException e) { + throw new IllegalArgumentException( e ); + } + catch (HibernateException he) { + throw entityManager.convert( he ); + } + return this; } @Override public BaseQueryImpl setParameter(int position, Object value) { - entityManager().checkOpen( true ); - registerParameterBinding( getParameter( position ), makeBindValue( value ) ); + checkOpen( true ); + + try { + findParameterRegistration( position ).bindValue( value ); + } + catch (QueryParameterException e) { + throw new IllegalArgumentException( e ); + } + catch (HibernateException he) { + throw entityManager.convert( he ); + } + return this; } @Override public BaseQueryImpl setParameter(int position, Calendar value, TemporalType temporalType) { - entityManager().checkOpen( true ); - registerParameterBinding( getParameter( position ), makeBindValue( value, temporalType ) ); + checkOpen( true ); + + try { + findParameterRegistration( position ).bindValue( value, temporalType ); + } + catch (QueryParameterException e) { + throw new IllegalArgumentException( e ); + } + catch (HibernateException he) { + throw entityManager.convert( he ); + } + return this; } @Override public BaseQueryImpl setParameter(int position, Date value, TemporalType temporalType) { - entityManager().checkOpen( true ); - registerParameterBinding( getParameter( position ), makeBindValue( value, temporalType ) ); + checkOpen( true ); + + try { + findParameterRegistration( position ).bindValue( value, temporalType ); + } + catch (QueryParameterException e) { + throw new IllegalArgumentException( e ); + } + catch (HibernateException he) { + throw entityManager.convert( he ); + } + return this; } @Override @SuppressWarnings("unchecked") public Set getParameters() { - entityManager().checkOpen( false ); - return parameterBindingMap().keySet(); + checkOpen( false ); + return parameterRegistrations(); } @Override public Parameter getParameter(String name) { - entityManager().checkOpen( false ); - if ( parameterBindingMap() != null ) { - for ( ParameterImplementor param : parameterBindingMap.keySet() ) { + checkOpen( false ); + if ( parameterRegistrations != null ) { + for ( ParameterRegistration param : parameterRegistrations ) { if ( name.equals( param.getName() ) ) { return param; } @@ -571,15 +681,36 @@ public abstract class BaseQueryImpl implements Query { @Override @SuppressWarnings("unchecked") public Parameter getParameter(String name, Class type) { - entityManager().checkOpen( false ); - return (Parameter) getParameter( name ); + checkOpen( false ); + Parameter param = getParameter( 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) param; } @Override public Parameter getParameter(int position) { - entityManager().checkOpen( false ); - if ( parameterBindingMap() != null ) { - for ( ParameterImplementor param : parameterBindingMap.keySet() ) { + if ( isJpaPositionalParameter( position ) ) { + return getParameter( Integer.toString( position ) ); + } + checkOpen( false ); + if ( parameterRegistrations != null ) { + for ( ParameterRegistration param : parameterRegistrations ) { + if ( param.getPosition() == null ) { + continue; + } if ( position == param.getPosition() ) { return param; } @@ -591,25 +722,46 @@ public abstract class BaseQueryImpl implements Query { @Override @SuppressWarnings("unchecked") public Parameter getParameter(int position, Class type) { - entityManager().checkOpen( false ); - return (Parameter) getParameter( position ); + checkOpen( false ); + + Parameter param = getParameter( 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) param; } @Override public boolean isBound(Parameter param) { - entityManager().checkOpen( false ); - return parameterBindingMap() != null - && parameterBindingMap.get( (ParameterImplementor) param ) != null; + checkOpen( false ); + final ParameterRegistration registration = findParameterRegistration( param ); + return registration != null && registration.isBindable() && registration.getBind() != null; } @Override @SuppressWarnings("unchecked") public T getParameterValue(Parameter param) { - entityManager().checkOpen( false ); - if ( parameterBindingMap != null ) { - final ParameterValue boundValue = parameterBindingMap.get( (ParameterImplementor) param ); - if ( boundValue != null ) { - return (T) boundValue.getValue(); + checkOpen( false ); + + final ParameterRegistration registration = findParameterRegistration( param ); + if ( registration != null ) { + if ( ! registration.isBindable() ) { + throw new IllegalArgumentException( "Passed parameter [" + param + "] is not bindable" ); + } + final ParameterBind bind = registration.getBind(); + if ( bind != null ) { + return bind.getValue(); } } throw new IllegalStateException( "Parameter [" + param + "] has not yet been bound" ); @@ -617,13 +769,153 @@ public abstract class BaseQueryImpl implements Query { @Override public Object getParameterValue(String name) { - entityManager().checkOpen( false ); + checkOpen( false ); return getParameterValue( getParameter( name ) ); } @Override public Object getParameterValue(int position) { - entityManager().checkOpen( false ); + checkOpen( false ); return getParameterValue( getParameter( position ) ); } + + + + + + + + + + + + + + + + + + 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; + } + + + + } diff --git a/hibernate-entitymanager/src/test/java/org/hibernate/jpa/test/query/QueryTest.java b/hibernate-entitymanager/src/test/java/org/hibernate/jpa/test/query/QueryTest.java index 2ebed17a00..64d29ebfd9 100644 --- a/hibernate-entitymanager/src/test/java/org/hibernate/jpa/test/query/QueryTest.java +++ b/hibernate-entitymanager/src/test/java/org/hibernate/jpa/test/query/QueryTest.java @@ -423,12 +423,11 @@ public class QueryTest extends BaseEntityManagerFunctionalTestCase { em.flush(); + Query query = em.createQuery( "select w from Wallet w where w.brand = ?1 and w.model = ?3" ); + query.setParameter( 1, "Lacoste" ); try { - Query query = em.createQuery( "select w from Wallet w where w.brand = ?1 and w.model = ?3" ); - query.setParameter( 1, "Lacoste" ); query.setParameter( 2, "Expensive" ); - query.getResultList(); - fail("The query should fail due to a user error in parameters"); + fail( "Should fail due to a user error in parameters" ); } catch ( IllegalArgumentException e ) { //success