-
-
Notifications
You must be signed in to change notification settings - Fork 3.7k
HHH-19993 Introduce AnnotationBasedUserType and allow UserType constructor accept MemberDetails as parameter #11445
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
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -12,9 +12,12 @@ | |
| import java.util.function.Consumer; | ||
| import java.util.function.Function; | ||
|
|
||
| import org.hibernate.AnnotationException; | ||
| import org.hibernate.Incubating; | ||
| import org.hibernate.Internal; | ||
| import org.hibernate.MappingException; | ||
| import org.hibernate.models.spi.MemberDetails; | ||
| import org.hibernate.service.ServiceRegistry; | ||
| import org.hibernate.type.TimeZoneStorageStrategy; | ||
| import org.hibernate.annotations.SoftDelete; | ||
| import org.hibernate.annotations.SoftDeleteType; | ||
|
|
@@ -65,13 +68,15 @@ | |
| import org.hibernate.type.internal.ConvertedBasicTypeImpl; | ||
| import org.hibernate.type.spi.TypeConfiguration; | ||
| import org.hibernate.type.spi.TypeConfigurationAware; | ||
| import org.hibernate.usertype.AnnotationBasedUserType; | ||
| import org.hibernate.usertype.DynamicParameterizedType; | ||
| import org.hibernate.usertype.UserType; | ||
|
|
||
| import com.fasterxml.classmate.ResolvedType; | ||
| import jakarta.persistence.AttributeConverter; | ||
| import jakarta.persistence.EnumType; | ||
| import jakarta.persistence.TemporalType; | ||
| import org.hibernate.usertype.UserTypeCreationContext; | ||
|
|
||
| import static java.lang.Boolean.parseBoolean; | ||
| import static org.hibernate.boot.model.convert.spi.ConverterDescriptor.TYPE_NAME_PREFIX; | ||
|
|
@@ -85,6 +90,7 @@ | |
|
|
||
| /** | ||
| * @author Steve Ebersole | ||
| * @author Yanming Zhou | ||
| */ | ||
| public class BasicValue extends SimpleValue | ||
| implements JdbcTypeIndicators, Resolvable, JpaAttributeConverterCreationContext { | ||
|
|
@@ -1080,9 +1086,10 @@ public void setExplicitCustomType(Class<? extends UserType<?>> explicitCustomTyp | |
| else { | ||
| final var typeProperties = getCustomTypeProperties(); | ||
| final var typeAnnotation = getTypeAnnotation(); | ||
| final var memberDetails = getMemberDetails(); | ||
| resolution = new UserTypeResolution<>( | ||
| new CustomType<>( | ||
| getConfiguredUserTypeBean( explicitCustomType, typeProperties, typeAnnotation ), | ||
| getConfiguredUserTypeBean( explicitCustomType, typeProperties, typeAnnotation, memberDetails ), | ||
| getTypeConfiguration() | ||
| ), | ||
| null, | ||
|
|
@@ -1104,8 +1111,38 @@ private Properties getCustomTypeProperties() { | |
| } | ||
|
|
||
| private UserType<?> getConfiguredUserTypeBean( | ||
| Class<? extends UserType<?>> explicitCustomType, Properties properties, Annotation typeAnnotation) { | ||
| final var typeInstance = instantiateUserType( explicitCustomType, properties, typeAnnotation ); | ||
| Class<? extends UserType<?>> explicitCustomType, Properties properties, Annotation typeAnnotation, MemberDetails memberDetails) { | ||
| final var typeInstance = instantiateUserType( explicitCustomType, properties, typeAnnotation, memberDetails ); | ||
|
|
||
| if ( typeInstance instanceof AnnotationBasedUserType<?, ?> ) { | ||
| if ( typeAnnotation == null ) { | ||
| throw new AnnotationException( String.format( "'UserType' implementation '%s' implements '%s' but no custom annotation present," | ||
| + " please refer to the Javadoc of '%s'.", | ||
| typeInstance.getClass().getName(), AnnotationBasedUserType.class.getName(), UserType.class.getName() ) ); | ||
| } | ||
| AnnotationBasedUserType<Annotation, ?> annotationBased = (AnnotationBasedUserType<Annotation, ?>) typeInstance; | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is this an unchecked cast?
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It is. OK, here's my suggestion of how we should write this code, to get much better error messages instead of cryptic CCEs from weird undebuggable byte code. This approach uses wildcard capture and reflection. Since the reflection happens at start time, it should not cause a problem. private <T extends Annotation> void initializeType(Properties properties,
Annotation typeAnnotation,
MemberDetails memberDetails,
AnnotationBasedUserType<T, ?> annotationBased) {
annotationBased.initialize( castAnnotationType( typeAnnotation, annotationBased ),
new UserTypeCreationContext() {
@Override
public MetadataBuildingContext getBuildingContext() {
return BasicValue.this.getBuildingContext();
}
@Override
public ServiceRegistry getServiceRegistry() {
return BasicValue.this.getServiceRegistry();
}
@Override
public MemberDetails getMemberDetails() {
return memberDetails;
}
@Override
public Properties getParameters() {
return properties;
}
} );
}
private <T extends Annotation> T castAnnotationType(
Annotation typeAnnotation,
AnnotationBasedUserType<T, ?> annotationBased) {
final var annotationType = annotationBased.getClass();
for ( var iface: annotationType.getGenericInterfaces() ) {
if ( iface instanceof ParameterizedType parameterizedType
&& parameterizedType.getRawType() == AnnotationBasedUserType.class ) {
final var typeArguments = parameterizedType.getActualTypeArguments();
if ( typeArguments.length > 0
&& typeArguments[0] instanceof Class<?> annotationClass ) {
if ( !annotationClass.isInstance( typeAnnotation ) ) {
throw new AnnotationException( String.format( "Annotation '%s' is not assignable to '%s'",
annotationType.getName(), iface.getTypeName() ) );
}
@SuppressWarnings("unchecked") // safe, we just checked it
final var castAnnotation = (T) typeAnnotation;
return castAnnotation;
}
}
}
throw new AssertionFailure( "Could not find implementing interface" );
}
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. See the analogous change in this PR: |
||
| annotationBased.initialize( typeAnnotation, new UserTypeCreationContext() { | ||
| @Override | ||
| public MetadataBuildingContext getBuildingContext() { | ||
| return BasicValue.this.getBuildingContext(); | ||
| } | ||
|
|
||
| @Override | ||
| public ServiceRegistry getServiceRegistry() { | ||
| return BasicValue.this.getServiceRegistry(); | ||
| } | ||
|
|
||
| @Override | ||
| public MemberDetails getMemberDetails() { | ||
| return memberDetails; | ||
| } | ||
|
|
||
| @Override | ||
| public Properties getParameters() { | ||
| return properties; | ||
| } | ||
| } ); | ||
| } | ||
|
|
||
| if ( typeInstance instanceof TypeConfigurationAware configurationAware ) { | ||
| configurationAware.setTypeConfiguration( getTypeConfiguration() ); | ||
|
|
@@ -1127,21 +1164,41 @@ private UserType<?> getConfiguredUserTypeBean( | |
| } | ||
|
|
||
| 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 | ||
| Class<T> customType, Properties properties, Annotation typeAnnotation, MemberDetails memberDetails ) { | ||
| try { | ||
| // attempt to instantiate it with the member as a constructor argument | ||
| try { | ||
| final var constructor = customType.getDeclaredConstructor( typeAnnotation.annotationType() ); | ||
| final var constructor = customType.getDeclaredConstructor( MemberDetails.class ); | ||
| constructor.setAccessible( true ); | ||
| return constructor.newInstance( typeAnnotation ); | ||
| return constructor.newInstance( memberDetails ); | ||
| } | ||
| catch ( NoSuchMethodException ignored ) { | ||
| // no such constructor, instantiate it the old way | ||
| catch (NoSuchMethodException ignored) { | ||
| } | ||
| catch (InvocationTargetException | InstantiationException | IllegalAccessException e) { | ||
| throw new org.hibernate.InstantiationException( "Could not instantiate custom type", customType, e ); | ||
|
|
||
| if ( typeAnnotation != null ) { | ||
| // attempt to instantiate it with the annotation as a constructor argument | ||
| try { | ||
| final var constructor = customType.getDeclaredConstructor( typeAnnotation.annotationType() ); | ||
| constructor.setAccessible( true ); | ||
| return constructor.newInstance( typeAnnotation ); | ||
| } | ||
| catch (NoSuchMethodException ignored) { | ||
| // attempt to instantiate it with the annotation and member as constructor arguments | ||
| try { | ||
| final var constructor = customType.getDeclaredConstructor( typeAnnotation.annotationType(), | ||
| MemberDetails.class ); | ||
| constructor.setAccessible( true ); | ||
| return constructor.newInstance( typeAnnotation, memberDetails ); | ||
| } | ||
| 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() | ||
|
|
||
| Original file line number | Diff line number | Diff line change | ||||||
|---|---|---|---|---|---|---|---|---|
|
|
@@ -16,6 +16,7 @@ | |||||||
| import org.hibernate.resource.beans.internal.FallbackBeanInstanceProducer; | ||||||||
| import org.hibernate.resource.beans.spi.ManagedBean; | ||||||||
| import org.hibernate.resource.beans.spi.ProvidedInstanceManagedBeanImpl; | ||||||||
| import org.hibernate.usertype.AnnotationBasedUserType; | ||||||||
| import org.hibernate.usertype.ParameterizedType; | ||||||||
| import org.hibernate.usertype.UserCollectionType; | ||||||||
|
|
||||||||
|
|
@@ -75,9 +76,9 @@ public static void injectParameters(Object type, Properties parameters) { | |||||||
| if ( type instanceof ParameterizedType parameterizedType ) { | ||||||||
| parameterizedType.setParameterValues( parameters == null ? EMPTY_PROPERTIES : parameters ); | ||||||||
| } | ||||||||
| else if ( parameters != null && !parameters.isEmpty() ) { | ||||||||
| else if ( parameters != null && !parameters.isEmpty() && !( type instanceof AnnotationBasedUserType<?,?> ) ) { | ||||||||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||
| throw new MappingException( "'UserType' implementation '" + type.getClass().getName() | ||||||||
| + "' does not implement 'ParameterizedType' but parameters were provided" ); | ||||||||
| + "' does not implement 'ParameterizedType' or 'AnnotationBasedUserType' but parameters were provided" ); | ||||||||
| } | ||||||||
| } | ||||||||
|
|
||||||||
|
|
||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,35 @@ | ||
| /* | ||
| * SPDX-License-Identifier: Apache-2.0 | ||
| * Copyright Red Hat Inc. and Hibernate Authors | ||
| */ | ||
| package org.hibernate.usertype; | ||
|
|
||
| import org.hibernate.Incubating; | ||
|
|
||
| import java.lang.annotation.Annotation; | ||
|
|
||
|
|
||
| /** | ||
| * A {@link UserType} which receives parameters from a custom annotation. | ||
| * | ||
| * @param <A> The user type annotation type supported by an implementation | ||
| * @param <J> The java type | ||
| * | ||
| * @author Yanming Zhou | ||
| * | ||
| * @since 7.3 | ||
| */ | ||
| @Incubating | ||
| public interface AnnotationBasedUserType<A extends Annotation, J> extends UserType<J> { | ||
| /** | ||
| * Initializes this generation strategy for the given annotation instance. | ||
| * | ||
| * @param annotation an instance of the user type annotation type. Typically, | ||
| * implementations will retrieve the annotation's attribute | ||
| * values and store them in fields. | ||
| * @param context a {@link UserTypeCreationContext}. | ||
| * @throws org.hibernate.HibernateException in case an error occurred during initialization, e.g. if | ||
| * an implementation can't create a value for the given property type. | ||
| */ | ||
| void initialize(A annotation, UserTypeCreationContext context); | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,47 @@ | ||
| /* | ||
| * SPDX-License-Identifier: Apache-2.0 | ||
| * Copyright Red Hat Inc. and Hibernate Authors | ||
| */ | ||
| package org.hibernate.usertype; | ||
|
|
||
| import org.hibernate.Incubating; | ||
| import org.hibernate.annotations.Type; | ||
| import org.hibernate.boot.spi.MetadataBuildingContext; | ||
| import org.hibernate.models.spi.MemberDetails; | ||
| import org.hibernate.service.ServiceRegistry; | ||
|
|
||
| import java.util.Properties; | ||
|
|
||
| /** | ||
| * Access to information useful during {@linkplain UserType} creation and initialization. | ||
| * | ||
| * @author Yanming Zhou | ||
| * @see AnnotationBasedUserType | ||
| * | ||
| * @since 7.3 | ||
| */ | ||
| @Incubating | ||
| public interface UserTypeCreationContext { | ||
| /** | ||
| * Access to the {@link MetadataBuildingContext}. | ||
| */ | ||
| MetadataBuildingContext getBuildingContext(); | ||
|
|
||
| /** | ||
| * Access to available services. | ||
| */ | ||
| ServiceRegistry getServiceRegistry(); | ||
|
|
||
| /** | ||
| * Access to the {@link MemberDetails}. | ||
| */ | ||
| MemberDetails getMemberDetails(); | ||
|
|
||
| /** | ||
| * Access to the parameters. | ||
| * | ||
| * @see Type#parameters() | ||
| */ | ||
| Properties getParameters(); | ||
|
|
||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.