Skip to content

Commit a79769b

Browse files
committed
HHH-19993 Allow custom UserType constructor accepts Member as parameter
Signed-off-by: Yanming Zhou <zhouyanming@gmail.com>
1 parent 93c4950 commit a79769b

File tree

5 files changed

+143
-18
lines changed

5 files changed

+143
-18
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: 36 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66

77
import java.lang.annotation.Annotation;
88
import java.lang.reflect.InvocationTargetException;
9+
import java.lang.reflect.Member;
910
import java.util.HashMap;
1011
import java.util.Map;
1112
import java.util.Properties;
@@ -15,6 +16,7 @@
1516
import org.hibernate.Incubating;
1617
import org.hibernate.Internal;
1718
import org.hibernate.MappingException;
19+
import org.hibernate.models.spi.MemberDetails;
1820
import org.hibernate.type.TimeZoneStorageStrategy;
1921
import org.hibernate.annotations.SoftDelete;
2022
import org.hibernate.annotations.SoftDeleteType;
@@ -85,6 +87,7 @@
8587

8688
/**
8789
* @author Steve Ebersole
90+
* @author Yanming Zhou
8891
*/
8992
public class BasicValue extends SimpleValue
9093
implements JdbcTypeIndicators, Resolvable, JpaAttributeConverterCreationContext {
@@ -1080,9 +1083,10 @@ public void setExplicitCustomType(Class<? extends UserType<?>> explicitCustomTyp
10801083
else {
10811084
final var typeProperties = getCustomTypeProperties();
10821085
final var typeAnnotation = getTypeAnnotation();
1086+
final var memberDetails = getMemberDetails();
10831087
resolution = new UserTypeResolution<>(
10841088
new CustomType<>(
1085-
getConfiguredUserTypeBean( explicitCustomType, typeProperties, typeAnnotation ),
1089+
getConfiguredUserTypeBean( explicitCustomType, typeProperties, typeAnnotation, memberDetails ),
10861090
getTypeConfiguration()
10871091
),
10881092
null,
@@ -1104,8 +1108,8 @@ private Properties getCustomTypeProperties() {
11041108
}
11051109

11061110
private UserType<?> getConfiguredUserTypeBean(
1107-
Class<? extends UserType<?>> explicitCustomType, Properties properties, Annotation typeAnnotation) {
1108-
final var typeInstance = instantiateUserType( explicitCustomType, properties, typeAnnotation );
1111+
Class<? extends UserType<?>> explicitCustomType, Properties properties, Annotation typeAnnotation, MemberDetails memberDetails) {
1112+
final var typeInstance = instantiateUserType( explicitCustomType, properties, typeAnnotation, memberDetails );
11091113

11101114
if ( typeInstance instanceof TypeConfigurationAware configurationAware ) {
11111115
configurationAware.setTypeConfiguration( getTypeConfiguration() );
@@ -1127,21 +1131,41 @@ private UserType<?> getConfiguredUserTypeBean(
11271131
}
11281132

11291133
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
1134+
Class<T> customType, Properties properties, Annotation typeAnnotation, MemberDetails memberDetails ) {
1135+
try {
1136+
// attempt to instantiate it with the member as a constructor argument
11331137
try {
1134-
final var constructor = customType.getDeclaredConstructor( typeAnnotation.annotationType() );
1138+
final var constructor = customType.getDeclaredConstructor( Member.class );
11351139
constructor.setAccessible( true );
1136-
return constructor.newInstance( typeAnnotation );
1140+
return constructor.newInstance( memberDetails.toJavaMember() );
11371141
}
1138-
catch ( NoSuchMethodException ignored ) {
1139-
// no such constructor, instantiate it the old way
1142+
catch (NoSuchMethodException ignored) {
11401143
}
1141-
catch (InvocationTargetException | InstantiationException | IllegalAccessException e) {
1142-
throw new org.hibernate.InstantiationException( "Could not instantiate custom type", customType, e );
1144+
1145+
if ( typeAnnotation != null ) {
1146+
// attempt to instantiate it with the annotation as a constructor argument
1147+
try {
1148+
final var constructor = customType.getDeclaredConstructor( typeAnnotation.annotationType() );
1149+
constructor.setAccessible( true );
1150+
return constructor.newInstance( typeAnnotation );
1151+
}
1152+
catch (NoSuchMethodException ignored) {
1153+
// attempt to instantiate it with the annotation and member as constructor arguments
1154+
try {
1155+
final var constructor = customType.getDeclaredConstructor( typeAnnotation.annotationType(),
1156+
Member.class );
1157+
constructor.setAccessible( true );
1158+
return constructor.newInstance( typeAnnotation, memberDetails.toJavaMember() );
1159+
}
1160+
catch (NoSuchMethodException ignored_) {
1161+
// no such constructor, instantiate it the old way
1162+
}
1163+
}
11431164
}
11441165
}
1166+
catch (InvocationTargetException | InstantiationException | IllegalAccessException e) {
1167+
throw new org.hibernate.InstantiationException( "Could not instantiate custom type", customType, e );
1168+
}
11451169

11461170
return getBuildingContext().getBuildingOptions().isAllowExtensionsInCdi()
11471171
? getUserTypeBean( customType, properties ).getBeanInstance()

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;
@@ -804,6 +806,10 @@ public void setTypeAnnotation(Annotation typeAnnotation) {
804806
this.typeAnnotation = typeAnnotation;
805807
}
806808

809+
public void setMemberDetails(MemberDetails memberDetails) {
810+
this.memberDetails = memberDetails;
811+
}
812+
807813
public Properties getTypeParameters() {
808814
return typeParameters;
809815
}
@@ -812,6 +818,10 @@ public Annotation getTypeAnnotation() {
812818
return typeAnnotation;
813819
}
814820

821+
public MemberDetails getMemberDetails() {
822+
return memberDetails;
823+
}
824+
815825
public void copyTypeFrom(SimpleValue sourceValue ) {
816826
setTypeName( sourceValue.getTypeName() );
817827
setTypeParameters( sourceValue.getTypeParameters() );
@@ -835,6 +845,7 @@ public boolean isSame(SimpleValue other) {
835845
&& Objects.equals( typeName, other.typeName )
836846
&& Objects.equals( typeParameters, other.typeParameters )
837847
&& Objects.equals( typeAnnotation, other.typeAnnotation )
848+
&& Objects.equals( memberDetails, other.memberDetails )
838849
&& Objects.equals( table, other.table )
839850
&& Objects.equals( foreignKeyName, other.foreignKeyName )
840851
&& Objects.equals( foreignKeyDefinition, other.foreignKeyDefinition );

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

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -214,8 +214,9 @@
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 and could
218+
* declare a constructor accepts the {@link java.lang.reflect.Member},
219+
* or the annotation type, or the annotation type and the member.
219220
* <p>
220221
* A custom type implemented as a {@code UserType} is treated as a
221222
* non-composite value, and does not have persistent attributes which

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

Lines changed: 90 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@
1818
import java.io.Serializable;
1919
import java.lang.annotation.Retention;
2020
import java.lang.annotation.Target;
21+
import java.lang.reflect.Field;
22+
import java.lang.reflect.Member;
2123
import java.sql.PreparedStatement;
2224
import java.sql.ResultSet;
2325
import java.sql.SQLException;
@@ -33,7 +35,8 @@
3335
import static java.sql.Types.VARCHAR;
3436
import static org.junit.jupiter.api.Assertions.assertEquals;
3537

36-
@Jpa(annotatedClasses = {MetaUserTypeTest.Thing.class, MetaUserTypeTest.Things.class})
38+
@Jpa(annotatedClasses = {MetaUserTypeTest.Thing.class, MetaUserTypeTest.SecondThing.class,
39+
MetaUserTypeTest.ThirdThing.class, MetaUserTypeTest.Things.class})
3740
public class MetaUserTypeTest {
3841

3942
@Test void test(EntityManagerFactoryScope scope) {
@@ -48,6 +51,30 @@ public class MetaUserTypeTest {
4851
assertEquals( Period.of( 1, 2, 3 ), thing.period );
4952
assertEquals( Period.ofDays( 42 ), thing.days );
5053
} );
54+
55+
scope.inTransaction( em -> {
56+
SecondThing thing = new SecondThing();
57+
thing.period = Period.of( 1, 2, 3 );
58+
thing.days = Period.ofDays( 42 );
59+
em.persist( thing );
60+
} );
61+
scope.inTransaction( em -> {
62+
SecondThing thing = em.find( SecondThing.class, 1 );
63+
assertEquals( Period.of( 1, 2, 3 ), thing.period );
64+
assertEquals( Period.ofDays( 42 ), thing.days );
65+
} );
66+
67+
scope.inTransaction( em -> {
68+
ThirdThing thing = new ThirdThing();
69+
thing.period = Period.of( 1, 2, 3 );
70+
thing.days = Period.ofDays( 42 );
71+
em.persist( thing );
72+
} );
73+
scope.inTransaction( em -> {
74+
ThirdThing thing = em.find( ThirdThing.class, 1 );
75+
assertEquals( Period.of( 1, 2, 3 ), thing.period );
76+
assertEquals( Period.ofDays( 42 ), thing.days );
77+
} );
5178
}
5279

5380
@Test void testCollection(EntityManagerFactoryScope scope) {
@@ -73,6 +100,24 @@ public class MetaUserTypeTest {
73100
Period days;
74101
}
75102

103+
@Entity static class SecondThing {
104+
@Id @GeneratedValue
105+
long id;
106+
@SecondTimePeriod
107+
Period period;
108+
@SecondTimePeriod(days = true)
109+
Period days;
110+
}
111+
112+
@Entity static class ThirdThing {
113+
@Id @GeneratedValue
114+
long id;
115+
@ThirdTimePeriod
116+
Period period;
117+
@ThirdTimePeriod(days = true)
118+
Period days;
119+
}
120+
76121
@Entity static class Things {
77122
@Id @GeneratedValue
78123
long id;
@@ -89,11 +134,52 @@ public class MetaUserTypeTest {
89134
boolean days() default false;
90135
}
91136

92-
static class PeriodType implements UserType<Period> {
93-
private final boolean days;
137+
@Type(SecondPeriodType.class)
138+
@Target({METHOD, FIELD})
139+
@Retention(RUNTIME)
140+
public @interface SecondTimePeriod {
141+
boolean days() default false;
142+
}
143+
144+
@Type(ThirdPeriodType.class)
145+
@Target({METHOD, FIELD})
146+
@Retention(RUNTIME)
147+
public @interface ThirdTimePeriod {
148+
boolean days() default false;
149+
}
150+
151+
static class PeriodType extends AbstractPeriodType {
94152

95153
PeriodType(TimePeriod timePeriod) {
96-
days = timePeriod.days();
154+
super(timePeriod.days());
155+
}
156+
157+
}
158+
159+
static class SecondPeriodType extends AbstractPeriodType {
160+
161+
SecondPeriodType(Member member) {
162+
super( ( (Field) member ).getAnnotation( SecondTimePeriod.class ).days() );
163+
}
164+
165+
}
166+
167+
static class ThirdPeriodType extends AbstractPeriodType {
168+
169+
ThirdPeriodType(ThirdTimePeriod timePeriod, Member member) {
170+
super(timePeriod.days());
171+
if ( !timePeriod.equals( ( (Field) member ).getAnnotation( ThirdTimePeriod.class ) )) {
172+
throw new IllegalArgumentException(member + " should be annotated with " + timePeriod);
173+
}
174+
}
175+
176+
}
177+
178+
static abstract class AbstractPeriodType implements UserType<Period> {
179+
private final boolean days;
180+
181+
AbstractPeriodType(boolean days) {
182+
this.days = days;
97183
}
98184

99185
@Override

0 commit comments

Comments
 (0)