From ad2a9ef65159672f11091d8b6ea82752f4c2cc1c Mon Sep 17 00:00:00 2001 From: Steve Ebersole Date: Thu, 5 Jul 2012 17:48:02 -0500 Subject: [PATCH] HHH-7387 - Integrate Draft 6 of the JPA 2.1 spec : AttributeConverter --- .../org/hibernate/cfg/AnnotationBinder.java | 1 + .../cfg/AttributeConverterDefinition.java | 124 +++++++++ .../java/org/hibernate/cfg/Configuration.java | 66 +++++ .../main/java/org/hibernate/cfg/Mappings.java | 18 ++ .../cfg/annotations/SimpleValueBinder.java | 243 +++++++++++++++++- .../org/hibernate/mapping/SimpleValue.java | 177 ++++++++++++- ...AbstractSingleColumnStandardBasicType.java | 5 +- .../type/descriptor/JdbcTypeNameMapper.java | 48 +++- .../java/AbstractTypeDescriptor.java | 26 +- .../java/ByteArrayTypeDescriptor.java | 2 +- .../java/CharacterArrayTypeDescriptor.java | 2 +- .../descriptor/java/ClassTypeDescriptor.java | 2 +- .../java/CurrencyTypeDescriptor.java | 5 +- .../descriptor/java/DateTypeDescriptor.java | 2 +- .../java/ImmutableMutabilityPlan.java | 17 +- .../java/IncomparableComparator.java | 5 +- .../java/JavaTypeDescriptorRegistry.java | 112 ++++++++ .../java/JdbcDateTypeDescriptor.java | 2 +- .../java/JdbcTimeTypeDescriptor.java | 2 +- .../java/JdbcTimestampTypeDescriptor.java | 5 +- .../descriptor/java/LocaleTypeDescriptor.java | 2 +- .../type/descriptor/java/MutabilityPlan.java | 4 +- .../java/MutableMutabilityPlan.java | 15 +- ...PrimitiveCharacterArrayTypeDescriptor.java | 2 +- .../java/SerializableTypeDescriptor.java | 2 +- .../descriptor/sql/BigIntTypeDescriptor.java | 7 + .../descriptor/sql/BinaryTypeDescriptor.java | 4 + .../descriptor/sql/BitTypeDescriptor.java | 6 + .../descriptor/sql/BlobTypeDescriptor.java | 68 ++--- .../descriptor/sql/BooleanTypeDescriptor.java | 4 + .../descriptor/sql/CharTypeDescriptor.java | 4 + .../descriptor/sql/ClobTypeDescriptor.java | 64 +++-- .../descriptor/sql/DateTypeDescriptor.java | 7 + .../descriptor/sql/DecimalTypeDescriptor.java | 7 + .../descriptor/sql/DoubleTypeDescriptor.java | 7 + .../descriptor/sql/FloatTypeDescriptor.java | 6 + .../descriptor/sql/IntegerTypeDescriptor.java | 7 + .../sql/JdbcTypeFamilyInformation.java | 75 ++++++ .../sql/JdbcTypeJavaClassMappings.java | 134 ++++++++++ .../sql/LongNVarcharTypeDescriptor.java | 44 ++++ .../sql/LongVarbinaryTypeDescriptor.java | 5 + .../sql/LongVarcharTypeDescriptor.java | 5 + .../descriptor/sql/NCharTypeDescriptor.java | 44 ++++ .../descriptor/sql/NClobTypeDescriptor.java | 125 +++++++++ .../sql/NVarcharTypeDescriptor.java | 83 ++++++ .../descriptor/sql/NumericTypeDescriptor.java | 5 + .../descriptor/sql/RealTypeDescriptor.java | 7 + .../sql/SmallIntTypeDescriptor.java | 7 + .../descriptor/sql/SqlTypeDescriptor.java | 19 ++ .../sql/SqlTypeDescriptorRegistry.java | 152 +++++++++++ .../descriptor/sql/TimeTypeDescriptor.java | 7 + .../sql/TimestampTypeDescriptor.java | 7 + .../descriptor/sql/TinyIntTypeDescriptor.java | 7 + .../sql/VarbinaryTypeDescriptor.java | 4 + .../descriptor/sql/VarcharTypeDescriptor.java | 7 + .../type/AttributeConverterTest.java | 143 +++++++++++ .../testing/junit4/ExtraAssertions.java | 14 + 57 files changed, 1827 insertions(+), 147 deletions(-) create mode 100644 hibernate-core/src/main/java/org/hibernate/cfg/AttributeConverterDefinition.java create mode 100644 hibernate-core/src/main/java/org/hibernate/type/descriptor/java/JavaTypeDescriptorRegistry.java create mode 100644 hibernate-core/src/main/java/org/hibernate/type/descriptor/sql/JdbcTypeFamilyInformation.java create mode 100644 hibernate-core/src/main/java/org/hibernate/type/descriptor/sql/JdbcTypeJavaClassMappings.java create mode 100644 hibernate-core/src/main/java/org/hibernate/type/descriptor/sql/LongNVarcharTypeDescriptor.java create mode 100644 hibernate-core/src/main/java/org/hibernate/type/descriptor/sql/NCharTypeDescriptor.java create mode 100644 hibernate-core/src/main/java/org/hibernate/type/descriptor/sql/NClobTypeDescriptor.java create mode 100644 hibernate-core/src/main/java/org/hibernate/type/descriptor/sql/NVarcharTypeDescriptor.java create mode 100644 hibernate-core/src/main/java/org/hibernate/type/descriptor/sql/SqlTypeDescriptorRegistry.java create mode 100644 hibernate-core/src/test/java/org/hibernate/type/AttributeConverterTest.java diff --git a/hibernate-core/src/main/java/org/hibernate/cfg/AnnotationBinder.java b/hibernate-core/src/main/java/org/hibernate/cfg/AnnotationBinder.java index 1b792fc609..cc6802ff51 100644 --- a/hibernate-core/src/main/java/org/hibernate/cfg/AnnotationBinder.java +++ b/hibernate-core/src/main/java/org/hibernate/cfg/AnnotationBinder.java @@ -279,6 +279,7 @@ public final class AnnotationBinder { bindTypeDefs( pckg, mappings ); bindFetchProfiles( pckg, mappings ); BinderHelper.bindAnyMetaDefs( pckg, mappings ); + } private static void bindGenericGenerators(XAnnotatedElement annotatedElement, Mappings mappings) { diff --git a/hibernate-core/src/main/java/org/hibernate/cfg/AttributeConverterDefinition.java b/hibernate-core/src/main/java/org/hibernate/cfg/AttributeConverterDefinition.java new file mode 100644 index 0000000000..371f43a546 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/cfg/AttributeConverterDefinition.java @@ -0,0 +1,124 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * Copyright (c) 2012, Red Hat Inc. or third-party contributors as + * indicated by the @author tags or express copyright attribution + * statements applied by the authors. All third-party contributions are + * distributed under license by Red Hat Inc. + * + * This copyrighted material is made available to anyone wishing to use, modify, + * copy, or redistribute it subject to the terms and conditions of the GNU + * Lesser General Public License, as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this distribution; if not, write to: + * Free Software Foundation, Inc. + * 51 Franklin Street, Fifth Floor + * Boston, MA 02110-1301 USA + */ +package org.hibernate.cfg; + +import javax.persistence.AttributeConverter; +import java.lang.reflect.ParameterizedType; +import java.lang.reflect.Type; +import java.lang.reflect.TypeVariable; + +import org.jboss.logging.Logger; + +import org.hibernate.AnnotationException; +import org.hibernate.AssertionFailure; + +/** + * @author Steve Ebersole + */ +public class AttributeConverterDefinition { + private static final Logger log = Logger.getLogger( AttributeConverterDefinition.class ); + + private final AttributeConverter attributeConverter; + private final boolean autoApply; + private final Class entityAttributeType; + private final Class databaseColumnType; + + public AttributeConverterDefinition(AttributeConverter attributeConverter, boolean autoApply) { + this.attributeConverter = attributeConverter; + this.autoApply = autoApply; + + final Class attributeConverterClass = attributeConverter.getClass(); + final ParameterizedType attributeConverterSignature = extractAttributeConverterParameterizedType( attributeConverterClass ); + + if ( attributeConverterSignature.getActualTypeArguments().length < 2 ) { + throw new AnnotationException( + "AttributeConverter [" + attributeConverterClass.getName() + + "] did not retain parameterized type information" + ); + } + + if ( attributeConverterSignature.getActualTypeArguments().length > 2 ) { + throw new AnnotationException( + "AttributeConverter [" + attributeConverterClass.getName() + + "] specified more than 2 parameterized types" + ); + } + entityAttributeType = (Class) attributeConverterSignature.getActualTypeArguments()[0]; + if ( entityAttributeType == null ) { + throw new AnnotationException( + "Could not determine 'entity attribute' type from given AttributeConverter [" + + attributeConverterClass.getName() + "]" + ); + } + + databaseColumnType = (Class) attributeConverterSignature.getActualTypeArguments()[1]; + if ( databaseColumnType == null ) { + throw new AnnotationException( + "Could not determine 'database column' type from given AttributeConverter [" + + attributeConverterClass.getName() + "]" + ); + } + } + + private ParameterizedType extractAttributeConverterParameterizedType(Class attributeConverterClass) { + for ( Type type : attributeConverterClass.getGenericInterfaces() ) { + if ( ParameterizedType.class.isInstance( type ) ) { + final ParameterizedType parameterizedType = (ParameterizedType) type; + if ( AttributeConverter.class.equals( parameterizedType.getRawType() ) ) { + return parameterizedType; + } + } + } + + throw new AssertionFailure( + "Could not extract ParameterizedType representation of AttributeConverter definition " + + "from AttributeConverter implementation class [" + attributeConverterClass.getName() + "]" + ); + } + + public AttributeConverter getAttributeConverter() { + return attributeConverter; + } + + public boolean isAutoApply() { + return autoApply; + } + + public Class getEntityAttributeType() { + return entityAttributeType; + } + + public Class getDatabaseColumnType() { + return databaseColumnType; + } + + private static Class extractType(TypeVariable typeVariable) { + java.lang.reflect.Type[] boundTypes = typeVariable.getBounds(); + if ( boundTypes == null || boundTypes.length != 1 ) { + return null; + } + + return (Class) boundTypes[0]; + } +} diff --git a/hibernate-core/src/main/java/org/hibernate/cfg/Configuration.java b/hibernate-core/src/main/java/org/hibernate/cfg/Configuration.java index c6f6e94701..272241b070 100644 --- a/hibernate-core/src/main/java/org/hibernate/cfg/Configuration.java +++ b/hibernate-core/src/main/java/org/hibernate/cfg/Configuration.java @@ -48,8 +48,10 @@ import java.util.Properties; import java.util.Set; import java.util.StringTokenizer; import java.util.TreeMap; +import java.util.concurrent.ConcurrentHashMap; import java.util.jar.JarFile; import java.util.zip.ZipEntry; +import javax.persistence.AttributeConverter; import javax.persistence.Embeddable; import javax.persistence.Entity; import javax.persistence.MapsId; @@ -63,6 +65,7 @@ import org.xml.sax.EntityResolver; import org.xml.sax.InputSource; import org.hibernate.AnnotationException; +import org.hibernate.AssertionFailure; import org.hibernate.DuplicateMappingException; import org.hibernate.EmptyInterceptor; import org.hibernate.HibernateException; @@ -259,6 +262,7 @@ public class Configuration implements Serializable { private CurrentTenantIdentifierResolver currentTenantIdentifierResolver; private boolean specjProprietarySyntaxEnabled; + private ConcurrentHashMap attributeConverterDefinitionsByClass; protected Configuration(SettingsFactory settingsFactory) { this.settingsFactory = settingsFactory; @@ -2450,6 +2454,52 @@ public class Configuration implements Serializable { this.currentTenantIdentifierResolver = currentTenantIdentifierResolver; } + /** + * Adds the AttributeConverter Class to this Configuration. + * + * @param attributeConverterClass The AttributeConverter class. + * @param autoApply Should the AttributeConverter be auto applied to property types as specified + * by its "entity attribute" parameterized type? + */ + public void addAttributeConverter(Class attributeConverterClass, boolean autoApply) { + final AttributeConverter attributeConverter; + try { + attributeConverter = attributeConverterClass.newInstance(); + } + catch (Exception e) { + throw new AnnotationException( + "Unable to instantiate AttributeConverter [" + attributeConverterClass.getName() + "]" + ); + } + addAttributeConverter( attributeConverter, autoApply ); + } + + /** + * Adds the AttributeConverter instance to this Configuration. This form is mainly intended for developers + * to programatically add their own AttributeConverter instance. HEM, instead, uses the + * {@link #addAttributeConverter(Class, boolean)} form + * + * @param attributeConverter The AttributeConverter instance. + * @param autoApply Should the AttributeConverter be auto applied to property types as specified + * by its "entity attribute" parameterized type? + */ + public void addAttributeConverter(AttributeConverter attributeConverter, boolean autoApply) { + if ( attributeConverterDefinitionsByClass == null ) { + attributeConverterDefinitionsByClass = new ConcurrentHashMap(); + } + + final Object old = attributeConverterDefinitionsByClass.put( + attributeConverter.getClass(), + new AttributeConverterDefinition( attributeConverter, autoApply ) + ); + + if ( old != null ) { + throw new AssertionFailure( + "AttributeConverter class [" + attributeConverter.getClass() + "] registered multiple times" + ); + } + } + // Mappings impl ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -2984,6 +3034,22 @@ public class Configuration implements Serializable { } } + @Override + public AttributeConverterDefinition locateAttributeConverter(Class converterClass) { + if ( attributeConverterDefinitionsByClass == null ) { + return null; + } + return attributeConverterDefinitionsByClass.get( converterClass ); + } + + @Override + public java.util.Collection getAttributeConverters() { + if ( attributeConverterDefinitionsByClass == null ) { + return Collections.emptyList(); + } + return attributeConverterDefinitionsByClass.values(); + } + public void addPropertyReference(String referencedClass, String propertyName) { propertyReferences.add( new PropertyReference( referencedClass, propertyName, false ) ); } diff --git a/hibernate-core/src/main/java/org/hibernate/cfg/Mappings.java b/hibernate-core/src/main/java/org/hibernate/cfg/Mappings.java index e77172f547..92a9cf9136 100644 --- a/hibernate-core/src/main/java/org/hibernate/cfg/Mappings.java +++ b/hibernate-core/src/main/java/org/hibernate/cfg/Mappings.java @@ -23,6 +23,7 @@ */ package org.hibernate.cfg; +import javax.persistence.AttributeConverter; import java.io.Serializable; import java.util.Iterator; import java.util.List; @@ -509,6 +510,23 @@ public interface Mappings { */ public void addSecondPass(SecondPass sp, boolean onTopOfTheQueue); + /** + * Locate the AttributeConverterDefinition corresponding to the given AttributeConverter Class. + * + * @param attributeConverterClass The AttributeConverter Class for which to get the definition + * + * @return The corresponding AttributeConverter definition; will return {@code null} if no corresponding + * definition found. + */ + public AttributeConverterDefinition locateAttributeConverter(Class attributeConverterClass); + + /** + * All all AttributeConverter definitions + * + * @return The collection of all AttributeConverter definitions. + */ + public java.util.Collection getAttributeConverters(); + /** * Represents a property-ref mapping. *

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 c9a321b138..24f7219df7 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 @@ -24,11 +24,16 @@ package org.hibernate.cfg.annotations; import java.io.Serializable; +import java.lang.reflect.TypeVariable; import java.sql.Types; import java.util.Calendar; import java.util.Date; import java.util.Properties; +import javax.persistence.AttributeConverter; +import javax.persistence.Convert; +import javax.persistence.Converts; import javax.persistence.Enumerated; +import javax.persistence.Id; import javax.persistence.Lob; import javax.persistence.MapKeyEnumerated; import javax.persistence.MapKeyTemporal; @@ -43,6 +48,8 @@ import org.hibernate.annotations.Parameter; import org.hibernate.annotations.Type; import org.hibernate.annotations.common.reflection.XClass; import org.hibernate.annotations.common.reflection.XProperty; +import org.hibernate.annotations.common.util.ReflectHelper; +import org.hibernate.cfg.AttributeConverterDefinition; import org.hibernate.cfg.BinderHelper; import org.hibernate.cfg.Ejb3Column; import org.hibernate.cfg.Ejb3JoinColumn; @@ -82,6 +89,8 @@ public class SimpleValueBinder { private boolean key; private String referencedEntityName; + private AttributeConverterDefinition attributeConverterDefinition; + public void setReferencedEntityName(String referencedEntityName) { this.referencedEntityName = referencedEntityName; } @@ -172,7 +181,6 @@ public class SimpleValueBinder { } } else if ( property.isAnnotationPresent( Lob.class ) ) { - if ( mappings.getReflectionManager().equals( returnedClassOrElement, java.sql.Clob.class ) ) { type = "clob"; } @@ -243,6 +251,201 @@ public class SimpleValueBinder { this.typeParameters = typeParameters; Type annType = property.getAnnotation( Type.class ); setExplicitType( annType ); + + applyAttributeConverter( property ); + } + + private void applyAttributeConverter(XProperty property) { + final boolean canBeConverted = ! property.isAnnotationPresent( Id.class ) + && ! isVersion + && ! isAssociation() + && ! property.isAnnotationPresent( Temporal.class ) + && ! property.isAnnotationPresent( Enumerated.class ); + + if ( canBeConverted ) { + // @Convert annotations take precedence + final Convert convertAnnotation = locateConvertAnnotation( property ); + if ( convertAnnotation != null ) { + if ( ! convertAnnotation.disableConversion() ) { + attributeConverterDefinition = mappings.locateAttributeConverter( convertAnnotation.converter() ); + } + } + else { + attributeConverterDefinition = locateAutoApplyAttributeConverter( property ); + } + } + } + + private AttributeConverterDefinition locateAutoApplyAttributeConverter(XProperty property) { + final Class propertyType = mappings.getReflectionManager().toClass( property.getType() ); + for ( AttributeConverterDefinition attributeConverterDefinition : mappings.getAttributeConverters() ) { + if ( areTypeMatch( attributeConverterDefinition.getEntityAttributeType(), propertyType ) ) { + return attributeConverterDefinition; + } + } + return null; + } + + private boolean isAssociation() { + // todo : this information is only known to caller(s), need to pass that information in somehow. + // or, is this enough? + return referencedEntityName != null; + } + + @SuppressWarnings("unchecked") + private Convert locateConvertAnnotation(XProperty property) { + // first look locally on the property for @Convert + Convert localConvertAnnotation = property.getAnnotation( Convert.class ); + if ( localConvertAnnotation != null ) { + return localConvertAnnotation; + } + + if ( persistentClassName == null ) { + LOG.debug( "Persistent Class name not known during attempt to locate @Convert annotations" ); + return null; + } + + final XClass owner; + try { + final Class ownerClass = ReflectHelper.classForName( persistentClassName ); + owner = mappings.getReflectionManager().classForName( persistentClassName, ownerClass ); + } + catch (ClassNotFoundException e) { + throw new AnnotationException( "Unable to resolve Class reference during attempt to locate @Convert annotations" ); + } + + return lookForEntityDefinedConvertAnnotation( property, owner ); + } + + private Convert lookForEntityDefinedConvertAnnotation(XProperty property, XClass owner) { + if ( owner == null ) { + // we have hit the root of the entity hierarchy + return null; + } + + { + Convert convertAnnotation = owner.getAnnotation( Convert.class ); + if ( convertAnnotation != null && isMatch( convertAnnotation, property ) ) { + return convertAnnotation; + } + } + + { + Converts convertsAnnotation = owner.getAnnotation( Converts.class ); + if ( convertsAnnotation != null ) { + for ( Convert convertAnnotation : convertsAnnotation.value() ) { + if ( isMatch( convertAnnotation, property ) ) { + return convertAnnotation; + } + } + } + } + + // finally, look on superclass + return lookForEntityDefinedConvertAnnotation( property, owner.getSuperclass() ); + } + + @SuppressWarnings("unchecked") + private boolean isMatch(Convert convertAnnotation, XProperty property) { + return property.getName().equals( convertAnnotation.attributeName() ) + && isTypeMatch( convertAnnotation.converter(), property ); + } + + private boolean isTypeMatch(Class attributeConverterClass, XProperty property) { + return areTypeMatch( + extractEntityAttributeType( attributeConverterClass ), + mappings.getReflectionManager().toClass( property.getType() ) + ); + } + + private Class extractEntityAttributeType(Class attributeConverterClass) { + // this is duplicated in SimpleValue... + final TypeVariable[] attributeConverterTypeInformation = attributeConverterClass.getTypeParameters(); + if ( attributeConverterTypeInformation == null || attributeConverterTypeInformation.length < 2 ) { + throw new AnnotationException( + "AttributeConverter [" + attributeConverterClass.getName() + + "] did not retain parameterized type information" + ); + } + + if ( attributeConverterTypeInformation.length > 2 ) { + LOG.debug( + "AttributeConverter [" + attributeConverterClass.getName() + + "] specified more than 2 parameterized types" + ); + } + final Class entityAttributeJavaType = extractType( attributeConverterTypeInformation[0] ); + if ( entityAttributeJavaType == null ) { + throw new AnnotationException( + "Could not determine 'entity attribute' type from given AttributeConverter [" + + attributeConverterClass.getName() + "]" + ); + } + return entityAttributeJavaType; + } + + private Class extractType(TypeVariable typeVariable) { + java.lang.reflect.Type[] boundTypes = typeVariable.getBounds(); + if ( boundTypes == null || boundTypes.length != 1 ) { + return null; + } + + return (Class) boundTypes[0]; + } + + private boolean areTypeMatch(Class converterDefinedType, Class propertyType) { + if ( converterDefinedType == null ) { + throw new AnnotationException( "AttributeConverter defined java type cannot be null" ); + } + if ( propertyType == null ) { + throw new AnnotationException( "Property defined java type cannot be null" ); + } + + return converterDefinedType.equals( propertyType ) + || arePrimitiveWrapperEquivalents( converterDefinedType, propertyType ); + } + + private boolean arePrimitiveWrapperEquivalents(Class converterDefinedType, Class propertyType) { + if ( converterDefinedType.isPrimitive() ) { + return getWrapperEquivalent( converterDefinedType ).equals( propertyType ); + } + else if ( propertyType.isPrimitive() ) { + return getWrapperEquivalent( propertyType ).equals( converterDefinedType ); + } + return false; + } + + private static Class getWrapperEquivalent(Class primitive) { + if ( ! primitive.isPrimitive() ) { + throw new AssertionFailure( "Passed type for which to locate wrapper equivalent was not a primitive" ); + } + + if ( boolean.class.equals( primitive ) ) { + return Boolean.class; + } + else if ( char.class.equals( primitive ) ) { + return Character.class; + } + else if ( byte.class.equals( primitive ) ) { + return Byte.class; + } + else if ( short.class.equals( primitive ) ) { + return Short.class; + } + else if ( int.class.equals( primitive ) ) { + return Integer.class; + } + else if ( long.class.equals( primitive ) ) { + return Long.class; + } + else if ( float.class.equals( primitive ) ) { + return Float.class; + } + else if ( double.class.equals( primitive ) ) { + return Double.class; + } + + throw new AssertionFailure( "Unexpected primitive type (VOID most likely) passed to getWrapperEquivalent" ); } private javax.persistence.EnumType getEnumType(XProperty property) { @@ -338,21 +541,36 @@ public class SimpleValueBinder { } public void fillSimpleValue() { - LOG.debugf( "Setting SimpleValue typeName for %s", propertyName ); - String type = BinderHelper.isEmptyAnnotationValue( explicitType ) ? returnedClassName : explicitType; - org.hibernate.mapping.TypeDef typeDef = mappings.getTypeDef( type ); - if ( typeDef != null ) { - type = typeDef.getTypeClass(); - simpleValue.setTypeParameters( typeDef.getParameters() ); + if ( attributeConverterDefinition != null ) { + if ( ! BinderHelper.isEmptyAnnotationValue( explicitType ) ) { + throw new AnnotationException( + String.format( + "AttributeConverter and explicit Type cannot be applied to same attribute [%s.%s];" + + "remove @Type or specify @Convert(disableConversion = true)", + persistentClassName, + propertyName + ) + ); + } + simpleValue.setJpaAttributeConverterDefinition( attributeConverterDefinition ); } - if ( typeParameters != null && typeParameters.size() != 0 ) { - //explicit type params takes precedence over type def params - simpleValue.setTypeParameters( typeParameters ); + else { + String type = BinderHelper.isEmptyAnnotationValue( explicitType ) ? returnedClassName : explicitType; + org.hibernate.mapping.TypeDef typeDef = mappings.getTypeDef( type ); + if ( typeDef != null ) { + type = typeDef.getTypeClass(); + simpleValue.setTypeParameters( typeDef.getParameters() ); + } + if ( typeParameters != null && typeParameters.size() != 0 ) { + //explicit type params takes precedence over type def params + simpleValue.setTypeParameters( typeParameters ); + } + simpleValue.setTypeName( type ); } - simpleValue.setTypeName( type ); - if ( persistentClassName != null ) { + + if ( persistentClassName != null || attributeConverterDefinition != null ) { simpleValue.setTypeUsingReflection( persistentClassName, propertyName ); } @@ -369,4 +587,5 @@ public class SimpleValueBinder { public void setKey(boolean key) { this.key = key; } + } 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 37e9467e05..a74d06a9e2 100644 --- a/hibernate-core/src/main/java/org/hibernate/mapping/SimpleValue.java +++ b/hibernate-core/src/main/java/org/hibernate/mapping/SimpleValue.java @@ -22,13 +22,24 @@ * Boston, MA 02110-1301 USA */ package org.hibernate.mapping; + +import javax.persistence.AttributeConverter; +import java.lang.reflect.TypeVariable; +import java.sql.CallableStatement; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; import java.util.ArrayList; import java.util.Iterator; import java.util.List; import java.util.Properties; +import org.jboss.logging.Logger; + +import org.hibernate.AnnotationException; import org.hibernate.FetchMode; import org.hibernate.MappingException; +import org.hibernate.cfg.AttributeConverterDefinition; import org.hibernate.cfg.Environment; import org.hibernate.cfg.Mappings; import org.hibernate.dialect.Dialect; @@ -38,13 +49,26 @@ import org.hibernate.id.IdentityGenerator; import org.hibernate.id.PersistentIdentifierGenerator; import org.hibernate.id.factory.IdentifierGeneratorFactory; import org.hibernate.internal.util.ReflectHelper; +import org.hibernate.type.AbstractSingleColumnStandardBasicType; import org.hibernate.type.Type; +import org.hibernate.type.descriptor.ValueBinder; +import org.hibernate.type.descriptor.ValueExtractor; +import org.hibernate.type.descriptor.WrapperOptions; +import org.hibernate.type.descriptor.java.JavaTypeDescriptor; +import org.hibernate.type.descriptor.java.JavaTypeDescriptorRegistry; +import org.hibernate.type.descriptor.sql.BasicBinder; +import org.hibernate.type.descriptor.sql.BasicExtractor; +import org.hibernate.type.descriptor.sql.JdbcTypeJavaClassMappings; +import org.hibernate.type.descriptor.sql.SqlTypeDescriptor; +import org.hibernate.type.descriptor.sql.SqlTypeDescriptorRegistry; /** * Any value that maps to columns. * @author Gavin King */ public class SimpleValue implements KeyValue { + private static final Logger log = Logger.getLogger( SimpleValue.class ); + public static final String DEFAULT_ID_GEN_STRATEGY = "assigned"; private final Mappings mappings; @@ -60,6 +84,9 @@ public class SimpleValue implements KeyValue { private Properties typeParameters; private boolean cascadeDeleteEnabled; + private AttributeConverterDefinition jpaAttributeConverterDefinition; + private Type type; + public SimpleValue(Mappings mappings) { this.mappings = mappings; } @@ -290,30 +317,100 @@ public class SimpleValue implements KeyValue { } public Type getType() throws MappingException { - if (typeName==null) { - throw new MappingException("No type name"); + if ( type != null ) { + return type; } - Type result = mappings.getTypeResolver().heuristicType(typeName, typeParameters); - if (result==null) { + + if ( typeName == null ) { + throw new MappingException( "No type name" ); + } + + Type result = mappings.getTypeResolver().heuristicType( typeName, typeParameters ); + if ( result == null ) { String msg = "Could not determine type for: " + typeName; - if(table != null){ + if ( table != null ) { msg += ", at table: " + table.getName(); } - if(columns!=null && columns.size()>0) { + if ( columns!=null && columns.size()>0 ) { msg += ", for columns: " + columns; } - throw new MappingException(msg); + throw new MappingException( msg ); } + return result; } + @SuppressWarnings("unchecked") public void setTypeUsingReflection(String className, String propertyName) throws MappingException { - if (typeName==null) { - if (className==null) { - throw new MappingException("you must specify types for a dynamic entity: " + propertyName); - } - typeName = ReflectHelper.reflectedPropertyClass(className, propertyName).getName(); + // NOTE : this is called as the last piece in setting SimpleValue type information, and implementations + // rely on that fact, using it as a signal that all information it is going to get is defined at this point... + + if ( typeName != null ) { + // assume either (a) explicit type was specified or (b) determine was already performed + return; } + + if ( type != null ) { + return; + } + + if ( jpaAttributeConverterDefinition == null ) { + // this is here to work like legacy. This should change when we integrate with metamodel to + // look for SqlTypeDescriptor and JavaTypeDescriptor individually and create the BasicType (well, really + // keep a registry of [SqlTypeDescriptor,JavaTypeDescriptor] -> BasicType...) + if ( className == null ) { + throw new MappingException( "you must specify types for a dynamic entity: " + propertyName ); + } + typeName = ReflectHelper.reflectedPropertyClass( className, propertyName ).getName(); + return; + } + + // we had an AttributeConverter... + + // todo : we should validate the number of columns present + // todo : ultimately I want to see attributeConverterJavaType and attributeConverterJdbcTypeCode specify-able separately + // then we can "play them against each other" in terms of determining proper typing + // todo : see if we already have previously built a custom on-the-fly BasicType for this AttributeConverter; see note below about caching + + // AttributeConverter works totally in memory, meaning it converts between one Java representation (the entity + // attribute representation) and another (the value bound into JDBC statements or extracted from results). + // However, the Hibernate Type system operates at the lower level of actually dealing with those JDBC objects. + // So even though we have an AttributeConverter, we still need to "fill out" the rest of the BasicType + // data. For the JavaTypeDescriptor portion we simply resolve the "entity attribute representation" part of + // the AttributeConverter to resolve the corresponding descriptor. For the SqlTypeDescriptor portion we use the + // "database column representation" part of the AttributeConverter to resolve the "recommended" JDBC type-code + // and use that type-code to resolve the SqlTypeDescriptor to use. + final Class entityAttributeJavaType = jpaAttributeConverterDefinition.getEntityAttributeType(); + final Class databaseColumnJavaType = jpaAttributeConverterDefinition.getDatabaseColumnType(); + final int jdbcTypeCode = JdbcTypeJavaClassMappings.INSTANCE.determineJdbcTypeCodeForJavaClass( databaseColumnJavaType ); + + final JavaTypeDescriptor javaTypeDescriptor = JavaTypeDescriptorRegistry.INSTANCE.getDescriptor( entityAttributeJavaType ); + final SqlTypeDescriptor sqlTypeDescriptor = SqlTypeDescriptorRegistry.INSTANCE.getDescriptor( jdbcTypeCode ); + // the adapter here injects the AttributeConverter calls into the binding/extraction process... + final SqlTypeDescriptor sqlTypeDescriptorAdapter = new AttributeConverterSqlTypeDescriptorAdapter( + jpaAttributeConverterDefinition.getAttributeConverter(), + sqlTypeDescriptor + ); + + final String name = "BasicType adapter for AttributeConverter<" + entityAttributeJavaType + "," + databaseColumnJavaType + ">"; + type = new AbstractSingleColumnStandardBasicType( sqlTypeDescriptorAdapter, javaTypeDescriptor ) { + @Override + public String getName() { + return name; + } + }; + log.debug( "Created : " + name ); + + // todo : cache the BasicType we just created in case that AttributeConverter is applied multiple times. + } + + private Class extractType(TypeVariable typeVariable) { + java.lang.reflect.Type[] boundTypes = typeVariable.getBounds(); + if ( boundTypes == null || boundTypes.length != 1 ) { + return null; + } + + return (Class) boundTypes[0]; } public boolean isTypeSpecified() { @@ -351,4 +448,60 @@ public class SimpleValue implements KeyValue { public boolean[] getColumnUpdateability() { return getColumnInsertability(); } + + public void setJpaAttributeConverterDefinition(AttributeConverterDefinition jpaAttributeConverterDefinition) { + this.jpaAttributeConverterDefinition = jpaAttributeConverterDefinition; + } + + public static class AttributeConverterSqlTypeDescriptorAdapter implements SqlTypeDescriptor { + private final AttributeConverter converter; + private final SqlTypeDescriptor delegate; + + public AttributeConverterSqlTypeDescriptorAdapter(AttributeConverter converter, SqlTypeDescriptor delegate) { + this.converter = converter; + this.delegate = delegate; + } + + @Override + public int getSqlType() { + return delegate.getSqlType(); + } + + @Override + public boolean canBeRemapped() { + return delegate.canBeRemapped(); + } + + @Override + public ValueBinder getBinder(JavaTypeDescriptor javaTypeDescriptor) { + final ValueBinder realBinder = delegate.getBinder( javaTypeDescriptor ); + return new BasicBinder( javaTypeDescriptor, this ) { + @Override + @SuppressWarnings("unchecked") + protected void doBind(PreparedStatement st, X value, int index, WrapperOptions options) + throws SQLException { + realBinder.bind( st, converter.convertToDatabaseColumn( value ), index, options ); + } + }; + } + + @Override + public ValueExtractor getExtractor(JavaTypeDescriptor javaTypeDescriptor) { + final ValueExtractor realExtractor = delegate.getExtractor( javaTypeDescriptor ); + return new BasicExtractor( javaTypeDescriptor, this ) { + @Override + @SuppressWarnings("unchecked") + protected X doExtract(ResultSet rs, String name, WrapperOptions options) throws SQLException { + return (X) converter.convertToEntityAttribute( realExtractor.extract( rs, name, options ) ); + } + + @Override + @SuppressWarnings("unchecked") + protected X doExtract(CallableStatement statement, int index, WrapperOptions options) + throws SQLException { + return (X) converter.convertToEntityAttribute( realExtractor.extract( statement, index, options ) ); + } + }; + } + } } diff --git a/hibernate-core/src/main/java/org/hibernate/type/AbstractSingleColumnStandardBasicType.java b/hibernate-core/src/main/java/org/hibernate/type/AbstractSingleColumnStandardBasicType.java index d3463c847b..64b6467684 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/AbstractSingleColumnStandardBasicType.java +++ b/hibernate-core/src/main/java/org/hibernate/type/AbstractSingleColumnStandardBasicType.java @@ -44,13 +44,12 @@ public abstract class AbstractSingleColumnStandardBasicType super( sqlTypeDescriptor, javaTypeDescriptor ); } + @Override public final int sqlType() { return getSqlTypeDescriptor().getSqlType(); } - /** - * {@inheritDoc} - */ + @Override public final void nullSafeSet(PreparedStatement st, Object value, int index, boolean[] settable, SessionImplementor session) throws HibernateException, SQLException { if ( settable[0] ) { diff --git a/hibernate-core/src/main/java/org/hibernate/type/descriptor/JdbcTypeNameMapper.java b/hibernate-core/src/main/java/org/hibernate/type/descriptor/JdbcTypeNameMapper.java index 761ac20cc7..032dc07878 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/descriptor/JdbcTypeNameMapper.java +++ b/hibernate-core/src/main/java/org/hibernate/type/descriptor/JdbcTypeNameMapper.java @@ -24,7 +24,6 @@ package org.hibernate.type.descriptor; import java.lang.reflect.Field; -import java.sql.Types; import java.util.Collections; import java.util.HashMap; import java.util.Map; @@ -35,7 +34,7 @@ import org.hibernate.HibernateException; import org.hibernate.internal.CoreMessageLogger; /** - * TODO : javadoc + * (Badly named) helper for dealing with standard JDBC types as defined by {@link java.sql.Types} * * @author Steve Ebersole */ @@ -46,7 +45,7 @@ public class JdbcTypeNameMapper { private static Map buildJdbcTypeMap() { HashMap map = new HashMap(); - Field[] fields = Types.class.getFields(); + Field[] fields = java.sql.Types.class.getFields(); if ( fields == null ) { throw new HibernateException( "Unexpected problem extracting JDBC type mapping codes from java.sql.Types" ); } @@ -54,7 +53,9 @@ public class JdbcTypeNameMapper { try { final int code = field.getInt( null ); String old = map.put( code, field.getName() ); - if (old != null) LOG.JavaSqlTypesMappedSameCodeMultipleTimes(code, old, field.getName()); + if ( old != null ) { + LOG.JavaSqlTypesMappedSameCodeMultipleTimes( code, old, field.getName() ); + } } catch ( IllegalAccessException e ) { throw new HibernateException( "Unable to access JDBC type mapping [" + field.getName() + "]", e ); @@ -63,10 +64,43 @@ public class JdbcTypeNameMapper { return Collections.unmodifiableMap( map ); } - public static String getTypeName(Integer code) { - String name = JDBC_TYPE_MAP.get( code ); + /** + * Determine whether the given JDBC type code represents a standard JDBC type ("standard" being those defined on + * {@link java.sql.Types}). + * + * NOTE : {@link java.sql.Types#OTHER} is also "filtered out" as being non-standard. + * + * @param typeCode The JDBC type code to check + * + * @return {@code true} to indicate the type code is a standard type code; {@code false} otherwise. + */ + public static boolean isStandardTypeCode(int typeCode) { + return isStandardTypeCode( Integer.valueOf( typeCode ) ); + } + + /** + * Same as call to {@link #isStandardTypeCode(int)} + * + * @see #isStandardTypeCode(int) + */ + public static boolean isStandardTypeCode(Integer typeCode) { + return JDBC_TYPE_MAP.containsKey( typeCode ); + } + + /** + * Get the type name as in the static field names defined on {@link java.sql.Types}. If a type code is not + * recognized, it is reported as {@code UNKNOWN(?)} where '?' is replace with the given type code. + * + * Intended as useful for logging purposes... + * + * @param typeCode The type code to find the name for. + * + * @return The type name. + */ + public static String getTypeName(Integer typeCode) { + String name = JDBC_TYPE_MAP.get( typeCode ); if ( name == null ) { - return "UNKNOWN(" + code + ")"; + return "UNKNOWN(" + typeCode + ")"; } return name; } diff --git a/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/AbstractTypeDescriptor.java b/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/AbstractTypeDescriptor.java index a88c4a004c..8a92be9e6d 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/AbstractTypeDescriptor.java +++ b/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/AbstractTypeDescriptor.java @@ -65,46 +65,36 @@ public abstract class AbstractTypeDescriptor implements JavaTypeDescriptor this.comparator = Comparable.class.isAssignableFrom( type ) ? (Comparator) ComparableComparator.INSTANCE : null; + + JavaTypeDescriptorRegistry.INSTANCE.addDescriptor( this ); } - /** - * {@inheritDoc} - */ + @Override public MutabilityPlan getMutabilityPlan() { return mutabilityPlan; } - /** - * {@inheritDoc} - */ + @Override public Class getJavaTypeClass() { return type; } - /** - * {@inheritDoc} - */ + @Override public int extractHashCode(T value) { return value.hashCode(); } - /** - * {@inheritDoc} - */ + @Override public boolean areEqual(T one, T another) { return EqualsHelper.equals( one, another ); } - /** - * {@inheritDoc} - */ + @Override public Comparator getComparator() { return comparator; } - /** - * {@inheritDoc} - */ + @Override public String extractLoggableRepresentation(T value) { return (value == null) ? "null" : value.toString(); } diff --git a/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/ByteArrayTypeDescriptor.java b/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/ByteArrayTypeDescriptor.java index 39fcac2023..450d7ddffe 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/ByteArrayTypeDescriptor.java +++ b/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/ByteArrayTypeDescriptor.java @@ -33,7 +33,7 @@ import org.hibernate.type.descriptor.BinaryStream; import org.hibernate.type.descriptor.WrapperOptions; /** - * TODO : javadoc + * Descriptor for {@code Byte[]} handling. * * @author Steve Ebersole */ diff --git a/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/CharacterArrayTypeDescriptor.java b/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/CharacterArrayTypeDescriptor.java index fae1cc5d32..8d77e0cc04 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/CharacterArrayTypeDescriptor.java +++ b/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/CharacterArrayTypeDescriptor.java @@ -32,7 +32,7 @@ import org.hibernate.type.descriptor.CharacterStream; import org.hibernate.type.descriptor.WrapperOptions; /** - * TODO : javadoc + * Descriptor for {@code Character[]} handling. * * @author Steve Ebersole */ diff --git a/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/ClassTypeDescriptor.java b/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/ClassTypeDescriptor.java index c357932361..0c0ab2ec02 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/ClassTypeDescriptor.java +++ b/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/ClassTypeDescriptor.java @@ -27,7 +27,7 @@ import org.hibernate.internal.util.ReflectHelper; import org.hibernate.type.descriptor.WrapperOptions; /** - * TODO : javadoc + * Descriptor for {@link Class} handling. * * @author Steve Ebersole */ diff --git a/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/CurrencyTypeDescriptor.java b/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/CurrencyTypeDescriptor.java index 8566e3ae41..0b849a71ca 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/CurrencyTypeDescriptor.java +++ b/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/CurrencyTypeDescriptor.java @@ -28,7 +28,7 @@ import java.util.Currency; import org.hibernate.type.descriptor.WrapperOptions; /** - * TODO : javadoc + * Descriptor for {@link Currency} handling. * * @author Steve Ebersole */ @@ -39,10 +39,12 @@ public class CurrencyTypeDescriptor extends AbstractTypeDescriptor { super( Currency.class ); } + @Override public String toString(Currency value) { return value.getCurrencyCode(); } + @Override public Currency fromString(String string) { return Currency.getInstance( string ); } @@ -58,6 +60,7 @@ public class CurrencyTypeDescriptor extends AbstractTypeDescriptor { throw unknownUnwrap( type ); } + @Override public Currency wrap(X value, WrapperOptions options) { if ( value == null ) { return null; diff --git a/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/DateTypeDescriptor.java b/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/DateTypeDescriptor.java index 87a006212d..1882c725cd 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/DateTypeDescriptor.java +++ b/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/DateTypeDescriptor.java @@ -33,7 +33,7 @@ import org.hibernate.HibernateException; import org.hibernate.type.descriptor.WrapperOptions; /** - * TODO : javadoc + * Descriptor for {@link Date} handling. * * @author Steve Ebersole */ diff --git a/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/ImmutableMutabilityPlan.java b/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/ImmutableMutabilityPlan.java index 8b9a7ab7e8..6c7faae29e 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/ImmutableMutabilityPlan.java +++ b/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/ImmutableMutabilityPlan.java @@ -22,6 +22,7 @@ * Boston, MA 02110-1301 USA */ package org.hibernate.type.descriptor.java; + import java.io.Serializable; /** @@ -32,30 +33,22 @@ import java.io.Serializable; public class ImmutableMutabilityPlan implements MutabilityPlan { public static final ImmutableMutabilityPlan INSTANCE = new ImmutableMutabilityPlan(); - /** - * {@inheritDoc} - */ + @Override public boolean isMutable() { return false; } - /** - * {@inheritDoc} - */ + @Override public T deepCopy(T value) { return value; } - /** - * {@inheritDoc} - */ + @Override public Serializable disassemble(T value) { return (Serializable) value; } - /** - * {@inheritDoc} - */ + @Override @SuppressWarnings({ "unchecked" }) public T assemble(Serializable cached) { return (T) cached; diff --git a/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/IncomparableComparator.java b/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/IncomparableComparator.java index 9221550b91..9fc5316edc 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/IncomparableComparator.java +++ b/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/IncomparableComparator.java @@ -22,16 +22,19 @@ * Boston, MA 02110-1301 USA */ package org.hibernate.type.descriptor.java; + import java.util.Comparator; /** - * TODO : javadoc + * Comparator for things that cannot be compared (in a way we know about). * * @author Steve Ebersole */ public class IncomparableComparator implements Comparator { public static final IncomparableComparator INSTANCE = new IncomparableComparator(); + @Override + @SuppressWarnings("ComparatorMethodParameterNotUsed") public int compare(Object o1, Object o2) { return 0; } diff --git a/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/JavaTypeDescriptorRegistry.java b/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/JavaTypeDescriptorRegistry.java new file mode 100644 index 0000000000..48e94a1dca --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/JavaTypeDescriptorRegistry.java @@ -0,0 +1,112 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * Copyright (c) 2012, Red Hat Inc. or third-party contributors as + * indicated by the @author tags or express copyright attribution + * statements applied by the authors. All third-party contributions are + * distributed under license by Red Hat Inc. + * + * This copyrighted material is made available to anyone wishing to use, modify, + * copy, or redistribute it subject to the terms and conditions of the GNU + * Lesser General Public License, as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this distribution; if not, write to: + * Free Software Foundation, Inc. + * 51 Franklin Street, Fifth Floor + * Boston, MA 02110-1301 USA + */ +package org.hibernate.type.descriptor.java; + +import java.io.Serializable; +import java.util.Comparator; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +import org.hibernate.HibernateException; +import org.hibernate.type.descriptor.WrapperOptions; + +/** + * Basically a map from {@link Class} -> {@link JavaTypeDescriptor} + * + * @author Steve Ebersole + */ +public class JavaTypeDescriptorRegistry { + public static final JavaTypeDescriptorRegistry INSTANCE = new JavaTypeDescriptorRegistry(); + + private ConcurrentHashMap descriptorsByClass = new ConcurrentHashMap(); + + /** + * Adds the given descriptor to this registry + * + * @param descriptor The descriptor to add. + */ + public void addDescriptor(JavaTypeDescriptor descriptor) { + descriptorsByClass.put( descriptor.getJavaTypeClass(), descriptor ); + } + + @SuppressWarnings("unchecked") + public JavaTypeDescriptor getDescriptor(Class cls) { + if ( cls == null ) { + throw new IllegalArgumentException( "Class passed to locate Java type descriptor cannot be null" ); + } + + JavaTypeDescriptor descriptor = descriptorsByClass.get( cls ); + if ( descriptor != null ) { + return descriptor; + } + + // find the first "assignable" match + for ( Map.Entry entry : descriptorsByClass.entrySet() ) { + if ( cls.isAssignableFrom( entry.getKey() ) ) { + return entry.getValue(); + } + } + + // we could not find one; warn the user (as stuff is likely to break later) and create a fallback instance... + if ( Serializable.class.isAssignableFrom( cls ) ) { + return new SerializableTypeDescriptor( cls ); + } + else { + return new FallbackJavaTypeDescriptor( cls ); + } + } + + public static class FallbackJavaTypeDescriptor extends AbstractTypeDescriptor { + + @SuppressWarnings("unchecked") + protected FallbackJavaTypeDescriptor(Class type) { + // MutableMutabilityPlan would be the "safest" option, but we do not necessarily know how to deepCopy etc... + super( type, ImmutableMutabilityPlan.INSTANCE ); + } + + @Override + public String toString(T value) { + return value == null ? "" : value.toString(); + } + + @Override + public T fromString(String string) { + throw new HibernateException( + "Not known how to convert String to given type [" + getJavaTypeClass().getName() + "]" + ); + } + + @Override + @SuppressWarnings("unchecked") + public X unwrap(T value, Class type, WrapperOptions options) { + return (X) value; + } + + @Override + @SuppressWarnings("unchecked") + public T wrap(X value, WrapperOptions options) { + return (T) value; + } + } +} diff --git a/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/JdbcDateTypeDescriptor.java b/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/JdbcDateTypeDescriptor.java index 4e96131ce4..bc52ffba83 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/JdbcDateTypeDescriptor.java +++ b/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/JdbcDateTypeDescriptor.java @@ -33,7 +33,7 @@ import org.hibernate.HibernateException; import org.hibernate.type.descriptor.WrapperOptions; /** - * TODO : javadoc + * Descriptor for {@link java.sql.Date} handling. * * @author Steve Ebersole */ diff --git a/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/JdbcTimeTypeDescriptor.java b/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/JdbcTimeTypeDescriptor.java index aa175902cd..d68fd029a2 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/JdbcTimeTypeDescriptor.java +++ b/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/JdbcTimeTypeDescriptor.java @@ -34,7 +34,7 @@ import org.hibernate.HibernateException; import org.hibernate.type.descriptor.WrapperOptions; /** - * TODO : javadoc + * Descriptor for {@link Time} handling. * * @author Steve Ebersole */ diff --git a/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/JdbcTimestampTypeDescriptor.java b/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/JdbcTimestampTypeDescriptor.java index 92d32a58ed..bdb8732b2b 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/JdbcTimestampTypeDescriptor.java +++ b/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/JdbcTimestampTypeDescriptor.java @@ -34,7 +34,7 @@ import org.hibernate.HibernateException; import org.hibernate.type.descriptor.WrapperOptions; /** - * TODO : javadoc + * Descriptor for {@link Timestamp} handling. * * @author Steve Ebersole */ @@ -53,8 +53,7 @@ public class JdbcTimestampTypeDescriptor extends AbstractTypeDescriptor { return ts; } else { - Date orig = value; - return new Date( orig.getTime() ); + return new Date( value.getTime() ); } } } diff --git a/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/LocaleTypeDescriptor.java b/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/LocaleTypeDescriptor.java index 4a3f02772d..646662ac00 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/LocaleTypeDescriptor.java +++ b/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/LocaleTypeDescriptor.java @@ -30,7 +30,7 @@ import java.util.StringTokenizer; import org.hibernate.type.descriptor.WrapperOptions; /** - * TODO : javadoc + * Descriptor for {@link Locale} handling. * * @author Steve Ebersole */ diff --git a/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/MutabilityPlan.java b/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/MutabilityPlan.java index abd9aeac01..2224d40a19 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/MutabilityPlan.java +++ b/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/MutabilityPlan.java @@ -22,10 +22,12 @@ * Boston, MA 02110-1301 USA */ package org.hibernate.type.descriptor.java; + import java.io.Serializable; /** - * TODO : javadoc + * Describes the mutability aspects of a Java type. The term mutability refers to the fact that generally speaking + * the aspects described by this contract are defined by whether the Java type's internal state is mutable or not. * * @author Steve Ebersole */ diff --git a/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/MutableMutabilityPlan.java b/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/MutableMutabilityPlan.java index 4365250dc9..d405c71cd4 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/MutableMutabilityPlan.java +++ b/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/MutableMutabilityPlan.java @@ -22,6 +22,7 @@ * Boston, MA 02110-1301 USA */ package org.hibernate.type.descriptor.java; + import java.io.Serializable; /** @@ -30,29 +31,23 @@ import java.io.Serializable; * @author Steve Ebersole */ public abstract class MutableMutabilityPlan implements MutabilityPlan { - - /** - * {@inheritDoc} - */ + @Override public boolean isMutable() { return true; } - /** - * {@inheritDoc} - */ + @Override public Serializable disassemble(T value) { return (Serializable) deepCopy( value ); } - /** - * {@inheritDoc} - */ + @Override @SuppressWarnings({ "unchecked" }) public T assemble(Serializable cached) { return (T) deepCopy( (T) cached ); } + @Override public final T deepCopy(T value) { return value == null ? null : deepCopyNotNull( value ); } diff --git a/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/PrimitiveCharacterArrayTypeDescriptor.java b/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/PrimitiveCharacterArrayTypeDescriptor.java index 21e54cd0df..33bbbc8b5f 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/PrimitiveCharacterArrayTypeDescriptor.java +++ b/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/PrimitiveCharacterArrayTypeDescriptor.java @@ -32,7 +32,7 @@ import org.hibernate.type.descriptor.CharacterStream; import org.hibernate.type.descriptor.WrapperOptions; /** - * TODO : javadoc + * Descriptor for {@code char[]} handling. * * @author Steve Ebersole */ diff --git a/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/SerializableTypeDescriptor.java b/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/SerializableTypeDescriptor.java index 346798c0c5..47a6a40e88 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/SerializableTypeDescriptor.java +++ b/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/SerializableTypeDescriptor.java @@ -32,7 +32,7 @@ import org.hibernate.type.descriptor.BinaryStream; import org.hibernate.type.descriptor.WrapperOptions; /** - * TODO : javadoc + * Descriptor for general {@link Serializable} handling. * * @author Steve Ebersole */ diff --git a/hibernate-core/src/main/java/org/hibernate/type/descriptor/sql/BigIntTypeDescriptor.java b/hibernate-core/src/main/java/org/hibernate/type/descriptor/sql/BigIntTypeDescriptor.java index cf2103d90e..ff2080f645 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/descriptor/sql/BigIntTypeDescriptor.java +++ b/hibernate-core/src/main/java/org/hibernate/type/descriptor/sql/BigIntTypeDescriptor.java @@ -42,6 +42,11 @@ import org.hibernate.type.descriptor.java.JavaTypeDescriptor; public class BigIntTypeDescriptor implements SqlTypeDescriptor { public static final BigIntTypeDescriptor INSTANCE = new BigIntTypeDescriptor(); + public BigIntTypeDescriptor() { + SqlTypeDescriptorRegistry.INSTANCE.addDescriptor( this ); + } + + @Override public int getSqlType() { return Types.BIGINT; } @@ -51,6 +56,7 @@ public class BigIntTypeDescriptor implements SqlTypeDescriptor { return true; } + @Override public ValueBinder getBinder(final JavaTypeDescriptor javaTypeDescriptor) { return new BasicBinder( javaTypeDescriptor, this ) { @Override @@ -60,6 +66,7 @@ public class BigIntTypeDescriptor implements SqlTypeDescriptor { }; } + @Override public ValueExtractor getExtractor(final JavaTypeDescriptor javaTypeDescriptor) { return new BasicExtractor( javaTypeDescriptor, this ) { @Override diff --git a/hibernate-core/src/main/java/org/hibernate/type/descriptor/sql/BinaryTypeDescriptor.java b/hibernate-core/src/main/java/org/hibernate/type/descriptor/sql/BinaryTypeDescriptor.java index 55bcaefaa2..631a937d03 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/descriptor/sql/BinaryTypeDescriptor.java +++ b/hibernate-core/src/main/java/org/hibernate/type/descriptor/sql/BinaryTypeDescriptor.java @@ -32,6 +32,10 @@ import java.sql.Types; public class BinaryTypeDescriptor extends VarbinaryTypeDescriptor { public static final BinaryTypeDescriptor INSTANCE = new BinaryTypeDescriptor(); + public BinaryTypeDescriptor() { + SqlTypeDescriptorRegistry.INSTANCE.addDescriptor( this ); + } + @Override public int getSqlType() { return Types.BINARY; diff --git a/hibernate-core/src/main/java/org/hibernate/type/descriptor/sql/BitTypeDescriptor.java b/hibernate-core/src/main/java/org/hibernate/type/descriptor/sql/BitTypeDescriptor.java index a60fa08d4a..5111c21e2f 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/descriptor/sql/BitTypeDescriptor.java +++ b/hibernate-core/src/main/java/org/hibernate/type/descriptor/sql/BitTypeDescriptor.java @@ -45,6 +45,10 @@ import org.hibernate.type.descriptor.java.JavaTypeDescriptor; public class BitTypeDescriptor implements SqlTypeDescriptor { public static final BitTypeDescriptor INSTANCE = new BitTypeDescriptor(); + public BitTypeDescriptor() { + SqlTypeDescriptorRegistry.INSTANCE.addDescriptor( this ); + } + public int getSqlType() { return Types.BIT; } @@ -54,6 +58,7 @@ public class BitTypeDescriptor implements SqlTypeDescriptor { return true; } + @Override public ValueBinder getBinder(final JavaTypeDescriptor javaTypeDescriptor) { return new BasicBinder( javaTypeDescriptor, this ) { @Override @@ -63,6 +68,7 @@ public class BitTypeDescriptor implements SqlTypeDescriptor { }; } + @Override public ValueExtractor getExtractor(final JavaTypeDescriptor javaTypeDescriptor) { return new BasicExtractor( javaTypeDescriptor, this ) { @Override diff --git a/hibernate-core/src/main/java/org/hibernate/type/descriptor/sql/BlobTypeDescriptor.java b/hibernate-core/src/main/java/org/hibernate/type/descriptor/sql/BlobTypeDescriptor.java index 1cda8b54a1..2f6280c328 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/descriptor/sql/BlobTypeDescriptor.java +++ b/hibernate-core/src/main/java/org/hibernate/type/descriptor/sql/BlobTypeDescriptor.java @@ -31,7 +31,6 @@ import java.sql.SQLException; import java.sql.Types; import org.hibernate.type.descriptor.BinaryStream; -import org.hibernate.type.descriptor.ValueBinder; import org.hibernate.type.descriptor.ValueExtractor; import org.hibernate.type.descriptor.WrapperOptions; import org.hibernate.type.descriptor.java.JavaTypeDescriptor; @@ -40,13 +39,50 @@ import org.hibernate.type.descriptor.java.JavaTypeDescriptor; * Descriptor for {@link Types#BLOB BLOB} handling. * * @author Steve Ebersole + * @author Gail Badner */ public abstract class BlobTypeDescriptor implements SqlTypeDescriptor { - private BlobTypeDescriptor() {} + private BlobTypeDescriptor() { + } + + @Override + public int getSqlType() { + return Types.BLOB; + } + + @Override + public boolean canBeRemapped() { + return true; + } + + @Override + public ValueExtractor getExtractor(final JavaTypeDescriptor javaTypeDescriptor) { + return new BasicExtractor( javaTypeDescriptor, this ) { + @Override + protected X doExtract(ResultSet rs, String name, WrapperOptions options) throws SQLException { + return javaTypeDescriptor.wrap( rs.getBlob( name ), options ); + } + + @Override + protected X doExtract(CallableStatement statement, int index, WrapperOptions options) throws SQLException { + return javaTypeDescriptor.wrap( statement.getBlob( index ), options ); + } + }; + } + + protected abstract BasicBinder getBlobBinder(final JavaTypeDescriptor javaTypeDescriptor); + + public BasicBinder getBinder(final JavaTypeDescriptor javaTypeDescriptor) { + return getBlobBinder( javaTypeDescriptor ); + } public static final BlobTypeDescriptor DEFAULT = new BlobTypeDescriptor() { + { + SqlTypeDescriptorRegistry.INSTANCE.addDescriptor( this ); + } + @Override public BasicBinder getBlobBinder(final JavaTypeDescriptor javaTypeDescriptor) { return new BasicBinder( javaTypeDescriptor, this ) { @@ -110,32 +146,4 @@ public abstract class BlobTypeDescriptor implements SqlTypeDescriptor { } }; - protected abstract BasicBinder getBlobBinder(final JavaTypeDescriptor javaTypeDescriptor); - - public ValueExtractor getExtractor(final JavaTypeDescriptor javaTypeDescriptor) { - return new BasicExtractor( javaTypeDescriptor, this ) { - @Override - protected X doExtract(ResultSet rs, String name, WrapperOptions options) throws SQLException { - return javaTypeDescriptor.wrap( rs.getBlob( name ), options ); - } - - @Override - protected X doExtract(CallableStatement statement, int index, WrapperOptions options) throws SQLException { - return javaTypeDescriptor.wrap( statement.getBlob( index ), options ); - } - }; - } - - public int getSqlType() { - return Types.BLOB; - } - - @Override - public boolean canBeRemapped() { - return true; - } - - public ValueBinder getBinder(final JavaTypeDescriptor javaTypeDescriptor) { - return getBlobBinder( javaTypeDescriptor ); - } } diff --git a/hibernate-core/src/main/java/org/hibernate/type/descriptor/sql/BooleanTypeDescriptor.java b/hibernate-core/src/main/java/org/hibernate/type/descriptor/sql/BooleanTypeDescriptor.java index afa54c280f..a9cb2b889c 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/descriptor/sql/BooleanTypeDescriptor.java +++ b/hibernate-core/src/main/java/org/hibernate/type/descriptor/sql/BooleanTypeDescriptor.java @@ -42,6 +42,10 @@ import org.hibernate.type.descriptor.java.JavaTypeDescriptor; public class BooleanTypeDescriptor implements SqlTypeDescriptor { public static final BooleanTypeDescriptor INSTANCE = new BooleanTypeDescriptor(); + public BooleanTypeDescriptor() { + SqlTypeDescriptorRegistry.INSTANCE.addDescriptor( this ); + } + public int getSqlType() { return Types.BOOLEAN; } diff --git a/hibernate-core/src/main/java/org/hibernate/type/descriptor/sql/CharTypeDescriptor.java b/hibernate-core/src/main/java/org/hibernate/type/descriptor/sql/CharTypeDescriptor.java index b28e614900..8eb12bacd3 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/descriptor/sql/CharTypeDescriptor.java +++ b/hibernate-core/src/main/java/org/hibernate/type/descriptor/sql/CharTypeDescriptor.java @@ -32,6 +32,10 @@ import java.sql.Types; public class CharTypeDescriptor extends VarcharTypeDescriptor { public static final CharTypeDescriptor INSTANCE = new CharTypeDescriptor(); + public CharTypeDescriptor() { + SqlTypeDescriptorRegistry.INSTANCE.addDescriptor( this ); + } + @Override public int getSqlType() { return Types.CHAR; diff --git a/hibernate-core/src/main/java/org/hibernate/type/descriptor/sql/ClobTypeDescriptor.java b/hibernate-core/src/main/java/org/hibernate/type/descriptor/sql/ClobTypeDescriptor.java index 696df0e78e..2acadc8fbf 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/descriptor/sql/ClobTypeDescriptor.java +++ b/hibernate-core/src/main/java/org/hibernate/type/descriptor/sql/ClobTypeDescriptor.java @@ -40,11 +40,47 @@ import org.hibernate.type.descriptor.java.JavaTypeDescriptor; * Descriptor for {@link Types#CLOB CLOB} handling. * * @author Steve Ebersole + * @author Gail Badner */ public abstract class ClobTypeDescriptor implements SqlTypeDescriptor { + @Override + public int getSqlType() { + return Types.CLOB; + } + + @Override + public boolean canBeRemapped() { + return true; + } + + @Override + public ValueExtractor getExtractor(final JavaTypeDescriptor javaTypeDescriptor) { + return new BasicExtractor( javaTypeDescriptor, this ) { + @Override + protected X doExtract(ResultSet rs, String name, WrapperOptions options) throws SQLException { + return javaTypeDescriptor.wrap( rs.getClob( name ), options ); + } + + @Override + protected X doExtract(CallableStatement statement, int index, WrapperOptions options) throws SQLException { + return javaTypeDescriptor.wrap( statement.getClob( index ), options ); + } + }; + } + + + protected abstract BasicBinder getClobBinder(final JavaTypeDescriptor javaTypeDescriptor); + + public ValueBinder getBinder(final JavaTypeDescriptor javaTypeDescriptor) { + return getClobBinder( javaTypeDescriptor ); + } public static final ClobTypeDescriptor DEFAULT = new ClobTypeDescriptor() { + { + SqlTypeDescriptorRegistry.INSTANCE.addDescriptor( this ); + } + public BasicBinder getClobBinder(final JavaTypeDescriptor javaTypeDescriptor) { return new BasicBinder( javaTypeDescriptor, this ) { @Override @@ -87,32 +123,4 @@ public abstract class ClobTypeDescriptor implements SqlTypeDescriptor { } }; - protected abstract BasicBinder getClobBinder(final JavaTypeDescriptor javaTypeDescriptor); - - public ValueBinder getBinder(final JavaTypeDescriptor javaTypeDescriptor) { - return getClobBinder( javaTypeDescriptor ); - } - - public int getSqlType() { - return Types.CLOB; - } - - @Override - public boolean canBeRemapped() { - return true; - } - - public ValueExtractor getExtractor(final JavaTypeDescriptor javaTypeDescriptor) { - return new BasicExtractor( javaTypeDescriptor, this ) { - @Override - protected X doExtract(ResultSet rs, String name, WrapperOptions options) throws SQLException { - return javaTypeDescriptor.wrap( rs.getClob( name ), options ); - } - - @Override - protected X doExtract(CallableStatement statement, int index, WrapperOptions options) throws SQLException { - return javaTypeDescriptor.wrap( statement.getClob( index ), options ); - } - }; - } } diff --git a/hibernate-core/src/main/java/org/hibernate/type/descriptor/sql/DateTypeDescriptor.java b/hibernate-core/src/main/java/org/hibernate/type/descriptor/sql/DateTypeDescriptor.java index 4bb7a702e2..6c64e7c9e3 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/descriptor/sql/DateTypeDescriptor.java +++ b/hibernate-core/src/main/java/org/hibernate/type/descriptor/sql/DateTypeDescriptor.java @@ -43,6 +43,11 @@ import org.hibernate.type.descriptor.java.JavaTypeDescriptor; public class DateTypeDescriptor implements SqlTypeDescriptor { public static final DateTypeDescriptor INSTANCE = new DateTypeDescriptor(); + public DateTypeDescriptor() { + SqlTypeDescriptorRegistry.INSTANCE.addDescriptor( this ); + } + + @Override public int getSqlType() { return Types.DATE; } @@ -52,6 +57,7 @@ public class DateTypeDescriptor implements SqlTypeDescriptor { return true; } + @Override public ValueBinder getBinder(final JavaTypeDescriptor javaTypeDescriptor) { return new BasicBinder( javaTypeDescriptor, this ) { @Override @@ -61,6 +67,7 @@ public class DateTypeDescriptor implements SqlTypeDescriptor { }; } + @Override public ValueExtractor getExtractor(final JavaTypeDescriptor javaTypeDescriptor) { return new BasicExtractor( javaTypeDescriptor, this ) { @Override diff --git a/hibernate-core/src/main/java/org/hibernate/type/descriptor/sql/DecimalTypeDescriptor.java b/hibernate-core/src/main/java/org/hibernate/type/descriptor/sql/DecimalTypeDescriptor.java index 397c96f269..a168100960 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/descriptor/sql/DecimalTypeDescriptor.java +++ b/hibernate-core/src/main/java/org/hibernate/type/descriptor/sql/DecimalTypeDescriptor.java @@ -43,6 +43,11 @@ import org.hibernate.type.descriptor.java.JavaTypeDescriptor; public class DecimalTypeDescriptor implements SqlTypeDescriptor { public static final DecimalTypeDescriptor INSTANCE = new DecimalTypeDescriptor(); + public DecimalTypeDescriptor() { + SqlTypeDescriptorRegistry.INSTANCE.addDescriptor( this ); + } + + @Override public int getSqlType() { return Types.DECIMAL; } @@ -52,6 +57,7 @@ public class DecimalTypeDescriptor implements SqlTypeDescriptor { return true; } + @Override public ValueBinder getBinder(final JavaTypeDescriptor javaTypeDescriptor) { return new BasicBinder( javaTypeDescriptor, this ) { @Override @@ -61,6 +67,7 @@ public class DecimalTypeDescriptor implements SqlTypeDescriptor { }; } + @Override public ValueExtractor getExtractor(final JavaTypeDescriptor javaTypeDescriptor) { return new BasicExtractor( javaTypeDescriptor, this ) { @Override diff --git a/hibernate-core/src/main/java/org/hibernate/type/descriptor/sql/DoubleTypeDescriptor.java b/hibernate-core/src/main/java/org/hibernate/type/descriptor/sql/DoubleTypeDescriptor.java index 13b6a1a045..0adf43cf3d 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/descriptor/sql/DoubleTypeDescriptor.java +++ b/hibernate-core/src/main/java/org/hibernate/type/descriptor/sql/DoubleTypeDescriptor.java @@ -42,6 +42,11 @@ import org.hibernate.type.descriptor.java.JavaTypeDescriptor; public class DoubleTypeDescriptor implements SqlTypeDescriptor { public static final DoubleTypeDescriptor INSTANCE = new DoubleTypeDescriptor(); + public DoubleTypeDescriptor() { + SqlTypeDescriptorRegistry.INSTANCE.addDescriptor( this ); + } + + @Override public int getSqlType() { return Types.DOUBLE; } @@ -51,6 +56,7 @@ public class DoubleTypeDescriptor implements SqlTypeDescriptor { return true; } + @Override public ValueBinder getBinder(final JavaTypeDescriptor javaTypeDescriptor) { return new BasicBinder( javaTypeDescriptor, this ) { @Override @@ -60,6 +66,7 @@ public class DoubleTypeDescriptor implements SqlTypeDescriptor { }; } + @Override public ValueExtractor getExtractor(final JavaTypeDescriptor javaTypeDescriptor) { return new BasicExtractor( javaTypeDescriptor, this ) { @Override diff --git a/hibernate-core/src/main/java/org/hibernate/type/descriptor/sql/FloatTypeDescriptor.java b/hibernate-core/src/main/java/org/hibernate/type/descriptor/sql/FloatTypeDescriptor.java index fc6b63c6d3..4aade7e399 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/descriptor/sql/FloatTypeDescriptor.java +++ b/hibernate-core/src/main/java/org/hibernate/type/descriptor/sql/FloatTypeDescriptor.java @@ -22,6 +22,7 @@ * Boston, MA 02110-1301 USA */ package org.hibernate.type.descriptor.sql; + import java.sql.Types; /** @@ -32,6 +33,11 @@ import java.sql.Types; public class FloatTypeDescriptor extends RealTypeDescriptor { public static final FloatTypeDescriptor INSTANCE = new FloatTypeDescriptor(); + public FloatTypeDescriptor() { + SqlTypeDescriptorRegistry.INSTANCE.addDescriptor( this ); + } + + @Override public int getSqlType() { return Types.FLOAT; } diff --git a/hibernate-core/src/main/java/org/hibernate/type/descriptor/sql/IntegerTypeDescriptor.java b/hibernate-core/src/main/java/org/hibernate/type/descriptor/sql/IntegerTypeDescriptor.java index 8834e2d598..f93ed0c0ab 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/descriptor/sql/IntegerTypeDescriptor.java +++ b/hibernate-core/src/main/java/org/hibernate/type/descriptor/sql/IntegerTypeDescriptor.java @@ -42,6 +42,11 @@ import org.hibernate.type.descriptor.java.JavaTypeDescriptor; public class IntegerTypeDescriptor implements SqlTypeDescriptor { public static final IntegerTypeDescriptor INSTANCE = new IntegerTypeDescriptor(); + public IntegerTypeDescriptor() { + SqlTypeDescriptorRegistry.INSTANCE.addDescriptor( this ); + } + + @Override public int getSqlType() { return Types.INTEGER; } @@ -51,6 +56,7 @@ public class IntegerTypeDescriptor implements SqlTypeDescriptor { return true; } + @Override public ValueBinder getBinder(final JavaTypeDescriptor javaTypeDescriptor) { return new BasicBinder( javaTypeDescriptor, this ) { @Override @@ -60,6 +66,7 @@ public class IntegerTypeDescriptor implements SqlTypeDescriptor { }; } + @Override public ValueExtractor getExtractor(final JavaTypeDescriptor javaTypeDescriptor) { return new BasicExtractor( javaTypeDescriptor, this ) { @Override diff --git a/hibernate-core/src/main/java/org/hibernate/type/descriptor/sql/JdbcTypeFamilyInformation.java b/hibernate-core/src/main/java/org/hibernate/type/descriptor/sql/JdbcTypeFamilyInformation.java new file mode 100644 index 0000000000..670cf7b3b4 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/type/descriptor/sql/JdbcTypeFamilyInformation.java @@ -0,0 +1,75 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * Copyright (c) 2012, Red Hat Inc. or third-party contributors as + * indicated by the @author tags or express copyright attribution + * statements applied by the authors. All third-party contributions are + * distributed under license by Red Hat Inc. + * + * This copyrighted material is made available to anyone wishing to use, modify, + * copy, or redistribute it subject to the terms and conditions of the GNU + * Lesser General Public License, as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this distribution; if not, write to: + * Free Software Foundation, Inc. + * 51 Franklin Street, Fifth Floor + * Boston, MA 02110-1301 USA + */ +package org.hibernate.type.descriptor.sql; + +import java.sql.Types; +import java.util.concurrent.ConcurrentHashMap; + +/** + * Information pertaining to JDBC type families. + * + * @author Steve Ebersole + */ +public class JdbcTypeFamilyInformation { + public static final JdbcTypeFamilyInformation INSTANCE = new JdbcTypeFamilyInformation(); + + // todo : make Family non-enum so it can be expanded by Dialects? + + public static enum Family { + BINARY( Types.BINARY, Types.VARBINARY, Types.LONGVARBINARY ), + NUMERIC( Types.BIGINT, Types.DECIMAL, Types.DOUBLE, Types.FLOAT, Types.INTEGER, Types.NUMERIC, Types.REAL, Types.SMALLINT, Types.TINYINT ), + CHARACTER( Types.CHAR, Types.LONGNVARCHAR, Types.LONGVARCHAR, Types.NCHAR, Types.NVARCHAR, Types.VARCHAR ), + DATETIME( Types.DATE, Types.TIME, Types.TIMESTAMP ), + CLOB( Types.CLOB, Types.NCLOB ); + + private final int[] typeCodes; + + @SuppressWarnings("UnnecessaryBoxing") + private Family(int... typeCodes) { + this.typeCodes = typeCodes; + + for ( int typeCode : typeCodes ) { + JdbcTypeFamilyInformation.INSTANCE.typeCodeToFamilyMap.put( Integer.valueOf( typeCode ), this ); + } + } + + public int[] getTypeCodes() { + return typeCodes; + } + } + + private ConcurrentHashMap typeCodeToFamilyMap = new ConcurrentHashMap(); + + /** + * Will return {@code null} if no match is found. + * + * @param typeCode The JDBC type code. + * + * @return The family of datatypes the type code belongs to, or {@code null} if it belongs to no known families. + */ + @SuppressWarnings("UnnecessaryBoxing") + public Family locateJdbcTypeFamilyByTypeCode(int typeCode) { + return typeCodeToFamilyMap.get( Integer.valueOf( typeCode ) ); + } +} diff --git a/hibernate-core/src/main/java/org/hibernate/type/descriptor/sql/JdbcTypeJavaClassMappings.java b/hibernate-core/src/main/java/org/hibernate/type/descriptor/sql/JdbcTypeJavaClassMappings.java new file mode 100644 index 0000000000..05bf217c34 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/type/descriptor/sql/JdbcTypeJavaClassMappings.java @@ -0,0 +1,134 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * Copyright (c) 2012, Red Hat Inc. or third-party contributors as + * indicated by the @author tags or express copyright attribution + * statements applied by the authors. All third-party contributions are + * distributed under license by Red Hat Inc. + * + * This copyrighted material is made available to anyone wishing to use, modify, + * copy, or redistribute it subject to the terms and conditions of the GNU + * Lesser General Public License, as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this distribution; if not, write to: + * Free Software Foundation, Inc. + * 51 Franklin Street, Fifth Floor + * Boston, MA 02110-1301 USA + */ +package org.hibernate.type.descriptor.sql; + +import java.math.BigDecimal; +import java.sql.Blob; +import java.sql.Clob; +import java.sql.Date; +import java.sql.Ref; +import java.sql.Struct; +import java.sql.Time; +import java.sql.Timestamp; +import java.sql.Types; +import java.util.Calendar; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +import org.jboss.logging.Logger; + +import org.hibernate.mapping.Array; + +/** + * Presents recommended {@literal JDCB typecode <-> Java Class} mappings. Currently the recommendations + * contained here come from the JDBC spec itself, as outlined at + * Eventually, the plan is to have {@link org.hibernate.dialect.Dialect} contribute this information. + * + * @author Steve Ebersole + */ +public class JdbcTypeJavaClassMappings { + private static final Logger log = Logger.getLogger( JdbcTypeJavaClassMappings.class ); + + private static final ConcurrentHashMap javaClassToJdbcTypeCodeMap = buildJdbcJavaClassMappings(); + private static final ConcurrentHashMap jdbcTypeCodeToJavaClassMap = transpose( javaClassToJdbcTypeCodeMap ); + + public static final JdbcTypeJavaClassMappings INSTANCE = new JdbcTypeJavaClassMappings(); + + private JdbcTypeJavaClassMappings() { + } + + @SuppressWarnings("UnnecessaryUnboxing") + public int determineJdbcTypeCodeForJavaClass(Class cls) { + Integer typeCode = JdbcTypeJavaClassMappings.javaClassToJdbcTypeCodeMap.get( cls ); + if ( typeCode != null ) { + return typeCode.intValue(); + } + + int specialCode = cls.hashCode(); + log.debug( + "JDBC type code mapping not known for class [" + cls.getName() + "]; using custom code [" + specialCode + "]" + ); + return specialCode; + } + + @SuppressWarnings("UnnecessaryUnboxing") + public Class determineJavaClassForJdbcTypeCode(int typeCode) { + Class cls = jdbcTypeCodeToJavaClassMap.get( Integer.valueOf( typeCode ) ); + if ( cls != null ) { + return cls; + } + + log.debugf( + "Java Class mapping not known for JDBC type code [%s]; using java.lang.Object", + typeCode + ); + return Object.class; + } + + + // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + + private static ConcurrentHashMap buildJdbcJavaClassMappings() { + ConcurrentHashMap jdbcJavaClassMappings = new ConcurrentHashMap(); + + // these mappings are the ones outlined specifically in the spec + jdbcJavaClassMappings.put( String.class, Types.VARCHAR ); + jdbcJavaClassMappings.put( BigDecimal.class, Types.NUMERIC ); + jdbcJavaClassMappings.put( Boolean.class, Types.BIT ); + jdbcJavaClassMappings.put( Integer.class, Types.INTEGER ); + jdbcJavaClassMappings.put( Long.class, Types.BIGINT ); + jdbcJavaClassMappings.put( Float.class, Types.REAL ); + jdbcJavaClassMappings.put( Double.class, Types.DOUBLE ); + jdbcJavaClassMappings.put( byte[].class, Types.LONGVARBINARY ); + jdbcJavaClassMappings.put( Date.class, Types.DATE ); + jdbcJavaClassMappings.put( Time.class, Types.TIME ); + jdbcJavaClassMappings.put( Timestamp.class, Types.TIMESTAMP ); + jdbcJavaClassMappings.put( Blob.class, Types.BLOB ); + jdbcJavaClassMappings.put( Clob.class, Types.CLOB ); + jdbcJavaClassMappings.put( Array.class, Types.ARRAY ); + jdbcJavaClassMappings.put( Struct.class, Types.STRUCT ); + jdbcJavaClassMappings.put( Ref.class, Types.REF ); + jdbcJavaClassMappings.put( Class.class, Types.JAVA_OBJECT ); + + // additional "common sense" registrations + jdbcJavaClassMappings.put( Character.class, Types.CHAR ); + jdbcJavaClassMappings.put( char[].class, Types.VARCHAR ); + jdbcJavaClassMappings.put( Character[].class, Types.VARCHAR ); + jdbcJavaClassMappings.put( Byte[].class, Types.LONGVARBINARY ); + jdbcJavaClassMappings.put( Date.class, Types.TIMESTAMP ); + jdbcJavaClassMappings.put( Calendar.class, Types.TIMESTAMP ); + + return jdbcJavaClassMappings; + } + + private static ConcurrentHashMap transpose(ConcurrentHashMap javaClassToJdbcTypeCodeMap) { + final ConcurrentHashMap transposed = new ConcurrentHashMap(); + + for ( Map.Entry entry : javaClassToJdbcTypeCodeMap.entrySet() ) { + transposed.put( entry.getValue(), entry.getKey() ); + } + + return transposed; + } +} \ No newline at end of file diff --git a/hibernate-core/src/main/java/org/hibernate/type/descriptor/sql/LongNVarcharTypeDescriptor.java b/hibernate-core/src/main/java/org/hibernate/type/descriptor/sql/LongNVarcharTypeDescriptor.java new file mode 100644 index 0000000000..9cbd4db667 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/type/descriptor/sql/LongNVarcharTypeDescriptor.java @@ -0,0 +1,44 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * Copyright (c) 2012, Red Hat Inc. or third-party contributors as + * indicated by the @author tags or express copyright attribution + * statements applied by the authors. All third-party contributions are + * distributed under license by Red Hat Inc. + * + * This copyrighted material is made available to anyone wishing to use, modify, + * copy, or redistribute it subject to the terms and conditions of the GNU + * Lesser General Public License, as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this distribution; if not, write to: + * Free Software Foundation, Inc. + * 51 Franklin Street, Fifth Floor + * Boston, MA 02110-1301 USA + */ +package org.hibernate.type.descriptor.sql; + +import java.sql.Types; + +/** + * Descriptor for {@link Types#LONGNVARCHAR LONGNVARCHAR} handling. + * + * @author Steve Ebersole + */ +public class LongNVarcharTypeDescriptor extends NVarcharTypeDescriptor { + public static final LongNVarcharTypeDescriptor INSTANCE = new LongNVarcharTypeDescriptor(); + + public LongNVarcharTypeDescriptor() { + SqlTypeDescriptorRegistry.INSTANCE.addDescriptor( this ); + } + + @Override + public int getSqlType() { + return Types.LONGNVARCHAR; + } +} diff --git a/hibernate-core/src/main/java/org/hibernate/type/descriptor/sql/LongVarbinaryTypeDescriptor.java b/hibernate-core/src/main/java/org/hibernate/type/descriptor/sql/LongVarbinaryTypeDescriptor.java index 41fabafbd3..d338dd15a7 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/descriptor/sql/LongVarbinaryTypeDescriptor.java +++ b/hibernate-core/src/main/java/org/hibernate/type/descriptor/sql/LongVarbinaryTypeDescriptor.java @@ -22,6 +22,7 @@ * Boston, MA 02110-1301 USA */ package org.hibernate.type.descriptor.sql; + import java.sql.Types; /** @@ -32,6 +33,10 @@ import java.sql.Types; public class LongVarbinaryTypeDescriptor extends VarbinaryTypeDescriptor { public static final LongVarbinaryTypeDescriptor INSTANCE = new LongVarbinaryTypeDescriptor(); + public LongVarbinaryTypeDescriptor() { + SqlTypeDescriptorRegistry.INSTANCE.addDescriptor( this ); + } + @Override public int getSqlType() { return Types.LONGVARBINARY; diff --git a/hibernate-core/src/main/java/org/hibernate/type/descriptor/sql/LongVarcharTypeDescriptor.java b/hibernate-core/src/main/java/org/hibernate/type/descriptor/sql/LongVarcharTypeDescriptor.java index 7aa1666751..b7adc870a0 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/descriptor/sql/LongVarcharTypeDescriptor.java +++ b/hibernate-core/src/main/java/org/hibernate/type/descriptor/sql/LongVarcharTypeDescriptor.java @@ -22,6 +22,7 @@ * Boston, MA 02110-1301 USA */ package org.hibernate.type.descriptor.sql; + import java.sql.Types; /** @@ -32,6 +33,10 @@ import java.sql.Types; public class LongVarcharTypeDescriptor extends VarcharTypeDescriptor { public static final LongVarcharTypeDescriptor INSTANCE = new LongVarcharTypeDescriptor(); + public LongVarcharTypeDescriptor() { + SqlTypeDescriptorRegistry.INSTANCE.addDescriptor( this ); + } + @Override public int getSqlType() { return Types.LONGVARCHAR; diff --git a/hibernate-core/src/main/java/org/hibernate/type/descriptor/sql/NCharTypeDescriptor.java b/hibernate-core/src/main/java/org/hibernate/type/descriptor/sql/NCharTypeDescriptor.java new file mode 100644 index 0000000000..58b450699c --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/type/descriptor/sql/NCharTypeDescriptor.java @@ -0,0 +1,44 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * Copyright (c) 2012, Red Hat Inc. or third-party contributors as + * indicated by the @author tags or express copyright attribution + * statements applied by the authors. All third-party contributions are + * distributed under license by Red Hat Inc. + * + * This copyrighted material is made available to anyone wishing to use, modify, + * copy, or redistribute it subject to the terms and conditions of the GNU + * Lesser General Public License, as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this distribution; if not, write to: + * Free Software Foundation, Inc. + * 51 Franklin Street, Fifth Floor + * Boston, MA 02110-1301 USA + */ +package org.hibernate.type.descriptor.sql; + +import java.sql.Types; + +/** + * Descriptor for {@link Types#NCHAR NCHAR} handling. + * + * @author Steve Ebersole + */ +public class NCharTypeDescriptor extends NVarcharTypeDescriptor { + public static final NCharTypeDescriptor INSTANCE = new NCharTypeDescriptor(); + + public NCharTypeDescriptor() { + SqlTypeDescriptorRegistry.INSTANCE.addDescriptor( this ); + } + + @Override + public int getSqlType() { + return Types.NCHAR; + } +} diff --git a/hibernate-core/src/main/java/org/hibernate/type/descriptor/sql/NClobTypeDescriptor.java b/hibernate-core/src/main/java/org/hibernate/type/descriptor/sql/NClobTypeDescriptor.java new file mode 100644 index 0000000000..108927d929 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/type/descriptor/sql/NClobTypeDescriptor.java @@ -0,0 +1,125 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * Copyright (c) 2012, Red Hat Inc. or third-party contributors as + * indicated by the @author tags or express copyright attribution + * statements applied by the authors. All third-party contributions are + * distributed under license by Red Hat Inc. + * + * This copyrighted material is made available to anyone wishing to use, modify, + * copy, or redistribute it subject to the terms and conditions of the GNU + * Lesser General Public License, as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this distribution; if not, write to: + * Free Software Foundation, Inc. + * 51 Franklin Street, Fifth Floor + * Boston, MA 02110-1301 USA + */ +package org.hibernate.type.descriptor.sql; + +import java.sql.CallableStatement; +import java.sql.NClob; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.Types; + +import org.hibernate.type.descriptor.CharacterStream; +import org.hibernate.type.descriptor.ValueBinder; +import org.hibernate.type.descriptor.ValueExtractor; +import org.hibernate.type.descriptor.WrapperOptions; +import org.hibernate.type.descriptor.java.JavaTypeDescriptor; + +/** + * Descriptor for {@link Types#NCLOB NCLOB} handling. + * + * @author Steve Ebersole + * @author Gail Badner + */ +public abstract class NClobTypeDescriptor implements SqlTypeDescriptor { + @Override + public int getSqlType() { + return Types.NCLOB; + } + + @Override + public boolean canBeRemapped() { + return true; + } + + @Override + public ValueExtractor getExtractor(final JavaTypeDescriptor javaTypeDescriptor) { + return new BasicExtractor( javaTypeDescriptor, this ) { + @Override + protected X doExtract(ResultSet rs, String name, WrapperOptions options) throws SQLException { + return javaTypeDescriptor.wrap( rs.getNClob( name ), options ); + } + + @Override + protected X doExtract(CallableStatement statement, int index, WrapperOptions options) throws SQLException { + return javaTypeDescriptor.wrap( statement.getNClob( index ), options ); + } + }; + } + + + protected abstract BasicBinder getClobBinder(final JavaTypeDescriptor javaTypeDescriptor); + + public ValueBinder getBinder(final JavaTypeDescriptor javaTypeDescriptor) { + return getClobBinder( javaTypeDescriptor ); + } + + public static final ClobTypeDescriptor DEFAULT = + new ClobTypeDescriptor() { + { + SqlTypeDescriptorRegistry.INSTANCE.addDescriptor( this ); + } + + public BasicBinder getClobBinder(final JavaTypeDescriptor javaTypeDescriptor) { + return new BasicBinder( javaTypeDescriptor, this ) { + @Override + protected void doBind(PreparedStatement st, X value, int index, WrapperOptions options) throws SQLException { + if ( options.useStreamForLobBinding() ) { + STREAM_BINDING.getClobBinder( javaTypeDescriptor ).doBind( st, value, index, options ); + } + else { + CLOB_BINDING.getClobBinder( javaTypeDescriptor ).doBind( st, value, index, options ); + } + } + }; + } + }; + + public static final ClobTypeDescriptor CLOB_BINDING = + new ClobTypeDescriptor() { + public BasicBinder getClobBinder(final JavaTypeDescriptor javaTypeDescriptor) { + return new BasicBinder( javaTypeDescriptor, this ) { + @Override + protected void doBind(PreparedStatement st, X value, int index, WrapperOptions options) + throws SQLException { + st.setNClob( index, javaTypeDescriptor.unwrap( value, NClob.class, options ) ); + } + }; + } + }; + + public static final ClobTypeDescriptor STREAM_BINDING = + new ClobTypeDescriptor() { + public BasicBinder getClobBinder(final JavaTypeDescriptor javaTypeDescriptor) { + return new BasicBinder( javaTypeDescriptor, this ) { + @Override + protected void doBind(PreparedStatement st, X value, int index, WrapperOptions options) + throws SQLException { + final CharacterStream characterStream = javaTypeDescriptor.unwrap( value, CharacterStream.class, options ); + st.setCharacterStream( index, characterStream.getReader(), characterStream.getLength() ); + } + }; + } + }; +} diff --git a/hibernate-core/src/main/java/org/hibernate/type/descriptor/sql/NVarcharTypeDescriptor.java b/hibernate-core/src/main/java/org/hibernate/type/descriptor/sql/NVarcharTypeDescriptor.java new file mode 100644 index 0000000000..d1dfe1a363 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/type/descriptor/sql/NVarcharTypeDescriptor.java @@ -0,0 +1,83 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * Copyright (c) 2012, Red Hat Inc. or third-party contributors as + * indicated by the @author tags or express copyright attribution + * statements applied by the authors. All third-party contributions are + * distributed under license by Red Hat Inc. + * + * This copyrighted material is made available to anyone wishing to use, modify, + * copy, or redistribute it subject to the terms and conditions of the GNU + * Lesser General Public License, as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this distribution; if not, write to: + * Free Software Foundation, Inc. + * 51 Franklin Street, Fifth Floor + * Boston, MA 02110-1301 USA + */ +package org.hibernate.type.descriptor.sql; + +import java.sql.CallableStatement; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.Types; + +import org.hibernate.type.descriptor.ValueBinder; +import org.hibernate.type.descriptor.ValueExtractor; +import org.hibernate.type.descriptor.WrapperOptions; +import org.hibernate.type.descriptor.java.JavaTypeDescriptor; + +/** + * Descriptor for {@link Types#NVARCHAR NVARCHAR} handling. + * + * @author Steve Ebersole + */ +public class NVarcharTypeDescriptor implements SqlTypeDescriptor { + public static final NVarcharTypeDescriptor INSTANCE = new NVarcharTypeDescriptor(); + + public NVarcharTypeDescriptor() { + SqlTypeDescriptorRegistry.INSTANCE.addDescriptor( this ); + } + + @Override + public int getSqlType() { + return Types.NVARCHAR; + } + + @Override + public boolean canBeRemapped() { + return true; + } + + @Override + public ValueBinder getBinder(final JavaTypeDescriptor javaTypeDescriptor) { + return new BasicBinder( javaTypeDescriptor, this ) { + @Override + protected void doBind(PreparedStatement st, X value, int index, WrapperOptions options) throws SQLException { + st.setNString( index, javaTypeDescriptor.unwrap( value, String.class, options ) ); + } + }; + } + + @Override + public ValueExtractor getExtractor(final JavaTypeDescriptor javaTypeDescriptor) { + return new BasicExtractor( javaTypeDescriptor, this ) { + @Override + protected X doExtract(ResultSet rs, String name, WrapperOptions options) throws SQLException { + return javaTypeDescriptor.wrap( rs.getNString( name ), options ); + } + + @Override + protected X doExtract(CallableStatement statement, int index, WrapperOptions options) throws SQLException { + return javaTypeDescriptor.wrap( statement.getNString( index ), options ); + } + }; + } +} diff --git a/hibernate-core/src/main/java/org/hibernate/type/descriptor/sql/NumericTypeDescriptor.java b/hibernate-core/src/main/java/org/hibernate/type/descriptor/sql/NumericTypeDescriptor.java index a73384cfef..60a1eb871d 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/descriptor/sql/NumericTypeDescriptor.java +++ b/hibernate-core/src/main/java/org/hibernate/type/descriptor/sql/NumericTypeDescriptor.java @@ -22,6 +22,7 @@ * Boston, MA 02110-1301 USA */ package org.hibernate.type.descriptor.sql; + import java.sql.Types; /** @@ -32,6 +33,10 @@ import java.sql.Types; public class NumericTypeDescriptor extends DecimalTypeDescriptor { public static final NumericTypeDescriptor INSTANCE = new NumericTypeDescriptor(); + public NumericTypeDescriptor() { + SqlTypeDescriptorRegistry.INSTANCE.addDescriptor( this ); + } + @Override public int getSqlType() { return Types.NUMERIC; diff --git a/hibernate-core/src/main/java/org/hibernate/type/descriptor/sql/RealTypeDescriptor.java b/hibernate-core/src/main/java/org/hibernate/type/descriptor/sql/RealTypeDescriptor.java index 85c2a15559..eb859d51ae 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/descriptor/sql/RealTypeDescriptor.java +++ b/hibernate-core/src/main/java/org/hibernate/type/descriptor/sql/RealTypeDescriptor.java @@ -42,6 +42,11 @@ import org.hibernate.type.descriptor.java.JavaTypeDescriptor; public class RealTypeDescriptor implements SqlTypeDescriptor { public static final RealTypeDescriptor INSTANCE = new RealTypeDescriptor(); + public RealTypeDescriptor() { + SqlTypeDescriptorRegistry.INSTANCE.addDescriptor( this ); + } + + @Override public int getSqlType() { return Types.REAL; } @@ -51,6 +56,7 @@ public class RealTypeDescriptor implements SqlTypeDescriptor { return true; } + @Override public ValueBinder getBinder(final JavaTypeDescriptor javaTypeDescriptor) { return new BasicBinder( javaTypeDescriptor, this ) { @Override @@ -60,6 +66,7 @@ public class RealTypeDescriptor implements SqlTypeDescriptor { }; } + @Override public ValueExtractor getExtractor(final JavaTypeDescriptor javaTypeDescriptor) { return new BasicExtractor( javaTypeDescriptor, this ) { @Override diff --git a/hibernate-core/src/main/java/org/hibernate/type/descriptor/sql/SmallIntTypeDescriptor.java b/hibernate-core/src/main/java/org/hibernate/type/descriptor/sql/SmallIntTypeDescriptor.java index 0786ea242c..454bcc2dcc 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/descriptor/sql/SmallIntTypeDescriptor.java +++ b/hibernate-core/src/main/java/org/hibernate/type/descriptor/sql/SmallIntTypeDescriptor.java @@ -42,6 +42,11 @@ import org.hibernate.type.descriptor.java.JavaTypeDescriptor; public class SmallIntTypeDescriptor implements SqlTypeDescriptor { public static final SmallIntTypeDescriptor INSTANCE = new SmallIntTypeDescriptor(); + public SmallIntTypeDescriptor() { + SqlTypeDescriptorRegistry.INSTANCE.addDescriptor( this ); + } + + @Override public int getSqlType() { return Types.SMALLINT; } @@ -51,6 +56,7 @@ public class SmallIntTypeDescriptor implements SqlTypeDescriptor { return true; } + @Override public ValueBinder getBinder(final JavaTypeDescriptor javaTypeDescriptor) { return new BasicBinder( javaTypeDescriptor, this ) { @Override @@ -60,6 +66,7 @@ public class SmallIntTypeDescriptor implements SqlTypeDescriptor { }; } + @Override public ValueExtractor getExtractor(final JavaTypeDescriptor javaTypeDescriptor) { return new BasicExtractor( javaTypeDescriptor, this ) { @Override diff --git a/hibernate-core/src/main/java/org/hibernate/type/descriptor/sql/SqlTypeDescriptor.java b/hibernate-core/src/main/java/org/hibernate/type/descriptor/sql/SqlTypeDescriptor.java index de8ae5279b..a38371f48c 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/descriptor/sql/SqlTypeDescriptor.java +++ b/hibernate-core/src/main/java/org/hibernate/type/descriptor/sql/SqlTypeDescriptor.java @@ -31,6 +31,9 @@ import org.hibernate.type.descriptor.java.JavaTypeDescriptor; /** * Descriptor for the SQL/JDBC side of a value mapping. + *

+ * NOTE : Implementations should be registered with the {@link SqlTypeDescriptor}. The built-in Hibernate + * implementations register themselves on construction. * * @author Steve Ebersole */ @@ -52,7 +55,23 @@ public interface SqlTypeDescriptor extends Serializable { */ public boolean canBeRemapped(); + /** + * Get the binder (setting JDBC in-going parameter values) capable of handling values of the type described by the + * passed descriptor. + * + * @param javaTypeDescriptor The descriptor describing the types of Java values to be bound + * + * @return The appropriate binder. + */ public ValueBinder getBinder(JavaTypeDescriptor javaTypeDescriptor); + /** + * Get the extractor (pulling out-going values from JDBC objects) capable of handling values of the type described + * by the passed descriptor. + * + * @param javaTypeDescriptor The descriptor describing the types of Java values to be extracted + * + * @return The appropriate extractor + */ public ValueExtractor getExtractor(JavaTypeDescriptor javaTypeDescriptor); } diff --git a/hibernate-core/src/main/java/org/hibernate/type/descriptor/sql/SqlTypeDescriptorRegistry.java b/hibernate-core/src/main/java/org/hibernate/type/descriptor/sql/SqlTypeDescriptorRegistry.java new file mode 100644 index 0000000000..6744c3c770 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/type/descriptor/sql/SqlTypeDescriptorRegistry.java @@ -0,0 +1,152 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * Copyright (c) 2012, Red Hat Inc. or third-party contributors as + * indicated by the @author tags or express copyright attribution + * statements applied by the authors. All third-party contributions are + * distributed under license by Red Hat Inc. + * + * This copyrighted material is made available to anyone wishing to use, modify, + * copy, or redistribute it subject to the terms and conditions of the GNU + * Lesser General Public License, as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this distribution; if not, write to: + * Free Software Foundation, Inc. + * 51 Franklin Street, Fifth Floor + * Boston, MA 02110-1301 USA + */ +package org.hibernate.type.descriptor.sql; + +import java.io.Serializable; +import java.sql.CallableStatement; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.concurrent.ConcurrentHashMap; + +import org.jboss.logging.Logger; + +import org.hibernate.type.descriptor.JdbcTypeNameMapper; +import org.hibernate.type.descriptor.ValueBinder; +import org.hibernate.type.descriptor.ValueExtractor; +import org.hibernate.type.descriptor.WrapperOptions; +import org.hibernate.type.descriptor.java.JavaTypeDescriptor; + +/** + * Basically a map from JDBC type code (int) -> {@link SqlTypeDescriptor} + * + * @author Steve Ebersole + */ +public class SqlTypeDescriptorRegistry { + public static final SqlTypeDescriptorRegistry INSTANCE = new SqlTypeDescriptorRegistry(); + + private static final Logger log = Logger.getLogger( SqlTypeDescriptorRegistry.class ); + + private ConcurrentHashMap descriptorMap = new ConcurrentHashMap(); + + @SuppressWarnings("UnnecessaryBoxing") + public void addDescriptor(SqlTypeDescriptor sqlTypeDescriptor) { + descriptorMap.put( Integer.valueOf( sqlTypeDescriptor.getSqlType() ), sqlTypeDescriptor ); + } + + @SuppressWarnings("UnnecessaryBoxing") + public SqlTypeDescriptor getDescriptor(int jdbcTypeCode) { + SqlTypeDescriptor descriptor = descriptorMap.get( Integer.valueOf( jdbcTypeCode ) ); + if ( descriptor != null ) { + return descriptor; + } + + if ( JdbcTypeNameMapper.isStandardTypeCode( jdbcTypeCode ) ) { + log.debugf( + "A standard JDBC type code [%s] was not defined in SqlTypeDescriptorRegistry", + jdbcTypeCode + ); + } + + // see if the typecode is part of a known type family... + JdbcTypeFamilyInformation.Family family = JdbcTypeFamilyInformation.INSTANCE.locateJdbcTypeFamilyByTypeCode( jdbcTypeCode ); + if ( family != null ) { + for ( int potentialAlternateTypeCode : family.getTypeCodes() ) { + if ( potentialAlternateTypeCode != jdbcTypeCode ) { + final SqlTypeDescriptor potentialAlternateDescriptor = descriptorMap.get( Integer.valueOf( potentialAlternateTypeCode ) ); + if ( potentialAlternateDescriptor != null ) { + // todo : add a SqlTypeDescriptor.canBeAssignedFrom method... + return potentialAlternateDescriptor; + } + + if ( JdbcTypeNameMapper.isStandardTypeCode( potentialAlternateTypeCode ) ) { + log.debugf( + "A standard JDBC type code [%s] was not defined in SqlTypeDescriptorRegistry", + potentialAlternateTypeCode + ); + } + } + } + } + + // finally, create a new descriptor mapping to getObject/setObject for this type code... + final ObjectSqlTypeDescriptor fallBackDescriptor = new ObjectSqlTypeDescriptor( jdbcTypeCode ); + addDescriptor( fallBackDescriptor ); + return fallBackDescriptor; + } + + public static class ObjectSqlTypeDescriptor implements SqlTypeDescriptor { + private final int jdbcTypeCode; + + public ObjectSqlTypeDescriptor(int jdbcTypeCode) { + this.jdbcTypeCode = jdbcTypeCode; + } + + @Override + public int getSqlType() { + return jdbcTypeCode; + } + + @Override + public boolean canBeRemapped() { + return true; + } + + @Override + public ValueBinder getBinder(JavaTypeDescriptor javaTypeDescriptor) { + if ( Serializable.class.isAssignableFrom( javaTypeDescriptor.getJavaTypeClass() ) ) { + return VarbinaryTypeDescriptor.INSTANCE.getBinder( javaTypeDescriptor ); + } + + return new BasicBinder( javaTypeDescriptor, this ) { + @Override + protected void doBind(PreparedStatement st, X value, int index, WrapperOptions options) + throws SQLException { + st.setObject( index, value, jdbcTypeCode ); + } + }; + } + + @Override + @SuppressWarnings("unchecked") + public ValueExtractor getExtractor(JavaTypeDescriptor javaTypeDescriptor) { + if ( Serializable.class.isAssignableFrom( javaTypeDescriptor.getJavaTypeClass() ) ) { + return VarbinaryTypeDescriptor.INSTANCE.getExtractor( javaTypeDescriptor ); + } + + return new BasicExtractor( javaTypeDescriptor, this ) { + @Override + protected Object doExtract(ResultSet rs, String name, WrapperOptions options) throws SQLException { + return rs.getObject( name ); + } + + @Override + protected Object doExtract(CallableStatement statement, int index, WrapperOptions options) + throws SQLException { + return statement.getObject( index ); + } + }; + } + } +} diff --git a/hibernate-core/src/main/java/org/hibernate/type/descriptor/sql/TimeTypeDescriptor.java b/hibernate-core/src/main/java/org/hibernate/type/descriptor/sql/TimeTypeDescriptor.java index 2b1b1b034c..5a279d8177 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/descriptor/sql/TimeTypeDescriptor.java +++ b/hibernate-core/src/main/java/org/hibernate/type/descriptor/sql/TimeTypeDescriptor.java @@ -43,6 +43,11 @@ import org.hibernate.type.descriptor.java.JavaTypeDescriptor; public class TimeTypeDescriptor implements SqlTypeDescriptor { public static final TimeTypeDescriptor INSTANCE = new TimeTypeDescriptor(); + public TimeTypeDescriptor() { + SqlTypeDescriptorRegistry.INSTANCE.addDescriptor( this ); + } + + @Override public int getSqlType() { return Types.TIME; } @@ -52,6 +57,7 @@ public class TimeTypeDescriptor implements SqlTypeDescriptor { return true; } + @Override public ValueBinder getBinder(final JavaTypeDescriptor javaTypeDescriptor) { return new BasicBinder( javaTypeDescriptor, this ) { @Override @@ -61,6 +67,7 @@ public class TimeTypeDescriptor implements SqlTypeDescriptor { }; } + @Override public ValueExtractor getExtractor(final JavaTypeDescriptor javaTypeDescriptor) { return new BasicExtractor( javaTypeDescriptor, this ) { @Override diff --git a/hibernate-core/src/main/java/org/hibernate/type/descriptor/sql/TimestampTypeDescriptor.java b/hibernate-core/src/main/java/org/hibernate/type/descriptor/sql/TimestampTypeDescriptor.java index 4401becfc8..bf36bd66f2 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/descriptor/sql/TimestampTypeDescriptor.java +++ b/hibernate-core/src/main/java/org/hibernate/type/descriptor/sql/TimestampTypeDescriptor.java @@ -43,6 +43,11 @@ import org.hibernate.type.descriptor.java.JavaTypeDescriptor; public class TimestampTypeDescriptor implements SqlTypeDescriptor { public static final TimestampTypeDescriptor INSTANCE = new TimestampTypeDescriptor(); + public TimestampTypeDescriptor() { + SqlTypeDescriptorRegistry.INSTANCE.addDescriptor( this ); + } + + @Override public int getSqlType() { return Types.TIMESTAMP; } @@ -52,6 +57,7 @@ public class TimestampTypeDescriptor implements SqlTypeDescriptor { return true; } + @Override public ValueBinder getBinder(final JavaTypeDescriptor javaTypeDescriptor) { return new BasicBinder( javaTypeDescriptor, this ) { @Override @@ -61,6 +67,7 @@ public class TimestampTypeDescriptor implements SqlTypeDescriptor { }; } + @Override public ValueExtractor getExtractor(final JavaTypeDescriptor javaTypeDescriptor) { return new BasicExtractor( javaTypeDescriptor, this ) { @Override diff --git a/hibernate-core/src/main/java/org/hibernate/type/descriptor/sql/TinyIntTypeDescriptor.java b/hibernate-core/src/main/java/org/hibernate/type/descriptor/sql/TinyIntTypeDescriptor.java index 8418a28526..a5955c73f0 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/descriptor/sql/TinyIntTypeDescriptor.java +++ b/hibernate-core/src/main/java/org/hibernate/type/descriptor/sql/TinyIntTypeDescriptor.java @@ -45,6 +45,11 @@ import org.hibernate.type.descriptor.java.JavaTypeDescriptor; public class TinyIntTypeDescriptor implements SqlTypeDescriptor { public static final TinyIntTypeDescriptor INSTANCE = new TinyIntTypeDescriptor(); + public TinyIntTypeDescriptor() { + SqlTypeDescriptorRegistry.INSTANCE.addDescriptor( this ); + } + + @Override public int getSqlType() { return Types.TINYINT; } @@ -54,6 +59,7 @@ public class TinyIntTypeDescriptor implements SqlTypeDescriptor { return true; } + @Override public ValueBinder getBinder(final JavaTypeDescriptor javaTypeDescriptor) { return new BasicBinder( javaTypeDescriptor, this ) { @Override @@ -63,6 +69,7 @@ public class TinyIntTypeDescriptor implements SqlTypeDescriptor { }; } + @Override public ValueExtractor getExtractor(final JavaTypeDescriptor javaTypeDescriptor) { return new BasicExtractor( javaTypeDescriptor, this ) { @Override diff --git a/hibernate-core/src/main/java/org/hibernate/type/descriptor/sql/VarbinaryTypeDescriptor.java b/hibernate-core/src/main/java/org/hibernate/type/descriptor/sql/VarbinaryTypeDescriptor.java index 7c7d28a807..8ed51fd617 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/descriptor/sql/VarbinaryTypeDescriptor.java +++ b/hibernate-core/src/main/java/org/hibernate/type/descriptor/sql/VarbinaryTypeDescriptor.java @@ -42,6 +42,10 @@ import org.hibernate.type.descriptor.java.JavaTypeDescriptor; public class VarbinaryTypeDescriptor implements SqlTypeDescriptor { public static final VarbinaryTypeDescriptor INSTANCE = new VarbinaryTypeDescriptor(); + public VarbinaryTypeDescriptor() { + SqlTypeDescriptorRegistry.INSTANCE.addDescriptor( this ); + } + public int getSqlType() { return Types.VARBINARY; } diff --git a/hibernate-core/src/main/java/org/hibernate/type/descriptor/sql/VarcharTypeDescriptor.java b/hibernate-core/src/main/java/org/hibernate/type/descriptor/sql/VarcharTypeDescriptor.java index 50d5f3c5a9..edb98150d9 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/descriptor/sql/VarcharTypeDescriptor.java +++ b/hibernate-core/src/main/java/org/hibernate/type/descriptor/sql/VarcharTypeDescriptor.java @@ -42,6 +42,11 @@ import org.hibernate.type.descriptor.java.JavaTypeDescriptor; public class VarcharTypeDescriptor implements SqlTypeDescriptor { public static final VarcharTypeDescriptor INSTANCE = new VarcharTypeDescriptor(); + public VarcharTypeDescriptor() { + SqlTypeDescriptorRegistry.INSTANCE.addDescriptor( this ); + } + + @Override public int getSqlType() { return Types.VARCHAR; } @@ -51,6 +56,7 @@ public class VarcharTypeDescriptor implements SqlTypeDescriptor { return true; } + @Override public ValueBinder getBinder(final JavaTypeDescriptor javaTypeDescriptor) { return new BasicBinder( javaTypeDescriptor, this ) { @Override @@ -60,6 +66,7 @@ public class VarcharTypeDescriptor implements SqlTypeDescriptor { }; } + @Override public ValueExtractor getExtractor(final JavaTypeDescriptor javaTypeDescriptor) { return new BasicExtractor( javaTypeDescriptor, this ) { @Override diff --git a/hibernate-core/src/test/java/org/hibernate/type/AttributeConverterTest.java b/hibernate-core/src/test/java/org/hibernate/type/AttributeConverterTest.java new file mode 100644 index 0000000000..4405018174 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/type/AttributeConverterTest.java @@ -0,0 +1,143 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * Copyright (c) 2012, Red Hat Inc. or third-party contributors as + * indicated by the @author tags or express copyright attribution + * statements applied by the authors. All third-party contributions are + * distributed under license by Red Hat Inc. + * + * This copyrighted material is made available to anyone wishing to use, modify, + * copy, or redistribute it subject to the terms and conditions of the GNU + * Lesser General Public License, as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this distribution; if not, write to: + * Free Software Foundation, Inc. + * 51 Franklin Street, Fifth Floor + * Boston, MA 02110-1301 USA + */ +package org.hibernate.type; + +import javax.persistence.AttributeConverter; +import javax.persistence.Convert; +import javax.persistence.Converter; +import javax.persistence.Entity; +import javax.persistence.Id; +import java.sql.Clob; +import java.sql.Types; + +import org.hibernate.IrrelevantEntity; +import org.hibernate.cfg.AttributeConverterDefinition; +import org.hibernate.cfg.Configuration; +import org.hibernate.mapping.PersistentClass; +import org.hibernate.mapping.Property; +import org.hibernate.mapping.SimpleValue; +import org.hibernate.type.descriptor.java.StringTypeDescriptor; + +import org.junit.Test; + +import org.hibernate.testing.junit4.BaseUnitTestCase; + +import static org.hibernate.testing.junit4.ExtraAssertions.assertTyping; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertSame; + +/** + * Tests the principle of adding "AttributeConverter" to the mix of {@link Type} resolution + * + * @author Steve Ebersole + */ +public class AttributeConverterTest extends BaseUnitTestCase { + @Test + public void testBasicOperation() { + Configuration cfg = new Configuration(); + SimpleValue simpleValue = new SimpleValue( cfg.createMappings() ); + simpleValue.setJpaAttributeConverterDefinition( + new AttributeConverterDefinition( new StringClobConverter(), true ) + ); + simpleValue.setTypeUsingReflection( IrrelevantEntity.class.getName(), "name" ); + + Type type = simpleValue.getType(); + assertNotNull( type ); + assertTyping( BasicType.class, type ); + AbstractStandardBasicType basicType = assertTyping( AbstractStandardBasicType.class, type ); + assertSame( StringTypeDescriptor.INSTANCE, basicType.getJavaTypeDescriptor() ); + assertEquals( Types.CLOB, basicType.getSqlTypeDescriptor().getSqlType() ); + } + + @Test + public void testNormalOperation() { + Configuration cfg = new Configuration(); + cfg.addAttributeConverter( StringClobConverter.class, true ); + cfg.addAnnotatedClass( Tester.class ); + cfg.addAnnotatedClass( Tester2.class ); + cfg.buildMappings(); + + { + PersistentClass tester = cfg.getClassMapping( Tester.class.getName() ); + Property nameProp = tester.getProperty( "name" ); + SimpleValue nameValue = (SimpleValue) nameProp.getValue(); + Type type = nameValue.getType(); + assertNotNull( type ); + assertTyping( BasicType.class, type ); + AbstractStandardBasicType basicType = assertTyping( AbstractStandardBasicType.class, type ); + assertSame( StringTypeDescriptor.INSTANCE, basicType.getJavaTypeDescriptor() ); + assertEquals( Types.CLOB, basicType.getSqlTypeDescriptor().getSqlType() ); + } + + { + PersistentClass tester = cfg.getClassMapping( Tester2.class.getName() ); + Property nameProp = tester.getProperty( "name" ); + SimpleValue nameValue = (SimpleValue) nameProp.getValue(); + Type type = nameValue.getType(); + assertNotNull( type ); + assertTyping( BasicType.class, type ); + AbstractStandardBasicType basicType = assertTyping( AbstractStandardBasicType.class, type ); + assertSame( StringTypeDescriptor.INSTANCE, basicType.getJavaTypeDescriptor() ); + assertEquals( Types.VARCHAR, basicType.getSqlTypeDescriptor().getSqlType() ); + } + } + + + @Entity + public static class Tester { + @Id + private Long id; + private String name; + } + + @Entity + public static class Tester2 { + @Id + private Long id; + @Convert(disableConversion = true) + private String name; + } + + @Entity + public static class Tester3 { + @Id + private Long id; + @org.hibernate.annotations.Type( type = "string" ) + private String name; + } + + @Converter( autoApply = true ) + public static class StringClobConverter implements AttributeConverter { + @Override + public Clob convertToDatabaseColumn(String attribute) { + return null; + } + + @Override + public String convertToEntityAttribute(Clob dbData) { + return null; + } + } +} diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/junit4/ExtraAssertions.java b/hibernate-testing/src/main/java/org/hibernate/testing/junit4/ExtraAssertions.java index d8256e86c3..f80b801194 100644 --- a/hibernate-testing/src/main/java/org/hibernate/testing/junit4/ExtraAssertions.java +++ b/hibernate-testing/src/main/java/org/hibernate/testing/junit4/ExtraAssertions.java @@ -37,4 +37,18 @@ public class ExtraAssertions { ); } } + + @SuppressWarnings("unchecked") + public static T assertTyping(Class expectedType, Object value) { + if ( ! expectedType.isInstance( value ) ) { + Assert.fail( + String.format( + "Expecting value of type [%s], but found [%s]", + expectedType.getName(), + value == null ? "" : value + ) + ); + } + return (T) value; + } }