From 322b67f7c63e9c794030b2684729cde217b57360 Mon Sep 17 00:00:00 2001 From: Gavin King Date: Wed, 22 Jan 2025 15:23:01 +0100 Subject: [PATCH 1/2] HHH-18998 pass annotation to constructor of UserType --- .../hibernate/boot/model/TypeDefinition.java | 43 +++++- .../boot/model/internal/BasicValueBinder.java | 70 ++++++--- .../process/internal/UserTypeResolution.java | 10 +- .../source/internal/hbm/ModelBinder.java | 10 +- .../org/hibernate/mapping/BasicValue.java | 65 ++++++-- .../org/hibernate/mapping/SimpleValue.java | 13 +- .../mapping/basic/bitset/BitSetHelper.java | 2 +- .../basic/bitset/BitSetMetaUserTypeTest.java | 139 ++++++++++++++++++ .../basic/bitset/BitSetUserTypeTest.java | 2 +- .../basic/bitset/MetaUserTypeTest.java | 119 +++++++++++++++ 10 files changed, 429 insertions(+), 44 deletions(-) create mode 100644 hibernate-core/src/test/java/org/hibernate/orm/test/mapping/basic/bitset/BitSetMetaUserTypeTest.java create mode 100644 hibernate-core/src/test/java/org/hibernate/orm/test/mapping/basic/bitset/MetaUserTypeTest.java diff --git a/hibernate-core/src/main/java/org/hibernate/boot/model/TypeDefinition.java b/hibernate-core/src/main/java/org/hibernate/boot/model/TypeDefinition.java index 4cf7117bc119..07571fdedfb4 100644 --- a/hibernate-core/src/main/java/org/hibernate/boot/model/TypeDefinition.java +++ b/hibernate-core/src/main/java/org/hibernate/boot/model/TypeDefinition.java @@ -5,6 +5,9 @@ package org.hibernate.boot.model; import java.io.Serializable; +import java.lang.annotation.Annotation; +import java.lang.reflect.Constructor; +import java.lang.reflect.InvocationTargetException; import java.sql.Types; import java.util.Arrays; import java.util.Collections; @@ -98,25 +101,27 @@ public Map getParameters() { public BasicValue.Resolution resolve( Map localConfigParameters, + Annotation typeAnnotation, MutabilityPlan explicitMutabilityPlan, MetadataBuildingContext context, JdbcTypeIndicators indicators) { if ( CollectionHelper.isEmpty( localConfigParameters ) ) { // we can use the re-usable resolution... if ( reusableResolution == null ) { - reusableResolution = createResolution( name, Collections.emptyMap(), indicators, context ); + reusableResolution = createResolution( name, Collections.emptyMap(), typeAnnotation, indicators, context ); } return reusableResolution; } else { final String name = this.name + ":" + NAME_COUNTER.getAndIncrement(); - return createResolution( name, localConfigParameters, indicators, context ); + return createResolution( name, localConfigParameters, typeAnnotation, indicators, context ); } } private BasicValue.Resolution createResolution( String name, Map usageSiteProperties, + Annotation typeAnnotation, JdbcTypeIndicators indicators, MetadataBuildingContext context) { return createResolution( @@ -124,6 +129,7 @@ private BasicValue.Resolution createResolution( typeImplementorClass, parameters, usageSiteProperties, + typeAnnotation, indicators, context ); @@ -134,20 +140,21 @@ private static BasicValue.Resolution createResolution( Class typeImplementorClass, Map parameters, Map usageSiteProperties, + Annotation typeAnnotation, JdbcTypeIndicators indicators, MetadataBuildingContext context) { final BootstrapContext bootstrapContext = context.getBootstrapContext(); final TypeConfiguration typeConfiguration = bootstrapContext.getTypeConfiguration(); - final BeanInstanceProducer instanceProducer = bootstrapContext.getCustomTypeProducer(); + final boolean isKnownType = Type.class.isAssignableFrom( typeImplementorClass ) || UserType.class.isAssignableFrom( typeImplementorClass ); // support for AttributeConverter would be nice too if ( isKnownType ) { + final T typeInstance = - instantiateType( bootstrapContext.getServiceRegistry(), context.getBuildingOptions(), - name, typeImplementorClass, instanceProducer ); + instantiateType( name, typeImplementorClass, typeAnnotation, context, bootstrapContext ); if ( typeInstance instanceof TypeConfigurationAware configurationAware ) { configurationAware.setTypeConfiguration( typeConfiguration ); @@ -167,7 +174,7 @@ private static BasicValue.Resolution createResolution( @SuppressWarnings("unchecked") final UserType userType = (UserType) typeInstance; final CustomType customType = new CustomType<>( userType, typeConfiguration ); - return new UserTypeResolution<>( customType, null, combinedTypeParameters ); + return new UserTypeResolution<>( customType, null, combinedTypeParameters, typeAnnotation ); } if ( typeInstance instanceof BasicType ) { @@ -224,6 +231,28 @@ public MutabilityPlan getMutabilityPlan() { return resolveLegacyCases( typeImplementorClass, indicators, typeConfiguration ); } + private static T instantiateType( + String name, Class typeImplementorClass, Annotation typeAnnotation, + MetadataBuildingContext context, BootstrapContext bootstrapContext) { + if ( typeAnnotation != null ) { + // attempt to instantiate it with the annotation as a constructor argument + try { + final Constructor constructor = typeImplementorClass.getConstructor( typeAnnotation.annotationType() ); + constructor.setAccessible( true ); + return constructor.newInstance( typeAnnotation ); + } + catch ( NoSuchMethodException ignored ) { + // no such constructor, instantiate it the old way + } + catch (InvocationTargetException|InstantiationException|IllegalAccessException e) { + throw new org.hibernate.InstantiationException( "Could not instantiate custom type", typeImplementorClass, e ); + } + } + + return instantiateType( bootstrapContext.getServiceRegistry(), context.getBuildingOptions(), + name, typeImplementorClass, bootstrapContext.getCustomTypeProducer() ); + } + private static BasicValue.Resolution resolveLegacyCases( Class typeImplementorClass, JdbcTypeIndicators indicators, TypeConfiguration typeConfiguration) { final BasicType legacyType; @@ -316,12 +345,14 @@ public static BasicValue.Resolution createLocalResolution( String name, Class typeImplementorClass, Map localTypeParams, + Annotation typeAnnotation, MetadataBuildingContext buildingContext) { return createResolution( name + ':' + NAME_COUNTER.getAndIncrement(), typeImplementorClass, localTypeParams, null, + typeAnnotation, buildingContext.getBootstrapContext().getTypeConfiguration().getCurrentBaseSqlTypeIndicators(), buildingContext ); diff --git a/hibernate-core/src/main/java/org/hibernate/boot/model/internal/BasicValueBinder.java b/hibernate-core/src/main/java/org/hibernate/boot/model/internal/BasicValueBinder.java index 2ac51779c7a7..fd1792d23e58 100644 --- a/hibernate-core/src/main/java/org/hibernate/boot/model/internal/BasicValueBinder.java +++ b/hibernate-core/src/main/java/org/hibernate/boot/model/internal/BasicValueBinder.java @@ -109,7 +109,8 @@ public enum Kind { // in-flight info private Class> explicitCustomType; - private Map explicitLocalTypeParams; + private Map explicitLocalCustomTypeParams; + private Annotation explicitCustomTypeAnnotation; private Function explicitJdbcTypeAccess; private Function explicitJavaTypeAccess; @@ -324,9 +325,11 @@ public void setType( isLob = value.hasDirectAnnotationUsage( Lob.class ); } + final SourceModelBuildingContext context = getSourceModelContext(); + if ( getDialect().getNationalizationSupport() == NationalizationSupport.EXPLICIT ) { isNationalized = buildingContext.getBuildingOptions().useNationalizedCharacterData() - || value.locateAnnotationUsage( Nationalized.class, getSourceModelContext() ) != null; + || value.locateAnnotationUsage( Nationalized.class, context ) != null; } if ( converterDescriptor != null ) { @@ -334,20 +337,21 @@ public void setType( } final Class> userTypeImpl = - kind.mappingAccess.customType( value, getSourceModelContext() ); + kind.mappingAccess.customType( value, context ); if ( userTypeImpl != null ) { - applyExplicitType( userTypeImpl, - kind.mappingAccess.customTypeParameters( value, getSourceModelContext() ) ); + this.explicitCustomType = userTypeImpl; + this.explicitLocalCustomTypeParams = kind.mappingAccess.customTypeParameters( value, context ); + this.explicitCustomTypeAnnotation = kind.mappingAccess.customTypeAnnotation( value, context ); // An explicit custom UserType has top precedence when we get to BasicValue resolution. return; } else if ( modelClassDetails != null ) { - final ClassDetails rawClassDetails = modelClassDetails.determineRawClass(); - final Class basicClass = rawClassDetails.toJavaClass(); final Class> registeredUserTypeImpl = - getMetadataCollector().findRegisteredUserType( basicClass ); + getMetadataCollector() + .findRegisteredUserType( modelClassDetails.determineRawClass().toJavaClass() ); if ( registeredUserTypeImpl != null ) { - applyExplicitType( registeredUserTypeImpl, emptyMap() ); + this.explicitCustomType = registeredUserTypeImpl; + this.explicitLocalCustomTypeParams = emptyMap(); return; } } @@ -380,11 +384,6 @@ else if ( modelClassDetails != null ) { } - private void applyExplicitType(Class> impl, Map params) { - this.explicitCustomType = impl; - this.explicitLocalTypeParams = params; - } - private void prepareCollectionId(MemberDetails attribute) { final CollectionId collectionIdAnn = attribute.getDirectAnnotationUsage( CollectionId.class ); if ( collectionIdAnn == null ) { @@ -1277,7 +1276,7 @@ else if ( aggregateComponent != null ) { } public void fillSimpleValue() { - basicValue.setExplicitTypeParams( explicitLocalTypeParams ); + basicValue.setExplicitTypeParams( explicitLocalCustomTypeParams ); // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // todo (6.0) : we are dropping support for @Type and @TypeDef from annotations @@ -1293,6 +1292,10 @@ public void fillSimpleValue() { basicValue.setTypeParameters( createDynamicParameterizedTypeParameters() ); } + if ( explicitCustomType != null ) { + basicValue.setTypeAnnotation( explicitCustomTypeAnnotation ); + } + if ( converterDescriptor != null ) { basicValue.setJpaAttributeConverterDescriptor( converterDescriptor ); } @@ -1364,8 +1367,8 @@ private Map createDynamicParameterizedTypeParameters() { parameters.put( DynamicParameterizedType.ACCESS_TYPE, accessType.getType() ); } - if ( explicitLocalTypeParams != null ) { - parameters.putAll( explicitLocalTypeParams ); + if ( explicitLocalCustomTypeParams != null ) { + parameters.putAll( explicitLocalCustomTypeParams ); } return parameters; @@ -1377,6 +1380,7 @@ private Map createDynamicParameterizedTypeParameters() { private interface BasicMappingAccess { Class> customType(MemberDetails attribute, SourceModelBuildingContext context); Map customTypeParameters(MemberDetails attribute, SourceModelBuildingContext context); + Annotation customTypeAnnotation(MemberDetails attribute, SourceModelBuildingContext context); } private static class ValueMappingAccess implements BasicMappingAccess { @@ -1393,6 +1397,12 @@ public Map customTypeParameters(MemberDetails attribute, SourceMo final Type customType = attribute.locateAnnotationUsage( Type.class, context ); return customType == null ? null : extractParameterMap( customType.parameters() ); } + + @Override + public Annotation customTypeAnnotation(MemberDetails attribute, SourceModelBuildingContext context) { + final List metaAnnotated = attribute.getMetaAnnotated( Type.class, context ); + return metaAnnotated.size() == 1 ? metaAnnotated.get( 0 ) : null; + } } private static class AnyDiscriminatorMappingAccess implements BasicMappingAccess { @@ -1407,6 +1417,11 @@ public Class> customType(MemberDetails attribute, SourceMo public Map customTypeParameters(MemberDetails attribute, SourceModelBuildingContext context) { return emptyMap(); } + + @Override + public Annotation customTypeAnnotation(MemberDetails attribute, SourceModelBuildingContext context) { + return null; + } } private static class AnyKeyMappingAccess implements BasicMappingAccess { @@ -1421,6 +1436,11 @@ public Class> customType(MemberDetails attribute, SourceMo public Map customTypeParameters(MemberDetails attribute, SourceModelBuildingContext context) { return emptyMap(); } + + @Override + public Annotation customTypeAnnotation(MemberDetails attribute, SourceModelBuildingContext context) { + return null; + } } private static class MapKeyMappingAccess implements BasicMappingAccess { @@ -1439,6 +1459,12 @@ public Map customTypeParameters(MemberDetails attribute, SourceMo return customType == null ? null : extractParameterMap( customType.parameters() ); } + + @Override + public Annotation customTypeAnnotation(MemberDetails attribute, SourceModelBuildingContext context) { + final List metaAnnotated = attribute.getMetaAnnotated( MapKeyType.class, context ); + return metaAnnotated.size() == 1 ? metaAnnotated.get( 0 ) : null; + } } private static class CollectionIdMappingAccess implements BasicMappingAccess { @@ -1455,7 +1481,12 @@ public Class> customType(MemberDetails attribute, SourceMo public Map customTypeParameters(MemberDetails attribute, SourceModelBuildingContext context) { final CollectionIdType customType = attribute.locateAnnotationUsage( CollectionIdType.class, context ); return customType == null ? null : extractParameterMap( customType.parameters() ); + } + @Override + public Annotation customTypeAnnotation(MemberDetails attribute, SourceModelBuildingContext context) { + final List metaAnnotated = attribute.getMetaAnnotated( CollectionIdType.class, context ); + return metaAnnotated.size() == 1 ? metaAnnotated.get( 0 ) : null; } } @@ -1471,6 +1502,11 @@ public Class> customType(MemberDetails attribute, SourceMo public Map customTypeParameters(MemberDetails attribute, SourceModelBuildingContext context) { return emptyMap(); } + + @Override + public Annotation customTypeAnnotation(MemberDetails attribute, SourceModelBuildingContext context) { + return null; + } } private static AnnotatedJoinColumns convertToJoinColumns(AnnotatedColumns columns, MetadataBuildingContext context) { diff --git a/hibernate-core/src/main/java/org/hibernate/boot/model/process/internal/UserTypeResolution.java b/hibernate-core/src/main/java/org/hibernate/boot/model/process/internal/UserTypeResolution.java index e7fa2969901d..85408d51173c 100644 --- a/hibernate-core/src/main/java/org/hibernate/boot/model/process/internal/UserTypeResolution.java +++ b/hibernate-core/src/main/java/org/hibernate/boot/model/process/internal/UserTypeResolution.java @@ -4,6 +4,7 @@ */ package org.hibernate.boot.model.process.internal; +import java.lang.annotation.Annotation; import java.util.Properties; import org.hibernate.mapping.BasicValue; @@ -27,13 +28,16 @@ public class UserTypeResolution implements BasicValue.Resolution { * and builds its own :( */ private final Properties combinedTypeParameters; + private final Annotation typeAnnotation; public UserTypeResolution( CustomType userTypeAdapter, MutabilityPlan explicitMutabilityPlan, - Properties combinedTypeParameters) { + Properties combinedTypeParameters, + Annotation typeAnnotation) { this.userTypeAdapter = userTypeAdapter; this.combinedTypeParameters = combinedTypeParameters; + this.typeAnnotation = typeAnnotation; this.mutabilityPlan = explicitMutabilityPlan != null ? explicitMutabilityPlan : new UserTypeMutabilityPlanAdapter<>( userTypeAdapter.getUserType() ); @@ -77,6 +81,10 @@ public Properties getCombinedTypeParameters() { return combinedTypeParameters; } + public Annotation getTypeAnnotation() { + return typeAnnotation; + } + @Override public JdbcMapping getJdbcMapping() { return userTypeAdapter; diff --git a/hibernate-core/src/main/java/org/hibernate/boot/model/source/internal/hbm/ModelBinder.java b/hibernate-core/src/main/java/org/hibernate/boot/model/source/internal/hbm/ModelBinder.java index 49a4aa98eae3..15873b6f8a59 100644 --- a/hibernate-core/src/main/java/org/hibernate/boot/model/source/internal/hbm/ModelBinder.java +++ b/hibernate-core/src/main/java/org/hibernate/boot/model/source/internal/hbm/ModelBinder.java @@ -2185,8 +2185,9 @@ private void bindAny( } else { discriminatorTypeName = StandardBasicTypes.STRING.getName(); - discriminatorType = metadataBuildingContext.getBootstrapContext().getTypeConfiguration().getBasicTypeRegistry() - .resolve( StandardBasicTypes.STRING ); + discriminatorType = + metadataBuildingContext.getBootstrapContext().getTypeConfiguration().getBasicTypeRegistry() + .resolve( StandardBasicTypes.STRING ); } anyBinding.setMetaType( discriminatorTypeName ); @@ -2196,7 +2197,9 @@ private void bindAny( anyMapping.getDiscriminatorSource().getValueMappings().forEach( (discriminatorValueString, entityName) -> { try { - final Object discriminatorValue = discriminatorType.getJavaTypeDescriptor().fromString( discriminatorValueString ); + final Object discriminatorValue = + discriminatorType.getJavaTypeDescriptor() + .fromString( discriminatorValueString ); discriminatorValueToEntityNameMap.put( discriminatorValue, entityName ); } catch (Exception e) { @@ -2258,6 +2261,7 @@ private BasicType resolveExplicitlyNamedAnyDiscriminatorType( final BasicValue.Resolution resolution = typeDefinition.resolve( parameters, null, + null, metadataBuildingContext, typeConfiguration.getCurrentBaseSqlTypeIndicators() ); diff --git a/hibernate-core/src/main/java/org/hibernate/mapping/BasicValue.java b/hibernate-core/src/main/java/org/hibernate/mapping/BasicValue.java index 54455555a866..4163dd8b6f6c 100644 --- a/hibernate-core/src/main/java/org/hibernate/mapping/BasicValue.java +++ b/hibernate-core/src/main/java/org/hibernate/mapping/BasicValue.java @@ -4,6 +4,9 @@ */ package org.hibernate.mapping; +import java.lang.annotation.Annotation; +import java.lang.reflect.Constructor; +import java.lang.reflect.InvocationTargetException; import java.util.HashMap; import java.util.Map; import java.util.Properties; @@ -434,7 +437,9 @@ private Resolution buildResolution(Properties typeParameters) { explicitMutabilityPlanAccess, getAttributeConverterDescriptor(), typeParameters, + getTypeAnnotation(), this::setTypeParameters, + this::setTypeAnnotation, this, getBuildingContext() ); @@ -632,14 +637,14 @@ private Resolution resolution(BasicJavaType explicitJavaType, JavaType jav throw new MappingException( "Unable to determine JavaType to use : " + this ); } - if ( basicJavaType instanceof BasicJavaType - && ( !basicJavaType.getJavaTypeClass().isEnum() || enumerationStyle == null ) ) { + final MetadataBuildingContext context = getBuildingContext(); + if ( basicJavaType instanceof BasicJavaType castType + && ( !basicJavaType.getJavaTypeClass().isEnum() || enumerationStyle == null ) ) { final TypeDefinition autoAppliedTypeDef = - getBuildingContext().getTypeDefinitionRegistry() - .resolveAutoApplied( (BasicJavaType) basicJavaType ); + context.getTypeDefinitionRegistry().resolveAutoApplied( castType ); if ( autoAppliedTypeDef != null ) { log.debug("BasicValue resolution matched auto-applied type-definition"); - return autoAppliedTypeDef.resolve( getTypeParameters(), null, getBuildingContext(), this ); + return autoAppliedTypeDef.resolve( getTypeParameters(), null, null, context, this ); } } @@ -654,7 +659,7 @@ private Resolution resolution(BasicJavaType explicitJavaType, JavaType jav getColumn(), ownerName, propertyName, - getBuildingContext() + context ); } @@ -807,7 +812,9 @@ private static Resolution interpretExplicitlyNamedType( Function explicitMutabilityPlanAccess, ConverterDescriptor converterDescriptor, Map localTypeParams, + Annotation typeAnnotation, Consumer combinedParameterConsumer, + Consumer annotationConsumer, JdbcTypeIndicators stdIndicators, MetadataBuildingContext context) { @@ -890,12 +897,14 @@ public TypeConfiguration getTypeConfiguration() { if ( typeDefinition != null ) { final Resolution resolution = typeDefinition.resolve( localTypeParams, + typeAnnotation, explicitMutabilityPlanAccess != null ? explicitMutabilityPlanAccess.apply( typeConfiguration ) : null, context, stdIndicators ); + annotationConsumer.accept( resolution.getTypeAnnotation() ); combinedParameterConsumer.accept( resolution.getCombinedTypeParameters() ); return resolution; } @@ -911,6 +920,7 @@ public TypeConfiguration getTypeConfiguration() { context.getTypeDefinitionRegistry().register( implicitDefinition ); return implicitDefinition.resolve( localTypeParams, + typeAnnotation, explicitMutabilityPlanAccess != null ? explicitMutabilityPlanAccess.apply( typeConfiguration ) : null, @@ -919,7 +929,7 @@ public TypeConfiguration getTypeConfiguration() { ); } - return TypeDefinition.createLocalResolution( name, typeNamedClass, localTypeParams, context ); + return TypeDefinition.createLocalResolution( name, typeNamedClass, localTypeParams, typeAnnotation, context ); } catch (ClassLoadingException e) { // allow the exception below to trigger @@ -1021,13 +1031,16 @@ public void setExplicitCustomType(Class> explicitCustomTyp throw new UnsupportedOperationException( "Unsupported attempt to set an explicit-custom-type when value is already resolved" ); } else { + final Properties typeProperties = getCustomTypeProperties(); + final Annotation typeAnnotation = getTypeAnnotation(); resolution = new UserTypeResolution<>( new CustomType<>( - getConfiguredUserTypeBean( explicitCustomType, getCustomTypeProperties() ), + getConfiguredUserTypeBean( explicitCustomType, typeProperties, typeAnnotation ), getTypeConfiguration() ), null, - getCustomTypeProperties() + typeProperties, + typeAnnotation ); } } @@ -1044,11 +1057,9 @@ private Properties getCustomTypeProperties() { return properties; } - private UserType getConfiguredUserTypeBean(Class> explicitCustomType, Properties properties) { - final UserType typeInstance = - getBuildingContext().getBuildingOptions().isAllowExtensionsInCdi() - ? getUserTypeBean( explicitCustomType, properties ).getBeanInstance() - : FallbackBeanInstanceProducer.INSTANCE.produceBeanInstance( explicitCustomType ); + private UserType getConfiguredUserTypeBean( + Class> explicitCustomType, Properties properties, Annotation typeAnnotation) { + final UserType typeInstance = instantiateUserType( explicitCustomType, properties, typeAnnotation ); if ( typeInstance instanceof TypeConfigurationAware configurationAware ) { configurationAware.setTypeConfiguration( getTypeConfiguration() ); @@ -1069,6 +1080,28 @@ private UserType getConfiguredUserTypeBean(Class> expli return typeInstance; } + private > T instantiateUserType( + Class customType, Properties properties, Annotation typeAnnotation) { + if ( typeAnnotation != null ) { + // attempt to instantiate it with the annotation as a constructor argument + try { + final Constructor constructor = customType.getDeclaredConstructor( typeAnnotation.annotationType() ); + constructor.setAccessible( true ); + return constructor.newInstance( typeAnnotation ); + } + catch ( NoSuchMethodException ignored ) { + // no such constructor, instantiate it the old way + } + catch (InvocationTargetException | InstantiationException | IllegalAccessException e) { + throw new org.hibernate.InstantiationException( "Could not instantiate custom type", customType, e ); + } + } + + return getBuildingContext().getBuildingOptions().isAllowExtensionsInCdi() + ? getUserTypeBean( customType, properties ).getBeanInstance() + : FallbackBeanInstanceProducer.INSTANCE.produceBeanInstance( customType ); + } + private ManagedBean getUserTypeBean(Class explicitCustomType, Properties properties) { final BeanInstanceProducer producer = getBuildingContext().getBootstrapContext().getCustomTypeProducer(); if ( isNotEmpty( properties ) ) { @@ -1146,6 +1179,10 @@ default Properties getCombinedTypeParameters() { return null; } + default Annotation getTypeAnnotation() { + return null; + } + JdbcMapping getJdbcMapping(); /** 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 49c3b8558cc9..af87803e0aa4 100644 --- a/hibernate-core/src/main/java/org/hibernate/mapping/SimpleValue.java +++ b/hibernate-core/src/main/java/org/hibernate/mapping/SimpleValue.java @@ -86,6 +86,7 @@ public abstract class SimpleValue implements KeyValue { private String typeName; private Properties typeParameters; + private Annotation typeAnnotation; private boolean isVersion; private boolean isNationalized; private boolean isLob; @@ -125,6 +126,7 @@ protected SimpleValue(SimpleValue original) { this.partitionKey = original.partitionKey; this.typeName = original.typeName; this.typeParameters = original.typeParameters == null ? null : new Properties( original.typeParameters ); + this.typeAnnotation = original.typeAnnotation; this.isVersion = original.isVersion; this.isNationalized = original.isNationalized; this.isLob = original.isLob; @@ -800,11 +802,19 @@ public void setTypeParameters(Map parameters) { } } + public void setTypeAnnotation(Annotation typeAnnotation) { + this.typeAnnotation = typeAnnotation; + } + public Properties getTypeParameters() { return typeParameters; } - public void copyTypeFrom( SimpleValue sourceValue ) { + public Annotation getTypeAnnotation() { + return typeAnnotation; + } + + public void copyTypeFrom(SimpleValue sourceValue ) { setTypeName( sourceValue.getTypeName() ); setTypeParameters( sourceValue.getTypeParameters() ); @@ -826,6 +836,7 @@ public boolean isSame(SimpleValue other) { return Objects.equals( columns, other.columns ) && Objects.equals( typeName, other.typeName ) && Objects.equals( typeParameters, other.typeParameters ) + && Objects.equals( typeAnnotation, other.typeAnnotation ) && Objects.equals( table, other.table ) && Objects.equals( foreignKeyName, other.foreignKeyName ) && Objects.equals( foreignKeyDefinition, other.foreignKeyDefinition ); diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/basic/bitset/BitSetHelper.java b/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/basic/bitset/BitSetHelper.java index e3fc44e776e8..f9f423b48898 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/basic/bitset/BitSetHelper.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/basic/bitset/BitSetHelper.java @@ -16,7 +16,7 @@ public class BitSetHelper { public static String bitSetToString(BitSet bitSet) { StringBuilder builder = new StringBuilder(); for (long token : bitSet.toLongArray()) { - if (builder.length() > 0) { + if ( !builder.isEmpty() ) { builder.append(DELIMITER); } builder.append(Long.toString(token, 2)); diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/basic/bitset/BitSetMetaUserTypeTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/basic/bitset/BitSetMetaUserTypeTest.java new file mode 100644 index 000000000000..88d25cfdb63d --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/basic/bitset/BitSetMetaUserTypeTest.java @@ -0,0 +1,139 @@ +/* + * SPDX-License-Identifier: LGPL-2.1-or-later + * Copyright Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.orm.test.mapping.basic.bitset; + +import jakarta.persistence.Column; +import jakarta.persistence.ColumnResult; +import jakarta.persistence.ConstructorResult; +import jakarta.persistence.Entity; +import jakarta.persistence.Id; +import jakarta.persistence.NamedNativeQuery; +import jakarta.persistence.SqlResultSetMapping; +import jakarta.persistence.Table; +import org.hibernate.annotations.Type; +import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase; +import org.junit.Test; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.util.BitSet; + +import static org.hibernate.testing.transaction.TransactionUtil.doInHibernate; +import static org.junit.Assert.assertEquals; + + +public class BitSetMetaUserTypeTest extends BaseCoreFunctionalTestCase { + + @Override + protected Class[] getAnnotatedClasses() { + return new Class[] { + Product.class + }; + } + + @Test + public void test() { + + BitSet bitSet = BitSet.valueOf(new long[] {1, 2, 3}); + + doInHibernate(this::sessionFactory, session -> { + Product product = new Product(); + product.setId(1); + product.setBitSet(bitSet); + session.persist(product); + }); + + doInHibernate(this::sessionFactory, session -> { + Product product = session.get(Product.class, 1); + assertEquals(bitSet, product.getBitSet()); + }); + } + + @Test + public void testNativeQuery() { + BitSet bitSet = BitSet.valueOf(new long[] {1, 2, 3}); + + doInHibernate(this::sessionFactory, session -> { + Product product = new Product(); + product.setId(1); + product.setBitSet(bitSet); + session.persist(product); + }); + + doInHibernate(this::sessionFactory, session -> { + Product product = session.createNamedQuery( + "find_person_by_bitset", Product.class) + .setParameter("id", 1L) + .getSingleResult(); + + assertEquals(bitSet, product.getBitSet()); + }); + } + + @Override + protected boolean isCleanupTestDataRequired() { + return true; + } + + @Type(BitSetUserType.class) + @Retention(RetentionPolicy.RUNTIME) + @interface BitSetType {} + + @NamedNativeQuery( + name = "find_person_by_bitset", + query = + "SELECT " + + " pr.id AS \"pr.id\", " + + " pr.bitset_col AS \"pr.bitset\" " + + "FROM products pr " + + "WHERE pr.id = :id", + resultSetMapping = "Person" + ) + @SqlResultSetMapping( + name = "Person", + classes = @ConstructorResult( + targetClass = Product.class, + columns = { + @ColumnResult(name = "pr.id"), + @ColumnResult(name = "pr.bitset", type = BitSetUserType.class) + } + ) + ) + @Entity(name = "Product") + @Table(name = "products") + public static class Product { + + @Id + private Integer id; + + @BitSetType + @Column(name = "bitset_col") + private BitSet bitSet; + + public Product() { + } + + public Product(Number id, BitSet bitSet) { + this.id = id.intValue(); + this.bitSet = bitSet; + } + + public Integer getId() { + return id; + } + + public void setId(Integer id) { + this.id = id; + } + + public BitSet getBitSet() { + return bitSet; + } + + public void setBitSet(BitSet bitSet) { + this.bitSet = bitSet; + } + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/basic/bitset/BitSetUserTypeTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/basic/bitset/BitSetUserTypeTest.java index f8a514ba9a7c..c221783884d4 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/basic/bitset/BitSetUserTypeTest.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/basic/bitset/BitSetUserTypeTest.java @@ -77,7 +77,7 @@ public void testNativeQuery() { }); doInHibernate(this::sessionFactory, session -> { - Product product = (Product) session.createNamedQuery( + Product product = session.createNamedQuery( "find_person_by_bitset", Product.class) .setParameter("id", 1L) .getSingleResult(); diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/basic/bitset/MetaUserTypeTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/basic/bitset/MetaUserTypeTest.java new file mode 100644 index 000000000000..879423d589f1 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/basic/bitset/MetaUserTypeTest.java @@ -0,0 +1,119 @@ +/* + * SPDX-License-Identifier: LGPL-2.1-or-later + * Copyright Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.orm.test.mapping.basic.bitset; + +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.Id; +import org.hibernate.annotations.Type; +import org.hibernate.testing.orm.junit.EntityManagerFactoryScope; +import org.hibernate.testing.orm.junit.Jpa; +import org.hibernate.type.descriptor.WrapperOptions; +import org.hibernate.usertype.UserType; +import org.junit.jupiter.api.Test; + +import java.io.Serializable; +import java.lang.annotation.Retention; +import java.lang.annotation.Target; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.time.Period; +import java.util.Objects; + +import static java.lang.annotation.ElementType.FIELD; +import static java.lang.annotation.ElementType.METHOD; +import static java.lang.annotation.RetentionPolicy.RUNTIME; +import static java.sql.Types.VARCHAR; + +@Jpa(annotatedClasses = MetaUserTypeTest.Thing.class) +public class MetaUserTypeTest { + + @Test void test(EntityManagerFactoryScope scope) { + scope.inTransaction( em -> { + Period p = Period.of( 1, 2, 3 ); + Thing thing = new Thing(); + thing.period = p; + em.persist( thing ); + } ); + } + + @Entity static class Thing { + @Id @GeneratedValue + long id; + @TimePeriod + Period period; + } + + @Type(PeriodType. class) + @Target({METHOD, FIELD}) + @Retention(RUNTIME) + public @interface TimePeriod {} + + static class PeriodType implements UserType { + + PeriodType(TimePeriod timePeriod) { + + } + + @Override + public int getSqlType() { + return VARCHAR; + } + + @Override + public Class returnedClass() { + return Period.class; + } + + @Override + public boolean equals(Period x, Period y) { + return Objects.equals(x, y); + } + + @Override + public int hashCode(Period x) { + return x.hashCode(); + } + + @Override + public Period nullSafeGet(ResultSet rs, int position, WrapperOptions options) + throws SQLException { + String string = rs.getString(position); + return rs. wasNull() ? null : Period.parse(string); + } + + @Override + public void nullSafeSet(PreparedStatement st, Period value, int index, WrapperOptions options) + throws SQLException { + if ( value == null ) { + st.setNull(index, VARCHAR); + } + else { + st.setString(index, value.toString()); + } + } + + @Override + public boolean isMutable() { + return false; + } + + @Override + public Period deepCopy(Period value) { + return value; // Period is immutable + } + + @Override + public Serializable disassemble(Period period) { + return period; // Period is immutable + } + + @Override + public Period assemble(Serializable cached, Object owner) { + return (Period) cached; // Period is immutable + } + } +} From 182f0adea6d67d9b82dc42b5b515f3baf1ff757a Mon Sep 17 00:00:00 2001 From: Gavin King Date: Wed, 22 Jan 2025 15:35:04 +0100 Subject: [PATCH 2/2] HHH-18998 remove added code which is probably not necessary (need to check with @sebersole) --- .../hibernate/boot/model/TypeDefinition.java | 42 +++---------------- .../process/internal/UserTypeResolution.java | 10 +---- .../source/internal/hbm/ModelBinder.java | 1 - .../org/hibernate/mapping/BasicValue.java | 18 ++------ 4 files changed, 10 insertions(+), 61 deletions(-) diff --git a/hibernate-core/src/main/java/org/hibernate/boot/model/TypeDefinition.java b/hibernate-core/src/main/java/org/hibernate/boot/model/TypeDefinition.java index 07571fdedfb4..356dc3db3234 100644 --- a/hibernate-core/src/main/java/org/hibernate/boot/model/TypeDefinition.java +++ b/hibernate-core/src/main/java/org/hibernate/boot/model/TypeDefinition.java @@ -5,12 +5,8 @@ package org.hibernate.boot.model; import java.io.Serializable; -import java.lang.annotation.Annotation; -import java.lang.reflect.Constructor; -import java.lang.reflect.InvocationTargetException; import java.sql.Types; import java.util.Arrays; -import java.util.Collections; import java.util.Map; import java.util.Objects; import java.util.Properties; @@ -42,6 +38,7 @@ import org.hibernate.type.spi.TypeConfigurationAware; import org.hibernate.usertype.UserType; +import static java.util.Collections.emptyMap; import static org.hibernate.boot.model.process.internal.InferredBasicValueResolver.resolveSqlTypeIndicators; import static org.hibernate.mapping.MappingHelper.injectParameters; @@ -101,27 +98,25 @@ public Map getParameters() { public BasicValue.Resolution resolve( Map localConfigParameters, - Annotation typeAnnotation, MutabilityPlan explicitMutabilityPlan, MetadataBuildingContext context, JdbcTypeIndicators indicators) { if ( CollectionHelper.isEmpty( localConfigParameters ) ) { // we can use the re-usable resolution... if ( reusableResolution == null ) { - reusableResolution = createResolution( name, Collections.emptyMap(), typeAnnotation, indicators, context ); + reusableResolution = createResolution( name, emptyMap(), indicators, context ); } return reusableResolution; } else { final String name = this.name + ":" + NAME_COUNTER.getAndIncrement(); - return createResolution( name, localConfigParameters, typeAnnotation, indicators, context ); + return createResolution( name, localConfigParameters, indicators, context ); } } private BasicValue.Resolution createResolution( String name, Map usageSiteProperties, - Annotation typeAnnotation, JdbcTypeIndicators indicators, MetadataBuildingContext context) { return createResolution( @@ -129,7 +124,6 @@ private BasicValue.Resolution createResolution( typeImplementorClass, parameters, usageSiteProperties, - typeAnnotation, indicators, context ); @@ -140,7 +134,6 @@ private static BasicValue.Resolution createResolution( Class typeImplementorClass, Map parameters, Map usageSiteProperties, - Annotation typeAnnotation, JdbcTypeIndicators indicators, MetadataBuildingContext context) { final BootstrapContext bootstrapContext = context.getBootstrapContext(); @@ -154,7 +147,8 @@ private static BasicValue.Resolution createResolution( if ( isKnownType ) { final T typeInstance = - instantiateType( name, typeImplementorClass, typeAnnotation, context, bootstrapContext ); + instantiateType( bootstrapContext.getServiceRegistry(), context.getBuildingOptions(), + name, typeImplementorClass, bootstrapContext.getCustomTypeProducer() ); if ( typeInstance instanceof TypeConfigurationAware configurationAware ) { configurationAware.setTypeConfiguration( typeConfiguration ); @@ -174,7 +168,7 @@ private static BasicValue.Resolution createResolution( @SuppressWarnings("unchecked") final UserType userType = (UserType) typeInstance; final CustomType customType = new CustomType<>( userType, typeConfiguration ); - return new UserTypeResolution<>( customType, null, combinedTypeParameters, typeAnnotation ); + return new UserTypeResolution<>( customType, null, combinedTypeParameters ); } if ( typeInstance instanceof BasicType ) { @@ -231,28 +225,6 @@ public MutabilityPlan getMutabilityPlan() { return resolveLegacyCases( typeImplementorClass, indicators, typeConfiguration ); } - private static T instantiateType( - String name, Class typeImplementorClass, Annotation typeAnnotation, - MetadataBuildingContext context, BootstrapContext bootstrapContext) { - if ( typeAnnotation != null ) { - // attempt to instantiate it with the annotation as a constructor argument - try { - final Constructor constructor = typeImplementorClass.getConstructor( typeAnnotation.annotationType() ); - constructor.setAccessible( true ); - return constructor.newInstance( typeAnnotation ); - } - catch ( NoSuchMethodException ignored ) { - // no such constructor, instantiate it the old way - } - catch (InvocationTargetException|InstantiationException|IllegalAccessException e) { - throw new org.hibernate.InstantiationException( "Could not instantiate custom type", typeImplementorClass, e ); - } - } - - return instantiateType( bootstrapContext.getServiceRegistry(), context.getBuildingOptions(), - name, typeImplementorClass, bootstrapContext.getCustomTypeProducer() ); - } - private static BasicValue.Resolution resolveLegacyCases( Class typeImplementorClass, JdbcTypeIndicators indicators, TypeConfiguration typeConfiguration) { final BasicType legacyType; @@ -345,14 +317,12 @@ public static BasicValue.Resolution createLocalResolution( String name, Class typeImplementorClass, Map localTypeParams, - Annotation typeAnnotation, MetadataBuildingContext buildingContext) { return createResolution( name + ':' + NAME_COUNTER.getAndIncrement(), typeImplementorClass, localTypeParams, null, - typeAnnotation, buildingContext.getBootstrapContext().getTypeConfiguration().getCurrentBaseSqlTypeIndicators(), buildingContext ); diff --git a/hibernate-core/src/main/java/org/hibernate/boot/model/process/internal/UserTypeResolution.java b/hibernate-core/src/main/java/org/hibernate/boot/model/process/internal/UserTypeResolution.java index 85408d51173c..e7fa2969901d 100644 --- a/hibernate-core/src/main/java/org/hibernate/boot/model/process/internal/UserTypeResolution.java +++ b/hibernate-core/src/main/java/org/hibernate/boot/model/process/internal/UserTypeResolution.java @@ -4,7 +4,6 @@ */ package org.hibernate.boot.model.process.internal; -import java.lang.annotation.Annotation; import java.util.Properties; import org.hibernate.mapping.BasicValue; @@ -28,16 +27,13 @@ public class UserTypeResolution implements BasicValue.Resolution { * and builds its own :( */ private final Properties combinedTypeParameters; - private final Annotation typeAnnotation; public UserTypeResolution( CustomType userTypeAdapter, MutabilityPlan explicitMutabilityPlan, - Properties combinedTypeParameters, - Annotation typeAnnotation) { + Properties combinedTypeParameters) { this.userTypeAdapter = userTypeAdapter; this.combinedTypeParameters = combinedTypeParameters; - this.typeAnnotation = typeAnnotation; this.mutabilityPlan = explicitMutabilityPlan != null ? explicitMutabilityPlan : new UserTypeMutabilityPlanAdapter<>( userTypeAdapter.getUserType() ); @@ -81,10 +77,6 @@ public Properties getCombinedTypeParameters() { return combinedTypeParameters; } - public Annotation getTypeAnnotation() { - return typeAnnotation; - } - @Override public JdbcMapping getJdbcMapping() { return userTypeAdapter; diff --git a/hibernate-core/src/main/java/org/hibernate/boot/model/source/internal/hbm/ModelBinder.java b/hibernate-core/src/main/java/org/hibernate/boot/model/source/internal/hbm/ModelBinder.java index 15873b6f8a59..8680d730dd1b 100644 --- a/hibernate-core/src/main/java/org/hibernate/boot/model/source/internal/hbm/ModelBinder.java +++ b/hibernate-core/src/main/java/org/hibernate/boot/model/source/internal/hbm/ModelBinder.java @@ -2261,7 +2261,6 @@ private BasicType resolveExplicitlyNamedAnyDiscriminatorType( final BasicValue.Resolution resolution = typeDefinition.resolve( parameters, null, - null, metadataBuildingContext, typeConfiguration.getCurrentBaseSqlTypeIndicators() ); diff --git a/hibernate-core/src/main/java/org/hibernate/mapping/BasicValue.java b/hibernate-core/src/main/java/org/hibernate/mapping/BasicValue.java index 4163dd8b6f6c..fc089e152d4e 100644 --- a/hibernate-core/src/main/java/org/hibernate/mapping/BasicValue.java +++ b/hibernate-core/src/main/java/org/hibernate/mapping/BasicValue.java @@ -437,9 +437,7 @@ private Resolution buildResolution(Properties typeParameters) { explicitMutabilityPlanAccess, getAttributeConverterDescriptor(), typeParameters, - getTypeAnnotation(), this::setTypeParameters, - this::setTypeAnnotation, this, getBuildingContext() ); @@ -644,7 +642,7 @@ private Resolution resolution(BasicJavaType explicitJavaType, JavaType jav context.getTypeDefinitionRegistry().resolveAutoApplied( castType ); if ( autoAppliedTypeDef != null ) { log.debug("BasicValue resolution matched auto-applied type-definition"); - return autoAppliedTypeDef.resolve( getTypeParameters(), null, null, context, this ); + return autoAppliedTypeDef.resolve( getTypeParameters(), null, context, this ); } } @@ -812,9 +810,7 @@ private static Resolution interpretExplicitlyNamedType( Function explicitMutabilityPlanAccess, ConverterDescriptor converterDescriptor, Map localTypeParams, - Annotation typeAnnotation, Consumer combinedParameterConsumer, - Consumer annotationConsumer, JdbcTypeIndicators stdIndicators, MetadataBuildingContext context) { @@ -897,14 +893,12 @@ public TypeConfiguration getTypeConfiguration() { if ( typeDefinition != null ) { final Resolution resolution = typeDefinition.resolve( localTypeParams, - typeAnnotation, explicitMutabilityPlanAccess != null ? explicitMutabilityPlanAccess.apply( typeConfiguration ) : null, context, stdIndicators ); - annotationConsumer.accept( resolution.getTypeAnnotation() ); combinedParameterConsumer.accept( resolution.getCombinedTypeParameters() ); return resolution; } @@ -920,7 +914,6 @@ public TypeConfiguration getTypeConfiguration() { context.getTypeDefinitionRegistry().register( implicitDefinition ); return implicitDefinition.resolve( localTypeParams, - typeAnnotation, explicitMutabilityPlanAccess != null ? explicitMutabilityPlanAccess.apply( typeConfiguration ) : null, @@ -929,7 +922,7 @@ public TypeConfiguration getTypeConfiguration() { ); } - return TypeDefinition.createLocalResolution( name, typeNamedClass, localTypeParams, typeAnnotation, context ); + return TypeDefinition.createLocalResolution( name, typeNamedClass, localTypeParams, context ); } catch (ClassLoadingException e) { // allow the exception below to trigger @@ -1039,8 +1032,7 @@ public void setExplicitCustomType(Class> explicitCustomTyp getTypeConfiguration() ), null, - typeProperties, - typeAnnotation + typeProperties ); } } @@ -1179,10 +1171,6 @@ default Properties getCombinedTypeParameters() { return null; } - default Annotation getTypeAnnotation() { - return null; - } - JdbcMapping getJdbcMapping(); /**