Skip to content

Commit 023c027

Browse files
committed
HHH-19993 Introduce AnnotationBasedUserType
Signed-off-by: Yanming Zhou <[email protected]>
1 parent bbf5e41 commit 023c027

File tree

6 files changed

+170
-4
lines changed

6 files changed

+170
-4
lines changed

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

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,10 +12,12 @@
1212
import java.util.function.Consumer;
1313
import java.util.function.Function;
1414

15+
import org.hibernate.AnnotationException;
1516
import org.hibernate.Incubating;
1617
import org.hibernate.Internal;
1718
import org.hibernate.MappingException;
1819
import org.hibernate.models.spi.MemberDetails;
20+
import org.hibernate.service.ServiceRegistry;
1921
import org.hibernate.type.TimeZoneStorageStrategy;
2022
import org.hibernate.annotations.SoftDelete;
2123
import org.hibernate.annotations.SoftDeleteType;
@@ -66,13 +68,15 @@
6668
import org.hibernate.type.internal.ConvertedBasicTypeImpl;
6769
import org.hibernate.type.spi.TypeConfiguration;
6870
import org.hibernate.type.spi.TypeConfigurationAware;
71+
import org.hibernate.usertype.AnnotationBasedUserType;
6972
import org.hibernate.usertype.DynamicParameterizedType;
7073
import org.hibernate.usertype.UserType;
7174

7275
import com.fasterxml.classmate.ResolvedType;
7376
import jakarta.persistence.AttributeConverter;
7477
import jakarta.persistence.EnumType;
7578
import jakarta.persistence.TemporalType;
79+
import org.hibernate.usertype.UserTypeCreationContext;
7680

7781
import static java.lang.Boolean.parseBoolean;
7882
import static org.hibernate.boot.model.convert.spi.ConverterDescriptor.TYPE_NAME_PREFIX;
@@ -1110,6 +1114,36 @@ private UserType<?> getConfiguredUserTypeBean(
11101114
Class<? extends UserType<?>> explicitCustomType, Properties properties, Annotation typeAnnotation, MemberDetails memberDetails) {
11111115
final var typeInstance = instantiateUserType( explicitCustomType, properties, typeAnnotation, memberDetails );
11121116

1117+
if ( typeInstance instanceof AnnotationBasedUserType<?, ?> ) {
1118+
if ( typeAnnotation == null ) {
1119+
throw new AnnotationException( String.format( "'UserType' implementation '%s' implements '%s' but no custom annotation present,"
1120+
+ " please refer to the Javadoc of '%s'.",
1121+
typeInstance.getClass().getName(), AnnotationBasedUserType.class.getName(), UserType.class.getName() ) );
1122+
}
1123+
AnnotationBasedUserType<Annotation, ?> annotationBased = (AnnotationBasedUserType<Annotation, ?>) typeInstance;
1124+
annotationBased.initialize( typeAnnotation, new UserTypeCreationContext() {
1125+
@Override
1126+
public MetadataBuildingContext getBuildingContext() {
1127+
return BasicValue.this.getBuildingContext();
1128+
}
1129+
1130+
@Override
1131+
public ServiceRegistry getServiceRegistry() {
1132+
return BasicValue.this.getServiceRegistry();
1133+
}
1134+
1135+
@Override
1136+
public MemberDetails getMemberDetails() {
1137+
return memberDetails;
1138+
}
1139+
1140+
@Override
1141+
public Properties getParameters() {
1142+
return properties;
1143+
}
1144+
} );
1145+
}
1146+
11131147
if ( typeInstance instanceof TypeConfigurationAware configurationAware ) {
11141148
configurationAware.setTypeConfiguration( getTypeConfiguration() );
11151149
}

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

Lines changed: 3 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,9 @@ 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() && !( type instanceof AnnotationBasedUserType<?,?> ) ) {
7980
throw new MappingException( "'UserType' implementation '" + type.getClass().getName()
80-
+ "' does not implement 'ParameterizedType' but parameters were provided" );
81+
+ "' does not implement 'ParameterizedType' or 'AnnotationBasedUserType' but parameters were provided" );
8182
}
8283
}
8384

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: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -244,6 +244,7 @@
244244
*
245245
* @see org.hibernate.type.Type
246246
* @see org.hibernate.type.CustomType
247+
* @see org.hibernate.usertype.AnnotationBasedUserType
247248
*
248249
* @see org.hibernate.annotations.Type
249250
* @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+
}

hibernate-core/src/test/java/org/hibernate/orm/test/mapping/basic/bitset/MetaUserTypeTest.java

Lines changed: 50 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,12 +8,15 @@
88
import jakarta.persistence.Entity;
99
import jakarta.persistence.GeneratedValue;
1010
import jakarta.persistence.Id;
11+
import org.hibernate.annotations.Parameter;
1112
import org.hibernate.annotations.Type;
1213
import org.hibernate.models.spi.MemberDetails;
1314
import org.hibernate.testing.orm.junit.EntityManagerFactoryScope;
1415
import org.hibernate.testing.orm.junit.Jpa;
1516
import org.hibernate.type.descriptor.WrapperOptions;
17+
import org.hibernate.usertype.AnnotationBasedUserType;
1618
import org.hibernate.usertype.UserType;
19+
import org.hibernate.usertype.UserTypeCreationContext;
1720
import org.junit.jupiter.api.Test;
1821

1922
import java.io.Serializable;
@@ -36,7 +39,7 @@
3639
import static org.junit.jupiter.api.Assertions.assertEquals;
3740

3841
@Jpa(annotatedClasses = {MetaUserTypeTest.Thing.class, MetaUserTypeTest.SecondThing.class,
39-
MetaUserTypeTest.ThirdThing.class, MetaUserTypeTest.Things.class})
42+
MetaUserTypeTest.ThirdThing.class, MetaUserTypeTest.FourthThing.class, MetaUserTypeTest.Things.class})
4043
public class MetaUserTypeTest {
4144

4245
@Test void test(EntityManagerFactoryScope scope) {
@@ -75,6 +78,18 @@ public class MetaUserTypeTest {
7578
assertEquals( Period.of( 1, 2, 3 ), thing.period );
7679
assertEquals( Period.ofDays( 42 ), thing.days );
7780
} );
81+
82+
scope.inTransaction( em -> {
83+
FourthThing thing = new FourthThing();
84+
thing.period = Period.of( 1, 2, 3 );
85+
thing.days = Period.ofDays( 42 );
86+
em.persist( thing );
87+
} );
88+
scope.inTransaction( em -> {
89+
FourthThing thing = em.find( FourthThing.class, 1 );
90+
assertEquals( Period.of( 1, 2, 3 ), thing.period );
91+
assertEquals( Period.ofDays( 42 ), thing.days );
92+
} );
7893
}
7994

8095
@Test void testCollection(EntityManagerFactoryScope scope) {
@@ -118,6 +133,15 @@ public class MetaUserTypeTest {
118133
Period days;
119134
}
120135

136+
@Entity static class FourthThing {
137+
@Id @GeneratedValue
138+
long id;
139+
@FourthTimePeriod
140+
Period period;
141+
@FourthTimePeriod(days = true)
142+
Period days;
143+
}
144+
121145
@Entity static class Things {
122146
@Id @GeneratedValue
123147
long id;
@@ -148,6 +172,13 @@ public class MetaUserTypeTest {
148172
boolean days() default false;
149173
}
150174

175+
@Type(value = FourthPeriodType.class, parameters = @Parameter(name="foo", value ="bar"))
176+
@Target({METHOD, FIELD})
177+
@Retention(RUNTIME)
178+
public @interface FourthTimePeriod {
179+
boolean days() default false;
180+
}
181+
151182
static class PeriodType extends AbstractPeriodType {
152183

153184
PeriodType(TimePeriod timePeriod) {
@@ -175,8 +206,25 @@ static class ThirdPeriodType extends AbstractPeriodType {
175206

176207
}
177208

209+
static class FourthPeriodType extends AbstractPeriodType implements AnnotationBasedUserType<FourthTimePeriod, Period> {
210+
211+
FourthPeriodType() {
212+
super(false);
213+
}
214+
215+
@Override
216+
public void initialize(FourthTimePeriod timePeriod, UserTypeCreationContext context) {
217+
days = timePeriod.days();
218+
if ( !timePeriod.equals( ( (Field) context.getMemberDetails().toJavaMember() ).getAnnotation( FourthTimePeriod.class ) )) {
219+
// only for validation
220+
throw new IllegalArgumentException(context.getMemberDetails().toJavaMember() + " should be annotated with " + timePeriod);
221+
}
222+
assertEquals( "bar", context.getParameters().get("foo") );
223+
}
224+
}
225+
178226
static abstract class AbstractPeriodType implements UserType<Period> {
179-
private final boolean days;
227+
boolean days;
180228

181229
AbstractPeriodType(boolean days) {
182230
this.days = days;

0 commit comments

Comments
 (0)