Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

HHH-18998 annotation-based config for UserType #9660

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@
import java.io.Serializable;
import java.sql.Types;
import java.util.Arrays;
import java.util.Collections;
import java.util.Map;
import java.util.Objects;
import java.util.Properties;
Expand Down Expand Up @@ -39,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;

Expand Down Expand Up @@ -104,7 +104,7 @@ public BasicValue.Resolution<?> resolve(
if ( CollectionHelper.isEmpty( localConfigParameters ) ) {
// we can use the re-usable resolution...
if ( reusableResolution == null ) {
reusableResolution = createResolution( name, Collections.emptyMap(), indicators, context );
reusableResolution = createResolution( name, emptyMap(), indicators, context );
}
return reusableResolution;
}
Expand Down Expand Up @@ -138,16 +138,17 @@ private static <T> BasicValue.Resolution<T> createResolution(
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 );
name, typeImplementorClass, bootstrapContext.getCustomTypeProducer() );

if ( typeInstance instanceof TypeConfigurationAware configurationAware ) {
configurationAware.setTypeConfiguration( typeConfiguration );
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,8 @@ public enum Kind {
// in-flight info

private Class<? extends UserType<?>> explicitCustomType;
private Map<String,String> explicitLocalTypeParams;
private Map<String,String> explicitLocalCustomTypeParams;
private Annotation explicitCustomTypeAnnotation;

private Function<TypeConfiguration, JdbcType> explicitJdbcTypeAccess;
private Function<TypeConfiguration, BasicJavaType> explicitJavaTypeAccess;
Expand Down Expand Up @@ -324,30 +325,33 @@ 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 ) {
applyJpaConverter( value, converterDescriptor );
}

final Class<? extends UserType<?>> 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<? extends UserType<?>> registeredUserTypeImpl =
getMetadataCollector().findRegisteredUserType( basicClass );
getMetadataCollector()
.findRegisteredUserType( modelClassDetails.determineRawClass().toJavaClass() );
if ( registeredUserTypeImpl != null ) {
applyExplicitType( registeredUserTypeImpl, emptyMap() );
this.explicitCustomType = registeredUserTypeImpl;
this.explicitLocalCustomTypeParams = emptyMap();
return;
}
}
Expand Down Expand Up @@ -380,11 +384,6 @@ else if ( modelClassDetails != null ) {

}

private void applyExplicitType(Class<? extends UserType<?>> impl, Map<String,String> params) {
this.explicitCustomType = impl;
this.explicitLocalTypeParams = params;
}

private void prepareCollectionId(MemberDetails attribute) {
final CollectionId collectionIdAnn = attribute.getDirectAnnotationUsage( CollectionId.class );
if ( collectionIdAnn == null ) {
Expand Down Expand Up @@ -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
Expand All @@ -1293,6 +1292,10 @@ public void fillSimpleValue() {
basicValue.setTypeParameters( createDynamicParameterizedTypeParameters() );
}

if ( explicitCustomType != null ) {
basicValue.setTypeAnnotation( explicitCustomTypeAnnotation );
}

if ( converterDescriptor != null ) {
basicValue.setJpaAttributeConverterDescriptor( converterDescriptor );
}
Expand Down Expand Up @@ -1364,8 +1367,8 @@ private Map<String, Object> createDynamicParameterizedTypeParameters() {
parameters.put( DynamicParameterizedType.ACCESS_TYPE, accessType.getType() );
}

if ( explicitLocalTypeParams != null ) {
parameters.putAll( explicitLocalTypeParams );
if ( explicitLocalCustomTypeParams != null ) {
parameters.putAll( explicitLocalCustomTypeParams );
}

return parameters;
Expand All @@ -1377,6 +1380,7 @@ private Map<String, Object> createDynamicParameterizedTypeParameters() {
private interface BasicMappingAccess {
Class<? extends UserType<?>> customType(MemberDetails attribute, SourceModelBuildingContext context);
Map<String,String> customTypeParameters(MemberDetails attribute, SourceModelBuildingContext context);
Annotation customTypeAnnotation(MemberDetails attribute, SourceModelBuildingContext context);
}

private static class ValueMappingAccess implements BasicMappingAccess {
Expand All @@ -1393,6 +1397,12 @@ public Map<String,String> 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<? extends Annotation> metaAnnotated = attribute.getMetaAnnotated( Type.class, context );
return metaAnnotated.size() == 1 ? metaAnnotated.get( 0 ) : null;
}
}

private static class AnyDiscriminatorMappingAccess implements BasicMappingAccess {
Expand All @@ -1407,6 +1417,11 @@ public Class<? extends UserType<?>> customType(MemberDetails attribute, SourceMo
public Map<String,String> customTypeParameters(MemberDetails attribute, SourceModelBuildingContext context) {
return emptyMap();
}

@Override
public Annotation customTypeAnnotation(MemberDetails attribute, SourceModelBuildingContext context) {
return null;
}
}

private static class AnyKeyMappingAccess implements BasicMappingAccess {
Expand All @@ -1421,6 +1436,11 @@ public Class<? extends UserType<?>> customType(MemberDetails attribute, SourceMo
public Map<String,String> customTypeParameters(MemberDetails attribute, SourceModelBuildingContext context) {
return emptyMap();
}

@Override
public Annotation customTypeAnnotation(MemberDetails attribute, SourceModelBuildingContext context) {
return null;
}
}

private static class MapKeyMappingAccess implements BasicMappingAccess {
Expand All @@ -1439,6 +1459,12 @@ public Map<String,String> customTypeParameters(MemberDetails attribute, SourceMo
return customType == null ? null : extractParameterMap( customType.parameters() );

}

@Override
public Annotation customTypeAnnotation(MemberDetails attribute, SourceModelBuildingContext context) {
final List<? extends Annotation> metaAnnotated = attribute.getMetaAnnotated( MapKeyType.class, context );
return metaAnnotated.size() == 1 ? metaAnnotated.get( 0 ) : null;
}
}

private static class CollectionIdMappingAccess implements BasicMappingAccess {
Expand All @@ -1455,7 +1481,12 @@ public Class<? extends UserType<?>> customType(MemberDetails attribute, SourceMo
public Map<String,String> 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<? extends Annotation> metaAnnotated = attribute.getMetaAnnotated( CollectionIdType.class, context );
return metaAnnotated.size() == 1 ? metaAnnotated.get( 0 ) : null;
}
}

Expand All @@ -1471,6 +1502,11 @@ public Class<? extends UserType<?>> customType(MemberDetails attribute, SourceMo
public Map<String,String> 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) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 );
Expand All @@ -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) {
Expand Down
51 changes: 38 additions & 13 deletions hibernate-core/src/main/java/org/hibernate/mapping/BasicValue.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -632,14 +635,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, context, this );
}
}

Expand All @@ -654,7 +657,7 @@ private Resolution<?> resolution(BasicJavaType explicitJavaType, JavaType<?> jav
getColumn(),
ownerName,
propertyName,
getBuildingContext()
context
);
}

Expand Down Expand Up @@ -1021,13 +1024,15 @@ public void setExplicitCustomType(Class<? extends UserType<?>> 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
);
}
}
Expand All @@ -1044,11 +1049,9 @@ private Properties getCustomTypeProperties() {
return properties;
}

private UserType<?> getConfiguredUserTypeBean(Class<? extends UserType<?>> explicitCustomType, Properties properties) {
final UserType<?> typeInstance =
getBuildingContext().getBuildingOptions().isAllowExtensionsInCdi()
? getUserTypeBean( explicitCustomType, properties ).getBeanInstance()
: FallbackBeanInstanceProducer.INSTANCE.produceBeanInstance( explicitCustomType );
private UserType<?> getConfiguredUserTypeBean(
Class<? extends UserType<?>> explicitCustomType, Properties properties, Annotation typeAnnotation) {
final UserType<?> typeInstance = instantiateUserType( explicitCustomType, properties, typeAnnotation );

if ( typeInstance instanceof TypeConfigurationAware configurationAware ) {
configurationAware.setTypeConfiguration( getTypeConfiguration() );
Expand All @@ -1069,6 +1072,28 @@ private UserType<?> getConfiguredUserTypeBean(Class<? extends UserType<?>> expli
return typeInstance;
}

private <T extends UserType<?>> T instantiateUserType(
Class<T> customType, Properties properties, Annotation typeAnnotation) {
if ( typeAnnotation != null ) {
// attempt to instantiate it with the annotation as a constructor argument
try {
final Constructor<T> 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 <T> ManagedBean<T> getUserTypeBean(Class<T> explicitCustomType, Properties properties) {
final BeanInstanceProducer producer = getBuildingContext().getBootstrapContext().getCustomTypeProducer();
if ( isNotEmpty( properties ) ) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -800,11 +802,19 @@ public void setTypeParameters(Map<String, ?> 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() );

Expand All @@ -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 );
Expand Down
Loading
Loading