HHH-6916 - Not proper array-handling in AbstractQueryImpl.registerParameterBinding

This commit is contained in:
Steve Ebersole 2011-12-25 19:59:09 -06:00
parent e18d087592
commit 097e157329
5 changed files with 350 additions and 110 deletions

View File

@ -1,6 +1,8 @@
import org.apache.tools.ant.filters.ReplaceTokens
apply plugin: 'java'
apply plugin: "jdocbook"
apply plugin: org.hibernate.gradle.testing.matrix.MatrixTestingPlugin
buildscript {
repositories {
@ -14,27 +16,6 @@ buildscript {
}
dependencies {
jdocbookStyles "org.hibernate:hibernate-jdocbook-style:2.0.1"
}
jdocbook {
format('html_single') {
finalName = "index.html"
stylesheet = "classpath:/xslt/org/hibernate/jdocbook/xslt/xhtml-single.xsl"
}
format('html') {
finalName = "index.html"
stylesheet = "classpath:/xslt/org/hibernate/jdocbook/xslt/xhtml.xsl"
}
masterSourceDocumentName="master.xml"
masterLanguage="en"
}
apply plugin: 'java'
apply plugin: org.hibernate.gradle.testing.matrix.MatrixTestingPlugin
dependencies {
compile( project(':hibernate-core') )
compile( libraries.dom4j )
compile( libraries.commons_annotations )
@ -47,16 +28,9 @@ dependencies {
testCompile( libraries.shrinkwrap )
testCompile( libraries.validation )
testRuntime( libraries.validator )
jdocbookStyles "org.hibernate:hibernate-jdocbook-style:2.0.1"
}
aptDumpDir = file( buildDirName + "/tmp/apt" )
sourceSets.test {
originalJavaSrcDirs = java.srcDirs
generatedJpaMetamodelSrcDir = file( "${buildDir}/generated-src/jpamodelgen/${name}" )
java.srcDir generatedJpaMetamodelSrcDir
}
compileMatrixJava.options.define(compilerArgs: ["-proc:none", "-encoding", "UTF-8"])
sourceSets {
matrix {
java {
@ -67,13 +41,21 @@ sourceSets {
}
}
}
ideaModule {
excludeDirs += file("$buildDir/bundles")
testSourceDirs += file( 'src/matrix/java')
testSourceDirs += file( 'src/matrix/resources')
}
task generateJpaMetamodelClasses(type: Compile) {
////////////////////////////////////////////////////////////////////////////////////////////////////////
// JPA model-gen set up
////////////////////////////////////////////////////////////////////////////////////////////////////////
aptDumpDir = file( buildDirName + "/tmp/apt" )
////////////////////////////////////////////////////////////////////////////////////////////////////////
// 'test' sourceSet specific JPA model-gen set up
////////////////////////////////////////////////////////////////////////////////////////////////////////
sourceSets.test {
originalJavaSrcDirs = java.srcDirs
generatedJpaMetamodelSrcDir = file( "${buildDir}/generated-src/jpamodelgen/${name}" )
java.srcDir generatedJpaMetamodelSrcDir
}
task generateTestJpaMetamodelClasses(type: Compile) {
classpath = compileTestJava.classpath + configurations.hibernateJpaModelGenTool
source = sourceSets.test.originalJavaSrcDirs
destinationDir = aptDumpDir
@ -89,13 +71,41 @@ task generateJpaMetamodelClasses(type: Compile) {
sourceSets.test.generatedJpaMetamodelSrcDir.mkdirs()
}
}
// for the time being eat the annoying output from running the annotation processors
generateJpaMetamodelClasses.logging.captureStandardError(LogLevel.INFO)
compileTestJava.dependsOn generateJpaMetamodelClasses
generateTestJpaMetamodelClasses.logging.captureStandardError(LogLevel.INFO)
compileTestJava.dependsOn generateTestJpaMetamodelClasses
compileTestJava.options.define(compilerArgs: ["-proc:none"])
////////////////////////////////////////////////////////////////////////////////////////////////////////
// 'matrix' sourceSet specific JPA model-gen set up
////////////////////////////////////////////////////////////////////////////////////////////////////////
sourceSets.matrix {
originalJavaSrcDirs = java.srcDirs
generatedJpaMetamodelSrcDir = file( "${buildDir}/generated-src/jpamodelgen/${name}" )
java.srcDir generatedJpaMetamodelSrcDir
}
task generateMatrixJpaMetamodelClasses(type: Compile) {
classpath = compileMatrixJava.classpath + configurations.hibernateJpaModelGenTool
source = sourceSets.matrix.java
destinationDir = aptDumpDir
options.define(
compilerArgs: [
"-proc:only",
"-processor", "org.hibernate.jpamodelgen.JPAMetaModelEntityProcessor",
"-s", "$sourceSets.matrix.generatedJpaMetamodelSrcDir.absolutePath"
]
);
outputs.dir sourceSets.matrix.generatedJpaMetamodelSrcDir;
doFirst {
sourceSets.matrix.generatedJpaMetamodelSrcDir.mkdirs()
}
}
// for the time being eat the annoying output from running the annotation processors
generateMatrixJpaMetamodelClasses.logging.captureStandardError(LogLevel.INFO)
compileMatrixJava.dependsOn generateMatrixJpaMetamodelClasses
compileMatrixJava.options.define(compilerArgs: ["-proc:none", "-encoding", "UTF-8"])
bundlesTargetDir = file( "$buildDirName/bundles" )
task copyBundleResources (type: Copy) {
@ -116,3 +126,22 @@ task copyBundleResources (type: Copy) {
// make sure that the bundles for the packaged test (PackagingTestCase) are copied as well
processTestResources.dependsOn copyBundleResources
jdocbook {
format('html_single') {
finalName = "index.html"
stylesheet = "classpath:/xslt/org/hibernate/jdocbook/xslt/xhtml-single.xsl"
}
format('html') {
finalName = "index.html"
stylesheet = "classpath:/xslt/org/hibernate/jdocbook/xslt/xhtml.xsl"
}
masterSourceDocumentName="master.xml"
masterLanguage="en"
}
ideaModule {
excludeDirs += file("$buildDir/bundles")
testSourceDirs += file( 'src/matrix/java')
testSourceDirs += file( 'src/matrix/resources')
}

View File

@ -22,25 +22,20 @@
* Boston, MA 02110-1301 USA
*/
package org.hibernate.ejb;
import static org.hibernate.ejb.QueryHints.HINT_CACHEABLE;
import static org.hibernate.ejb.QueryHints.HINT_CACHE_MODE;
import static org.hibernate.ejb.QueryHints.HINT_CACHE_REGION;
import static org.hibernate.ejb.QueryHints.HINT_COMMENT;
import static org.hibernate.ejb.QueryHints.HINT_FETCH_SIZE;
import static org.hibernate.ejb.QueryHints.HINT_FLUSH_MODE;
import static org.hibernate.ejb.QueryHints.HINT_READONLY;
import static org.hibernate.ejb.QueryHints.HINT_TIMEOUT;
import static org.hibernate.ejb.QueryHints.SPEC_HINT_TIMEOUT;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import javax.persistence.CacheRetrieveMode;
import javax.persistence.CacheStoreMode;
import javax.persistence.FlushModeType;
import javax.persistence.Parameter;
import javax.persistence.TransactionRequiredException;
import javax.persistence.TypedQuery;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import org.jboss.logging.Logger;
import org.hibernate.CacheMode;
import org.hibernate.FlushMode;
import org.hibernate.HibernateException;
@ -51,7 +46,16 @@ import org.hibernate.ejb.util.CacheModeHelper;
import org.hibernate.ejb.util.ConfigurationHelper;
import org.hibernate.ejb.util.LockModeTypeHelper;
import org.hibernate.hql.internal.QueryExecutionRequestException;
import org.jboss.logging.Logger;
import static org.hibernate.ejb.QueryHints.HINT_CACHEABLE;
import static org.hibernate.ejb.QueryHints.HINT_CACHE_MODE;
import static org.hibernate.ejb.QueryHints.HINT_CACHE_REGION;
import static org.hibernate.ejb.QueryHints.HINT_COMMENT;
import static org.hibernate.ejb.QueryHints.HINT_FETCH_SIZE;
import static org.hibernate.ejb.QueryHints.HINT_FLUSH_MODE;
import static org.hibernate.ejb.QueryHints.HINT_READONLY;
import static org.hibernate.ejb.QueryHints.HINT_TIMEOUT;
import static org.hibernate.ejb.QueryHints.SPEC_HINT_TIMEOUT;
/**
* Intended as a base class providing convenience in implementing both {@link javax.persistence.Query} and
@ -84,9 +88,7 @@ public abstract class AbstractQueryImpl<X> implements TypedQuery<X> {
*/
protected abstract int internalExecuteUpdate();
/**
* {@inheritDoc}
*/
@Override
@SuppressWarnings({ "ThrowableInstanceNeverThrown" })
public int executeUpdate() {
try {
@ -117,9 +119,7 @@ public abstract class AbstractQueryImpl<X> implements TypedQuery<X> {
*/
protected abstract void applyMaxResults(int maxResults);
/**
* {@inheritDoc}
*/
@Override
public TypedQuery<X> setMaxResults(int maxResult) {
if ( maxResult < 0 ) {
throw new IllegalArgumentException(
@ -135,6 +135,7 @@ public abstract class AbstractQueryImpl<X> implements TypedQuery<X> {
return maxResults;
}
@Override
public int getMaxResults() {
return maxResults == -1
? Integer.MAX_VALUE // stupid spec... MAX_VALUE??
@ -150,6 +151,7 @@ public abstract class AbstractQueryImpl<X> implements TypedQuery<X> {
*/
protected abstract void applyFirstResult(int firstResult);
@Override
public TypedQuery<X> setFirstResult(int firstResult) {
if ( firstResult < 0 ) {
throw new IllegalArgumentException(
@ -161,15 +163,14 @@ public abstract class AbstractQueryImpl<X> implements TypedQuery<X> {
return this;
}
@Override
public int getFirstResult() {
return firstResult;
}
private Map<String, Object> hints;
/**
* {@inheritDoc}
*/
@Override
public Map<String, Object> getHints() {
return hints;
}
@ -194,9 +195,8 @@ public abstract class AbstractQueryImpl<X> implements TypedQuery<X> {
protected abstract void applyAliasSpecificLockMode(String alias, LockMode lockMode);
/**
* {@inheritDoc}
*/
@Override
@SuppressWarnings( {"deprecation"})
public TypedQuery<X> setHint(String hintName, Object value) {
boolean skipped = false;
try {
@ -294,16 +294,20 @@ public abstract class AbstractQueryImpl<X> implements TypedQuery<X> {
return this;
}
@SuppressWarnings( {"UnusedDeclaration"})
public Set<String> getSupportedHints() {
return QueryHints.getDefinedHints();
}
@Override
public abstract TypedQuery<X> setLockMode(javax.persistence.LockModeType lockModeType);
@Override
public abstract javax.persistence.LockModeType getLockMode();
private FlushModeType jpaFlushMode;
@Override
public TypedQuery<X> setFlushMode(FlushModeType jpaFlushMode) {
this.jpaFlushMode = jpaFlushMode;
// TODO : treat as hint?
@ -316,10 +320,12 @@ public abstract class AbstractQueryImpl<X> implements TypedQuery<X> {
return this;
}
@SuppressWarnings( {"UnusedDeclaration"})
protected FlushModeType getSpecifiedFlushMode() {
return jpaFlushMode;
}
@Override
public FlushModeType getFlushMode() {
return jpaFlushMode != null
? jpaFlushMode
@ -328,57 +334,112 @@ public abstract class AbstractQueryImpl<X> implements TypedQuery<X> {
private Map parameterBindings;
@SuppressWarnings( {"unchecked"})
protected void registerParameterBinding(Parameter parameter, Object value) {
if ( value != null && parameter.getParameterType() != null ) {
if ( Collection.class.isInstance( value ) ) {
final Collection collection = (Collection) value;
// validate the elements...
for ( Object element : collection ) {
if ( ! parameter.getParameterType().isInstance( element ) ) {
throw new IllegalArgumentException(
"Parameter value [" + element + "] was not matching type [" +
parameter.getParameterType().getName() + "]"
);
}
}
}
else if ( value.getClass().isArray() && value.getClass().equals( Object[].class ) ) {
final Object[] array = (Object[]) value;
for ( Object element : array ) {
if ( ! parameter.getParameterType().isInstance( element ) ) {
throw new IllegalArgumentException(
"Parameter value [" + element + "] was not matching type [" +
parameter.getParameterType().getName() + "]"
);
}
}
}
else {
if ( ! parameter.getParameterType().isInstance( value ) ) {
throw new IllegalArgumentException(
"Parameter value [" + value + "] was not matching type [" +
parameter.getParameterType().getName() + "]"
);
}
}
if ( parameter == null ) {
throw new IllegalArgumentException( "parameter cannot be null" );
}
validateParameterBinding( parameter, value );
if ( parameterBindings == null ) {
parameterBindings = new HashMap();
}
parameterBindings.put( parameter, value );
}
/**
* {@inheritDoc}
*/
private void validateParameterBinding(Parameter parameter, Object value) {
if ( value == null || parameter.getParameterType() == null ) {
// nothing we can check
return;
}
if ( Collection.class.isInstance( value )
&& ! Collection.class.isAssignableFrom( parameter.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.
validateCollectionValuedParameterMultiBinding( parameter, (Collection) value );
}
else if ( value.getClass().isArray() ) {
validateArrayValuedParameterBinding( parameter, value );
}
else {
if ( ! parameter.getParameterType().isInstance( value ) ) {
throw new IllegalArgumentException(
String.format(
"Parameter value [%s] did not match expected type [%s]",
value,
parameter.getParameterType().getName()
)
);
}
}
}
private void validateCollectionValuedParameterMultiBinding(Parameter parameter, Collection value) {
// validate the elements...
for ( Object element : value ) {
if ( ! parameter.getParameterType().isInstance( element ) ) {
throw new IllegalArgumentException(
String.format(
"Parameter value element [%s] did not match expected type [%s]",
element,
parameter.getParameterType().getName()
)
);
}
}
}
private void validateArrayValuedParameterBinding(Parameter 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) {
return parameterBindings != null && parameterBindings.containsKey( param );
}
/**
* {@inheritDoc}
*/
@Override
@SuppressWarnings({ "unchecked" })
public <T> T getParameterValue(Parameter<T> param) {
if ( parameterBindings == null ) {
@ -396,16 +457,12 @@ public abstract class AbstractQueryImpl<X> implements TypedQuery<X> {
}
}
/**
* {@inheritDoc}
*/
@Override
public Object getParameterValue(String name) {
return getParameterValue( getParameter( name ) );
}
/**
* {@inheritDoc}
*/
@Override
public Object getParameterValue(int position) {
return getParameterValue( getParameter( position ) );
}

View File

@ -0,0 +1,70 @@
/*
* Hibernate, Relational Persistence for Idiomatic Java
*
* Copyright (c) 2011, 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.ejb.criteria;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import org.hibernate.annotations.GenericGenerator;
/**
* An entity with multiple attributes of basic type for use in testing using those types/attributes
* in queries.
*
* @author Steve Ebersole
*/
@Entity
public class MultiTypedBasicAttributesEntity {
@Id
@GeneratedValue( generator = "increment" )
@GenericGenerator( name = "increment", strategy = "increment" )
private Long id;
private byte[] someBytes;
private Byte[] someWrappedBytes;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public byte[] getSomeBytes() {
return someBytes;
}
public void setSomeBytes(byte[] someBytes) {
this.someBytes = someBytes;
}
public Byte[] getSomeWrappedBytes() {
return someWrappedBytes;
}
public void setSomeWrappedBytes(Byte[] someWrappedBytes) {
this.someWrappedBytes = someWrappedBytes;
}
}

View File

@ -0,0 +1,81 @@
/*
* Hibernate, Relational Persistence for Idiomatic Java
*
* Copyright (c) 2011, 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.ejb.criteria;
import javax.persistence.EntityManager;
import javax.persistence.Parameter;
import javax.persistence.TypedQuery;
import javax.persistence.criteria.CriteriaQuery;
import javax.persistence.criteria.ParameterExpression;
import javax.persistence.criteria.Path;
import javax.persistence.criteria.Root;
import org.hibernate.ejb.test.BaseEntityManagerFunctionalTestCase;
import org.junit.Test;
/**
* @author Steve Ebersole
*/
public class ParameterTest extends BaseEntityManagerFunctionalTestCase {
@Test
public void testPrimitiveArrayParameterBinding() {
EntityManager em = getOrCreateEntityManager();
em.getTransaction().begin();
CriteriaQuery<MultiTypedBasicAttributesEntity> criteria = em.getCriteriaBuilder()
.createQuery( MultiTypedBasicAttributesEntity.class );
Root<MultiTypedBasicAttributesEntity> rootEntity = criteria.from( MultiTypedBasicAttributesEntity.class );
Path<byte[]> someBytesPath = rootEntity.get( MultiTypedBasicAttributesEntity_.someBytes );
ParameterExpression<byte[]> param = em.getCriteriaBuilder().parameter( byte[].class, "theBytes" );
criteria.where( em.getCriteriaBuilder().equal( someBytesPath, param ) );
TypedQuery<MultiTypedBasicAttributesEntity> query = em.createQuery( criteria );
query.setParameter( param, new byte[] { 1,1,1 } );
query.getResultList();
em.getTransaction().commit();
em.close();
}
@Test
public void testNonPrimitiveArrayParameterBinding() {
EntityManager em = getOrCreateEntityManager();
em.getTransaction().begin();
CriteriaQuery<MultiTypedBasicAttributesEntity> criteria = em.getCriteriaBuilder()
.createQuery( MultiTypedBasicAttributesEntity.class );
Root<MultiTypedBasicAttributesEntity> rootEntity = criteria.from( MultiTypedBasicAttributesEntity.class );
Path<Byte[]> thePath = rootEntity.get( MultiTypedBasicAttributesEntity_.someWrappedBytes );
ParameterExpression<Byte[]> param = em.getCriteriaBuilder().parameter( Byte[].class, "theBytes" );
criteria.where( em.getCriteriaBuilder().equal( thePath, param ) );
TypedQuery<MultiTypedBasicAttributesEntity> query = em.createQuery( criteria );
query.setParameter( param, new Byte[] { Byte.valueOf((byte)1), Byte.valueOf((byte)1), Byte.valueOf((byte)1) } );
query.getResultList();
em.getTransaction().commit();
em.close();
}
@Override
public Class[] getAnnotatedClasses() {
return new Class[] { MultiTypedBasicAttributesEntity.class };
}
}

View File

@ -32,6 +32,8 @@ import java.util.Date;
import java.util.List;
import org.hibernate.Hibernate;
import org.hibernate.criterion.CriteriaQuery;
import org.hibernate.ejb.criteria.MultiTypedBasicAttributesEntity;
import org.hibernate.ejb.test.BaseEntityManagerFunctionalTestCase;
import org.hibernate.ejb.test.Distributor;
import org.hibernate.ejb.test.Item;
@ -571,7 +573,8 @@ public class QueryTest extends BaseEntityManagerFunctionalTestCase {
return new Class[]{
Item.class,
Distributor.class,
Wallet.class
Wallet.class,
MultiTypedBasicAttributesEntity.class
};
}
}