Skip to content

Commit afbad8c

Browse files
quaffgavinking
authored andcommitted
HHH-19993 Introduce UserTypeCreationContext and AnnotationBasedUserType
Signed-off-by: Yanming Zhou <[email protected]>
1 parent f8d7767 commit afbad8c

File tree

8 files changed

+377
-20
lines changed

8 files changed

+377
-20
lines changed

hibernate-core/src/main/java/org/hibernate/boot/model/internal/BasicValueBinder.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1193,6 +1193,9 @@ else if ( aggregateComponent != null ) {
11931193
}
11941194

11951195
public void fillSimpleValue() {
1196+
1197+
basicValue.setMemberDetails( memberDetails );
1198+
11961199
basicValue.setExplicitTypeParams( explicitLocalCustomTypeParams );
11971200

11981201
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

hibernate-core/src/main/java/org/hibernate/mapping/BasicValue.java

Lines changed: 123 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -6,15 +6,20 @@
66

77
import java.lang.annotation.Annotation;
88
import java.lang.reflect.InvocationTargetException;
9+
import java.lang.reflect.ParameterizedType;
910
import java.util.HashMap;
1011
import java.util.Map;
1112
import java.util.Properties;
1213
import java.util.function.Consumer;
1314
import java.util.function.Function;
1415

16+
import org.hibernate.AnnotationException;
17+
import org.hibernate.AssertionFailure;
1518
import org.hibernate.Incubating;
1619
import org.hibernate.Internal;
1720
import org.hibernate.MappingException;
21+
import org.hibernate.models.spi.MemberDetails;
22+
import org.hibernate.service.ServiceRegistry;
1823
import org.hibernate.type.TimeZoneStorageStrategy;
1924
import org.hibernate.annotations.SoftDelete;
2025
import org.hibernate.annotations.SoftDeleteType;
@@ -65,13 +70,15 @@
6570
import org.hibernate.type.internal.ConvertedBasicTypeImpl;
6671
import org.hibernate.type.spi.TypeConfiguration;
6772
import org.hibernate.type.spi.TypeConfigurationAware;
73+
import org.hibernate.usertype.AnnotationBasedUserType;
6874
import org.hibernate.usertype.DynamicParameterizedType;
6975
import org.hibernate.usertype.UserType;
7076

7177
import com.fasterxml.classmate.ResolvedType;
7278
import jakarta.persistence.AttributeConverter;
7379
import jakarta.persistence.EnumType;
7480
import jakarta.persistence.TemporalType;
81+
import org.hibernate.usertype.UserTypeCreationContext;
7582

7683
import static java.lang.Boolean.parseBoolean;
7784
import static org.hibernate.boot.model.convert.spi.ConverterDescriptor.TYPE_NAME_PREFIX;
@@ -85,6 +92,7 @@
8592

8693
/**
8794
* @author Steve Ebersole
95+
* @author Yanming Zhou
8896
*/
8997
public class BasicValue extends SimpleValue
9098
implements JdbcTypeIndicators, Resolvable, JpaAttributeConverterCreationContext {
@@ -1080,9 +1088,10 @@ public void setExplicitCustomType(Class<? extends UserType<?>> explicitCustomTyp
10801088
else {
10811089
final var typeProperties = getCustomTypeProperties();
10821090
final var typeAnnotation = getTypeAnnotation();
1091+
final var memberDetails = getMemberDetails();
10831092
resolution = new UserTypeResolution<>(
10841093
new CustomType<>(
1085-
getConfiguredUserTypeBean( explicitCustomType, typeProperties, typeAnnotation ),
1094+
getConfiguredUserTypeBean( explicitCustomType, typeProperties, typeAnnotation, memberDetails ),
10861095
getTypeConfiguration()
10871096
),
10881097
null,
@@ -1104,8 +1113,37 @@ private Properties getCustomTypeProperties() {
11041113
}
11051114

11061115
private UserType<?> getConfiguredUserTypeBean(
1107-
Class<? extends UserType<?>> explicitCustomType, Properties properties, Annotation typeAnnotation) {
1108-
final var typeInstance = instantiateUserType( explicitCustomType, properties, typeAnnotation );
1116+
Class<? extends UserType<?>> explicitCustomType, Properties properties, Annotation typeAnnotation, MemberDetails memberDetails) {
1117+
final var creationContext = new UserTypeCreationContext() {
1118+
@Override
1119+
public MetadataBuildingContext getBuildingContext() {
1120+
return BasicValue.this.getBuildingContext();
1121+
}
1122+
1123+
@Override
1124+
public ServiceRegistry getServiceRegistry() {
1125+
return BasicValue.this.getServiceRegistry();
1126+
}
1127+
1128+
@Override
1129+
public MemberDetails getMemberDetails() {
1130+
return memberDetails;
1131+
}
1132+
1133+
@Override
1134+
public Properties getParameters() {
1135+
return properties;
1136+
}
1137+
};
1138+
final var typeInstance = instantiateUserType( explicitCustomType, creationContext, typeAnnotation );
1139+
1140+
if ( typeInstance instanceof AnnotationBasedUserType<?, ?> annotationBased ) {
1141+
if ( typeAnnotation == null ) {
1142+
throw new AnnotationException( String.format( "Custom type '%s' implements 'AnnotationBasedUserType' but no custom type annotation is present",
1143+
typeInstance.getClass().getName() ) );
1144+
}
1145+
initializeAnnotationBasedUserType( properties, typeAnnotation, memberDetails, annotationBased );
1146+
}
11091147

11101148
if ( typeInstance instanceof TypeConfigurationAware configurationAware ) {
11111149
configurationAware.setTypeConfiguration( getTypeConfiguration() );
@@ -1126,25 +1164,97 @@ private UserType<?> getConfiguredUserTypeBean(
11261164
return typeInstance;
11271165
}
11281166

1167+
private <A extends Annotation> void initializeAnnotationBasedUserType(Properties properties,
1168+
Annotation typeAnnotation,
1169+
MemberDetails memberDetails,
1170+
AnnotationBasedUserType<A, ?> annotationBased) {
1171+
annotationBased.initialize( castAnnotationType( typeAnnotation, annotationBased ),
1172+
new UserTypeCreationContext() {
1173+
@Override
1174+
public MetadataBuildingContext getBuildingContext() {
1175+
return BasicValue.this.getBuildingContext();
1176+
}
1177+
1178+
@Override
1179+
public ServiceRegistry getServiceRegistry() {
1180+
return BasicValue.this.getServiceRegistry();
1181+
}
1182+
1183+
@Override
1184+
public MemberDetails getMemberDetails() {
1185+
return memberDetails;
1186+
}
1187+
1188+
@Override
1189+
public Properties getParameters() {
1190+
return properties;
1191+
}
1192+
} );
1193+
}
1194+
1195+
private <A extends Annotation> A castAnnotationType(
1196+
Annotation typeAnnotation, AnnotationBasedUserType<A, ?> annotationBased ) {
1197+
final var annotationType = annotationBased.getClass();
1198+
for ( var iface: annotationType.getGenericInterfaces() ) {
1199+
if ( iface instanceof ParameterizedType parameterizedType
1200+
&& parameterizedType.getRawType() == AnnotationBasedUserType.class ) {
1201+
final var typeArguments = parameterizedType.getActualTypeArguments();
1202+
if ( typeArguments.length > 0
1203+
&& typeArguments[0] instanceof Class<?> annotationClass ) {
1204+
if ( !annotationClass.isInstance( typeAnnotation ) ) {
1205+
throw new AnnotationException( String.format( "Annotation '%s' is not assignable to '%s'",
1206+
annotationType.getName(), iface.getTypeName() ) );
1207+
}
1208+
@SuppressWarnings("unchecked") // safe, we just checked it
1209+
final var castAnnotation = (A) typeAnnotation;
1210+
return castAnnotation;
1211+
}
1212+
}
1213+
}
1214+
throw new AssertionFailure( "Could not find implementing interface" );
1215+
}
1216+
11291217
private <T extends UserType<?>> T instantiateUserType(
1130-
Class<T> customType, Properties properties, Annotation typeAnnotation) {
1131-
if ( typeAnnotation != null ) {
1132-
// attempt to instantiate it with the annotation as a constructor argument
1218+
Class<T> customType, UserTypeCreationContext creationContext, Annotation typeAnnotation) {
1219+
try {
1220+
if ( typeAnnotation != null ) {
1221+
// attempt to instantiate it with the annotation and context object as constructor arguments
1222+
try {
1223+
final var constructor = customType.getDeclaredConstructor( typeAnnotation.annotationType(),
1224+
UserTypeCreationContext.class );
1225+
constructor.setAccessible( true );
1226+
return constructor.newInstance( typeAnnotation, creationContext );
1227+
}
1228+
catch (NoSuchMethodException ignored) {
1229+
// attempt to instantiate it with the annotation as a constructor argument
1230+
try {
1231+
final var constructor = customType.getDeclaredConstructor( typeAnnotation.annotationType() );
1232+
constructor.setAccessible( true );
1233+
return constructor.newInstance( typeAnnotation );
1234+
}
1235+
catch (NoSuchMethodException ignored_) {
1236+
// no such constructor
1237+
}
1238+
}
1239+
}
1240+
1241+
// attempt to instantiate it with the context object as a constructor argument
11331242
try {
1134-
final var constructor = customType.getDeclaredConstructor( typeAnnotation.annotationType() );
1243+
final var constructor = customType.getDeclaredConstructor( UserTypeCreationContext.class );
11351244
constructor.setAccessible( true );
1136-
return constructor.newInstance( typeAnnotation );
1245+
return constructor.newInstance( creationContext );
11371246
}
1138-
catch ( NoSuchMethodException ignored ) {
1247+
catch (NoSuchMethodException ignored) {
11391248
// no such constructor, instantiate it the old way
11401249
}
1141-
catch (InvocationTargetException | InstantiationException | IllegalAccessException e) {
1142-
throw new org.hibernate.InstantiationException( "Could not instantiate custom type", customType, e );
1143-
}
1250+
1251+
}
1252+
catch (InvocationTargetException | InstantiationException | IllegalAccessException e) {
1253+
throw new org.hibernate.InstantiationException( "Could not instantiate custom type", customType, e );
11441254
}
11451255

11461256
return getBuildingContext().getBuildingOptions().isAllowExtensionsInCdi()
1147-
? getUserTypeBean( customType, properties ).getBeanInstance()
1257+
? getUserTypeBean( customType, creationContext.getParameters() ).getBeanInstance()
11481258
: FallbackBeanInstanceProducer.INSTANCE.produceBeanInstance( customType );
11491259
}
11501260

hibernate-core/src/main/java/org/hibernate/mapping/MappingHelper.java

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
import org.hibernate.resource.beans.internal.FallbackBeanInstanceProducer;
1717
import org.hibernate.resource.beans.spi.ManagedBean;
1818
import org.hibernate.resource.beans.spi.ProvidedInstanceManagedBeanImpl;
19+
import org.hibernate.usertype.AnnotationBasedUserType;
1920
import org.hibernate.usertype.ParameterizedType;
2021
import org.hibernate.usertype.UserCollectionType;
2122

@@ -75,9 +76,10 @@ public static void injectParameters(Object type, Properties parameters) {
7576
if ( type instanceof ParameterizedType parameterizedType ) {
7677
parameterizedType.setParameterValues( parameters == null ? EMPTY_PROPERTIES : parameters );
7778
}
78-
else if ( parameters != null && !parameters.isEmpty() ) {
79+
else if ( parameters != null && !parameters.isEmpty()
80+
&& !( type instanceof AnnotationBasedUserType<?,?> ) ) {
7981
throw new MappingException( "'UserType' implementation '" + type.getClass().getName()
80-
+ "' does not implement 'ParameterizedType' but parameters were provided" );
82+
+ "' does not implement 'ParameterizedType' or 'AnnotationBasedUserType' but parameters were provided" );
8183
}
8284
}
8385

hibernate-core/src/main/java/org/hibernate/mapping/SimpleValue.java

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,7 @@ public abstract class SimpleValue implements KeyValue {
8989
private String typeName;
9090
private Properties typeParameters;
9191
private Annotation typeAnnotation;
92+
private MemberDetails memberDetails;
9293
private boolean isVersion;
9394
private boolean isNationalized;
9495
private boolean isLob;
@@ -135,6 +136,7 @@ protected SimpleValue(SimpleValue original) {
135136
this.typeName = original.typeName;
136137
this.typeParameters = original.typeParameters == null ? null : new Properties( original.typeParameters );
137138
this.typeAnnotation = original.typeAnnotation;
139+
this.memberDetails = original.memberDetails;
138140
this.isVersion = original.isVersion;
139141
this.isNationalized = original.isNationalized;
140142
this.isLob = original.isLob;
@@ -809,6 +811,10 @@ public void setTypeAnnotation(Annotation typeAnnotation) {
809811
this.typeAnnotation = typeAnnotation;
810812
}
811813

814+
public void setMemberDetails(MemberDetails memberDetails) {
815+
this.memberDetails = memberDetails;
816+
}
817+
812818
public Properties getTypeParameters() {
813819
return typeParameters;
814820
}
@@ -817,6 +823,10 @@ public Annotation getTypeAnnotation() {
817823
return typeAnnotation;
818824
}
819825

826+
public MemberDetails getMemberDetails() {
827+
return memberDetails;
828+
}
829+
820830
public void copyTypeFrom(SimpleValue sourceValue ) {
821831
setTypeName( sourceValue.getTypeName() );
822832
setTypeParameters( sourceValue.getTypeParameters() );
@@ -840,6 +850,7 @@ public boolean isSame(SimpleValue other) {
840850
&& Objects.equals( typeName, other.typeName )
841851
&& Objects.equals( typeParameters, other.typeParameters )
842852
&& Objects.equals( typeAnnotation, other.typeAnnotation )
853+
&& Objects.equals( memberDetails, other.memberDetails )
843854
&& Objects.equals( table, other.table )
844855
&& Objects.equals( foreignKeyName, other.foreignKeyName )
845856
&& Objects.equals( foreignKeyDefinition, other.foreignKeyDefinition );
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
/*
2+
* SPDX-License-Identifier: Apache-2.0
3+
* Copyright Red Hat Inc. and Hibernate Authors
4+
*/
5+
package org.hibernate.usertype;
6+
7+
import org.hibernate.Incubating;
8+
9+
import java.lang.annotation.Annotation;
10+
11+
12+
/**
13+
* A {@link UserType} which receives parameters from a custom annotation.
14+
*
15+
* @param <A> The user type annotation type supported by an implementation
16+
* @param <J> The java type
17+
*
18+
* @author Yanming Zhou
19+
*
20+
* @since 7.3
21+
*/
22+
@Incubating
23+
public interface AnnotationBasedUserType<A extends Annotation, J> extends UserType<J> {
24+
/**
25+
* Initializes this generation strategy for the given annotation instance.
26+
*
27+
* @param annotation an instance of the user type annotation type. Typically,
28+
* implementations will retrieve the annotation's attribute
29+
* values and store them in fields.
30+
* @param context a {@link UserTypeCreationContext}.
31+
* @throws org.hibernate.HibernateException in case an error occurred during initialization, e.g. if
32+
* an implementation can't create a value for the given property type.
33+
*/
34+
void initialize(A annotation, UserTypeCreationContext context);
35+
}

hibernate-core/src/main/java/org/hibernate/usertype/UserType.java

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -214,8 +214,23 @@
214214
* }
215215
* </pre>
216216
* <p>
217-
* Every implementor of {@code UserType} must be immutable and must
218-
* declare a public default constructor.
217+
* Every implementor of {@code UserType} must be immutable.
218+
* <p>
219+
* A custom type may receive parameters from its type annotation.
220+
* The custom type may either:
221+
* <ul>
222+
* <li>implement {@link AnnotationBasedUserType}, and receive the
223+
* annotation as an argument to
224+
* {@link AnnotationBasedUserType#initialize},
225+
* <li>declare a constructor with the same signature as
226+
* {@link AnnotationBasedUserType#initialize},
227+
* <li>declare a constructor which accepts just the annotation instance,
228+
* or
229+
* <li>declare a constructor which accepts just the {@link UserTypeCreationContext},
230+
* or
231+
* <li>declare only a default constructor, in which case it does not
232+
* receive parameters.
233+
* </ul>
219234
* <p>
220235
* A custom type implemented as a {@code UserType} is treated as a
221236
* non-composite value, and does not have persistent attributes which
@@ -242,6 +257,7 @@
242257
*
243258
* @see org.hibernate.type.Type
244259
* @see org.hibernate.type.CustomType
260+
* @see org.hibernate.usertype.AnnotationBasedUserType
245261
*
246262
* @see org.hibernate.annotations.Type
247263
* @see org.hibernate.annotations.TypeRegistration
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
/*
2+
* SPDX-License-Identifier: Apache-2.0
3+
* Copyright Red Hat Inc. and Hibernate Authors
4+
*/
5+
package org.hibernate.usertype;
6+
7+
import org.hibernate.Incubating;
8+
import org.hibernate.annotations.Type;
9+
import org.hibernate.boot.spi.MetadataBuildingContext;
10+
import org.hibernate.models.spi.MemberDetails;
11+
import org.hibernate.service.ServiceRegistry;
12+
13+
import java.util.Properties;
14+
15+
/**
16+
* Access to information useful during {@linkplain UserType} creation and initialization.
17+
*
18+
* @author Yanming Zhou
19+
* @see AnnotationBasedUserType
20+
*
21+
* @since 7.3
22+
*/
23+
@Incubating
24+
public interface UserTypeCreationContext {
25+
/**
26+
* Access to the {@link MetadataBuildingContext}.
27+
*/
28+
MetadataBuildingContext getBuildingContext();
29+
30+
/**
31+
* Access to available services.
32+
*/
33+
ServiceRegistry getServiceRegistry();
34+
35+
/**
36+
* Access to the {@link MemberDetails}.
37+
*/
38+
MemberDetails getMemberDetails();
39+
40+
/**
41+
* Access to the parameters.
42+
*
43+
* @see Type#parameters()
44+
*/
45+
Properties getParameters();
46+
47+
}

0 commit comments

Comments
 (0)