diff --git a/hibernate-core/src/main/java/org/hibernate/cfg/annotations/SimpleValueBinder.java b/hibernate-core/src/main/java/org/hibernate/cfg/annotations/SimpleValueBinder.java index 1a0eb53a90..3b30fdf341 100644 --- a/hibernate-core/src/main/java/org/hibernate/cfg/annotations/SimpleValueBinder.java +++ b/hibernate-core/src/main/java/org/hibernate/cfg/annotations/SimpleValueBinder.java @@ -78,6 +78,8 @@ import org.jboss.logging.Logger; public class SimpleValueBinder { private static final CoreMessageLogger LOG = Logger.getMessageLogger(CoreMessageLogger.class, SimpleValueBinder.class.getName()); + private Mappings mappings; + private String propertyName; private String returnedClassName; private Ejb3Column[] columns; @@ -85,7 +87,8 @@ public class SimpleValueBinder { private String explicitType = ""; private String defaultType = ""; private Properties typeParameters = new Properties(); - private Mappings mappings; + private boolean isNationalized; + private Table table; private SimpleValue simpleValue; private boolean isVersion; @@ -157,7 +160,7 @@ public class SimpleValueBinder { typeParameters.clear(); String type = BinderHelper.ANNOTATION_STRING_DEFAULT; - final boolean isNationalized = property.isAnnotationPresent( Nationalized.class ) + isNationalized = property.isAnnotationPresent( Nationalized.class ) || mappings.useNationalizedCharacterData(); Type annType = property.getAnnotation( Type.class ); @@ -387,6 +390,9 @@ public class SimpleValueBinder { table = columns[0].getTable(); } simpleValue = new SimpleValue( mappings, table ); + if ( isNationalized ) { + simpleValue.makeNationalized(); + } linkWithValue(); @@ -407,7 +413,7 @@ public class SimpleValueBinder { if ( columns[0].isNameDeferred() && !mappings.isInSecondPass() && referencedEntityName != null ) { mappings.addSecondPass( new PkDrivenByDefaultMapsIdSecondPass( - referencedEntityName, ( Ejb3JoinColumn[] ) columns, simpleValue + referencedEntityName, (Ejb3JoinColumn[]) columns, simpleValue ) ); } diff --git a/hibernate-core/src/main/java/org/hibernate/mapping/SimpleValue.java b/hibernate-core/src/main/java/org/hibernate/mapping/SimpleValue.java index a4f4d52033..4679b93fa6 100644 --- a/hibernate-core/src/main/java/org/hibernate/mapping/SimpleValue.java +++ b/hibernate-core/src/main/java/org/hibernate/mapping/SimpleValue.java @@ -49,6 +49,7 @@ import org.hibernate.type.descriptor.converter.AttributeConverterTypeAdapter; import org.hibernate.type.descriptor.java.JavaTypeDescriptor; import org.hibernate.type.descriptor.java.JavaTypeDescriptorRegistry; 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.SqlTypeDescriptorRegistry; import org.hibernate.usertype.DynamicParameterizedType; @@ -69,13 +70,15 @@ public class SimpleValue implements KeyValue { private final List columns = new ArrayList(); private String typeName; + private Properties typeParameters; + private boolean isNationalized; + private Properties identifierGeneratorProperties; private String identifierGeneratorStrategy = DEFAULT_ID_GEN_STRATEGY; private String nullValue; private Table table; private String foreignKeyName; private boolean alternateUniqueKey; - private Properties typeParameters; private boolean cascadeDeleteEnabled; private AttributeConverterDefinition attributeConverterDefinition; @@ -136,6 +139,15 @@ public class SimpleValue implements KeyValue { public void setTypeName(String type) { this.typeName = type; } + + public void makeNationalized() { + this.isNationalized = true; + } + + public boolean isNationalized() { + return isNationalized; + } + public void setTable(Table table) { this.table = table; } @@ -318,6 +330,7 @@ public class SimpleValue implements KeyValue { if ( typeName == null ) { throw new MappingException( "No type name" ); } + if ( typeParameters != null && Boolean.valueOf( typeParameters.getProperty( DynamicParameterizedType.IS_DYNAMIC ) ) && typeParameters.get( DynamicParameterizedType.PARAMETER_TYPE ) == null ) { @@ -360,6 +373,10 @@ public class SimpleValue implements KeyValue { throw new MappingException( "you must specify types for a dynamic entity: " + propertyName ); } 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; } @@ -419,7 +436,10 @@ public class SimpleValue implements KeyValue { // 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 // 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. final SqlTypeDescriptor sqlTypeDescriptor = SqlTypeDescriptorRegistry.INSTANCE.getDescriptor( jdbcTypeCode ); // find the JavaTypeDescriptor representing the "intermediate database type representation". Back to the diff --git a/hibernate-core/src/main/java/org/hibernate/type/descriptor/sql/NationalizedTypeMappings.java b/hibernate-core/src/main/java/org/hibernate/type/descriptor/sql/NationalizedTypeMappings.java new file mode 100644 index 0000000000..79fe7d2aed --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/type/descriptor/sql/NationalizedTypeMappings.java @@ -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 nationalizedCodeByNonNationalized; + + public NationalizedTypeMappings() { + this.nationalizedCodeByNonNationalized = new BoundedConcurrentHashMap(); + 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; + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/test/type/converter/AndNationalizedTests.java b/hibernate-core/src/test/java/org/hibernate/test/type/converter/AndNationalizedTests.java new file mode 100644 index 0000000000..3e22f71c78 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/type/converter/AndNationalizedTests.java @@ -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 { + @Override + public String convertToDatabaseColumn(Name attribute) { + return attribute.getText(); + } + + @Override + public Name convertToEntityAttribute(String dbData) { + return new Name( dbData ); + } + } +}