HHH-9599 - AnnotationException occurs when applying @Nationalized and @Convert annotations to the same field

This commit is contained in:
Steve Ebersole 2015-03-20 16:55:29 -05:00
parent 8a7bc2e7c1
commit 38c004431d
5 changed files with 215 additions and 6 deletions

View File

@ -614,6 +614,9 @@ public class ModelBinder {
primaryTable, primaryTable,
entityDescriptor.getIdentifier() entityDescriptor.getIdentifier()
); );
if ( mappingDocument.getBuildingOptions().useNationalizedCharacterData() ) {
keyBinding.makeNationalized();
}
entityDescriptor.setKey( keyBinding ); entityDescriptor.setKey( keyBinding );
keyBinding.setCascadeDeleteEnabled( entitySource.isCascadeDeleteEnabled() ); keyBinding.setCascadeDeleteEnabled( entitySource.isCascadeDeleteEnabled() );
relationalObjectBinder.bindColumns( relationalObjectBinder.bindColumns(
@ -1818,6 +1821,9 @@ public class ModelBinder {
secondaryTable, secondaryTable,
persistentClass.getIdentifier() persistentClass.getIdentifier()
); );
if ( mappingDocument.getBuildingOptions().useNationalizedCharacterData() ) {
keyBinding.makeNationalized();
}
secondaryTableJoin.setKey( keyBinding ); secondaryTableJoin.setKey( keyBinding );
keyBinding.setCascadeDeleteEnabled( secondaryTableSource.isCascadeDeleteEnabled() ); keyBinding.setCascadeDeleteEnabled( secondaryTableSource.isCascadeDeleteEnabled() );
@ -2799,10 +2805,14 @@ public class ModelBinder {
} }
private static void bindSimpleValueType( private static void bindSimpleValueType(
MappingDocument sourceDocument, MappingDocument mappingDocument,
HibernateTypeSource typeSource, HibernateTypeSource typeSource,
SimpleValue simpleValue) { SimpleValue simpleValue) {
final TypeResolution typeResolution = resolveType( sourceDocument, typeSource ); if ( mappingDocument.getBuildingOptions().useNationalizedCharacterData() ) {
simpleValue.makeNationalized();
}
final TypeResolution typeResolution = resolveType( mappingDocument, typeSource );
if ( typeResolution == null ) { if ( typeResolution == null ) {
// no explicit type info was found // no explicit type info was found
return; return;

View File

@ -88,6 +88,8 @@ public class SimpleValueBinder {
private String explicitType = ""; private String explicitType = "";
private String defaultType = ""; private String defaultType = "";
private Properties typeParameters = new Properties(); private Properties typeParameters = new Properties();
private boolean isNationalized;
private Table table; private Table table;
private SimpleValue simpleValue; private SimpleValue simpleValue;
private boolean isVersion; private boolean isVersion;
@ -159,7 +161,7 @@ public class SimpleValueBinder {
typeParameters.clear(); typeParameters.clear();
String type = BinderHelper.ANNOTATION_STRING_DEFAULT; String type = BinderHelper.ANNOTATION_STRING_DEFAULT;
final boolean isNationalized = property.isAnnotationPresent( Nationalized.class ) isNationalized = property.isAnnotationPresent( Nationalized.class )
|| buildingContext.getBuildingOptions().useNationalizedCharacterData(); || buildingContext.getBuildingOptions().useNationalizedCharacterData();
Type annType = property.getAnnotation( Type.class ); Type annType = property.getAnnotation( Type.class );
@ -389,6 +391,9 @@ public class SimpleValueBinder {
table = columns[0].getTable(); table = columns[0].getTable();
} }
simpleValue = new SimpleValue( buildingContext.getMetadataCollector(), table ); simpleValue = new SimpleValue( buildingContext.getMetadataCollector(), table );
if ( isNationalized ) {
simpleValue.makeNationalized();
}
linkWithValue(); linkWithValue();

View File

@ -31,7 +31,6 @@ import java.util.Properties;
import javax.persistence.AttributeConverter; import javax.persistence.AttributeConverter;
import org.hibernate.FetchMode; import org.hibernate.FetchMode;
import org.hibernate.HibernateException;
import org.hibernate.MappingException; import org.hibernate.MappingException;
import org.hibernate.annotations.common.reflection.XProperty; import org.hibernate.annotations.common.reflection.XProperty;
import org.hibernate.boot.registry.classloading.spi.ClassLoaderService; import org.hibernate.boot.registry.classloading.spi.ClassLoaderService;
@ -55,6 +54,7 @@ import org.hibernate.type.descriptor.converter.AttributeConverterTypeAdapter;
import org.hibernate.type.descriptor.java.JavaTypeDescriptor; import org.hibernate.type.descriptor.java.JavaTypeDescriptor;
import org.hibernate.type.descriptor.java.JavaTypeDescriptorRegistry; import org.hibernate.type.descriptor.java.JavaTypeDescriptorRegistry;
import org.hibernate.type.descriptor.sql.JdbcTypeJavaClassMappings; import org.hibernate.type.descriptor.sql.JdbcTypeJavaClassMappings;
import org.hibernate.type.descriptor.sql.NationalizedTypeMappings;
import org.hibernate.type.descriptor.sql.SqlTypeDescriptor; import org.hibernate.type.descriptor.sql.SqlTypeDescriptor;
import org.hibernate.type.descriptor.sql.SqlTypeDescriptorRegistry; import org.hibernate.type.descriptor.sql.SqlTypeDescriptorRegistry;
import org.hibernate.usertype.DynamicParameterizedType; import org.hibernate.usertype.DynamicParameterizedType;
@ -73,13 +73,15 @@ public class SimpleValue implements KeyValue {
private final List<Selectable> columns = new ArrayList<Selectable>(); private final List<Selectable> columns = new ArrayList<Selectable>();
private String typeName; private String typeName;
private Properties typeParameters;
private boolean isNationalized;
private Properties identifierGeneratorProperties; private Properties identifierGeneratorProperties;
private String identifierGeneratorStrategy = DEFAULT_ID_GEN_STRATEGY; private String identifierGeneratorStrategy = DEFAULT_ID_GEN_STRATEGY;
private String nullValue; private String nullValue;
private Table table; private Table table;
private String foreignKeyName; private String foreignKeyName;
private boolean alternateUniqueKey; private boolean alternateUniqueKey;
private Properties typeParameters;
private boolean cascadeDeleteEnabled; private boolean cascadeDeleteEnabled;
private AttributeConverterDefinition attributeConverterDefinition; private AttributeConverterDefinition attributeConverterDefinition;
@ -164,6 +166,14 @@ public class SimpleValue implements KeyValue {
this.typeName = typeName; this.typeName = typeName;
} }
public void makeNationalized() {
this.isNationalized = true;
}
public boolean isNationalized() {
return isNationalized;
}
public void setTable(Table table) { public void setTable(Table table) {
this.table = table; this.table = table;
} }
@ -357,6 +367,7 @@ public class SimpleValue implements KeyValue {
if ( typeName == null ) { if ( typeName == null ) {
throw new MappingException( "No type name" ); throw new MappingException( "No type name" );
} }
if ( typeParameters != null if ( typeParameters != null
&& Boolean.valueOf( typeParameters.getProperty( DynamicParameterizedType.IS_DYNAMIC ) ) && Boolean.valueOf( typeParameters.getProperty( DynamicParameterizedType.IS_DYNAMIC ) )
&& typeParameters.get( DynamicParameterizedType.PARAMETER_TYPE ) == null ) { && typeParameters.get( DynamicParameterizedType.PARAMETER_TYPE ) == null ) {
@ -399,6 +410,10 @@ public class SimpleValue implements KeyValue {
throw new MappingException( "Attribute types for a dynamic entity must be explicitly specified: " + propertyName ); throw new MappingException( "Attribute types for a dynamic entity must be explicitly specified: " + propertyName );
} }
typeName = ReflectHelper.reflectedPropertyClass( className, propertyName ).getName(); typeName = ReflectHelper.reflectedPropertyClass( className, propertyName ).getName();
// todo : to fully support isNationalized here we need do the process hinted at above
// essentially, much of the logic from #buildAttributeConverterTypeAdapter wrt resolving
// a (1) SqlTypeDescriptor, a (2) JavaTypeDescriptor and dynamically building a BasicType
// combining them.
return; return;
} }
@ -458,7 +473,10 @@ public class SimpleValue implements KeyValue {
// corresponding to the AttributeConverter's declared "databaseColumnJavaType" (how we read that value out // corresponding to the AttributeConverter's declared "databaseColumnJavaType" (how we read that value out
// of ResultSets). See JdbcTypeJavaClassMappings for details. Again, given example, this should return // of ResultSets). See JdbcTypeJavaClassMappings for details. Again, given example, this should return
// VARCHAR/CHAR // VARCHAR/CHAR
final int jdbcTypeCode = JdbcTypeJavaClassMappings.INSTANCE.determineJdbcTypeCodeForJavaClass( databaseColumnJavaType ); int jdbcTypeCode = JdbcTypeJavaClassMappings.INSTANCE.determineJdbcTypeCodeForJavaClass( databaseColumnJavaType );
if ( isNationalized() ) {
jdbcTypeCode = NationalizedTypeMappings.INSTANCE.getCorrespondingNationalizedCode( jdbcTypeCode );
}
// find the standard SqlTypeDescriptor for that JDBC type code. // find the standard SqlTypeDescriptor for that JDBC type code.
final SqlTypeDescriptor sqlTypeDescriptor = SqlTypeDescriptorRegistry.INSTANCE.getDescriptor( jdbcTypeCode ); final SqlTypeDescriptor sqlTypeDescriptor = SqlTypeDescriptorRegistry.INSTANCE.getDescriptor( jdbcTypeCode );
// find the JavaTypeDescriptor representing the "intermediate database type representation". Back to the // find the JavaTypeDescriptor representing the "intermediate database type representation". Back to the

View File

@ -0,0 +1,71 @@
/*
* Hibernate, Relational Persistence for Idiomatic Java
*
* Copyright (c) 2015, 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.type.descriptor.sql;
import java.sql.Types;
import java.util.Map;
import org.hibernate.internal.util.collections.BoundedConcurrentHashMap;
import org.jboss.logging.Logger;
/**
* Manages a mapping between nationalized and non-nationalized variants of JDBC types.
*
* At the moment we only care about being able to map non-nationalized codes to the
* corresponding nationalized equivalent, so that's all we implement for now
*
* @author Steve Ebersole
*/
public class NationalizedTypeMappings {
private static final Logger log = Logger.getLogger( NationalizedTypeMappings.class );
/**
* Singleton access
*/
public static final NationalizedTypeMappings INSTANCE = new NationalizedTypeMappings();
private final Map<Integer,Integer> nationalizedCodeByNonNationalized;
public NationalizedTypeMappings() {
this.nationalizedCodeByNonNationalized = new BoundedConcurrentHashMap<Integer, Integer>();
map( Types.CHAR, Types.NCHAR );
map( Types.CLOB, Types.NCLOB );
map( Types.LONGVARCHAR, Types.LONGNVARCHAR );
map( Types.VARCHAR, Types.NVARCHAR );
}
private void map(int nonNationalizedCode, int nationalizedCode) {
nationalizedCodeByNonNationalized.put( nonNationalizedCode, nationalizedCode );
}
public int getCorrespondingNationalizedCode(int jdbcCode) {
Integer nationalizedCode = nationalizedCodeByNonNationalized.get( jdbcCode );
if ( nationalizedCode == null ) {
log.debug( "Unable to locate nationalized jdbc-code equivalent for given jdbc code : " + jdbcCode );
return jdbcCode;
}
return nationalizedCode;
}
}

View File

@ -0,0 +1,105 @@
/*
* Hibernate, Relational Persistence for Idiomatic Java
*
* Copyright (c) 2015, 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.test.type.converter;
import java.sql.Types;
import javax.persistence.AttributeConverter;
import javax.persistence.Convert;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.Table;
import org.hibernate.annotations.Nationalized;
import org.hibernate.boot.Metadata;
import org.hibernate.boot.MetadataSources;
import org.hibernate.boot.internal.MetadataImpl;
import org.hibernate.boot.registry.StandardServiceRegistry;
import org.hibernate.boot.registry.StandardServiceRegistryBuilder;
import org.hibernate.mapping.PersistentClass;
import org.hibernate.testing.TestForIssue;
import org.hibernate.testing.junit4.BaseUnitTestCase;
import org.junit.Test;
import static org.junit.Assert.assertEquals;
/**
* Test the combination of @Nationalized and @Convert
*
* @author Steve Ebersole
*/
public class AndNationalizedTests extends BaseUnitTestCase {
@Test
@TestForIssue( jiraKey = "HHH-9599")
public void basicTest() {
StandardServiceRegistry ssr = new StandardServiceRegistryBuilder().build();
try {
Metadata metadata = new MetadataSources( ssr ).addAnnotatedClass( TestEntity.class ).buildMetadata();
( (MetadataImpl) metadata ).validate();
final PersistentClass entityBinding = metadata.getEntityBinding( TestEntity.class.getName() );
assertEquals(
Types.NVARCHAR,
entityBinding.getProperty( "name" ).getType().sqlTypes( metadata )[0]
);
}
finally {
StandardServiceRegistryBuilder.destroy( ssr );
}
}
@Entity(name = "TestEntity")
@Table(name = "TestEntity")
public static class TestEntity {
@Id
public Integer id;
@Nationalized
@Convert(converter = NameConverter.class)
public Name name;
}
public static class Name {
private final String text;
public Name(String text) {
this.text = text;
}
public String getText() {
return text;
}
}
public static class NameConverter implements AttributeConverter<Name,String> {
@Override
public String convertToDatabaseColumn(Name attribute) {
return attribute.getText();
}
@Override
public Name convertToEntityAttribute(String dbData) {
return new Name( dbData );
}
}
}