Skip to content
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
23 changes: 22 additions & 1 deletion hibernate-core/src/main/java/org/hibernate/annotations/Type.java
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@
* BigDecimal amount;
* </pre>
* <p>
* we may define an annotation type:
* we may define a custom annotation type:
* <pre>
* &#64;Retention(RUNTIME)
* &#64;Target({METHOD,FIELD})
Expand All @@ -47,6 +47,27 @@
* <p>
* which is much cleaner.
* <p>
* An implementation of {@link UserType} applied via a custom annotation
* may declare a constructor which accepts the annotation instance,
* allowing the annotation to be used to configure the type.
* <pre>
* &#64;Retention(RUNTIME)
* &#64;Target({METHOD,FIELD})
* &#64;Type(MonetaryAmountUserType.class)
* public @interface MonetaryAmount {
* public Currency currency();
* }
* </pre>
* <pre>
* public class MonetaryAmountUserType implements UserType&lt;Amount&gt; {
* private final Currency currency;
* public MonetaryAmountUserType(MonetaryAmount annotation) {
* currency = annotation.currency();
* }
* ...
* }
* </pre>
* <p>
* The use of a {@code UserType} is usually mutually exclusive with the
* compositional approach of {@link JavaType} and {@link JdbcType}.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,7 @@ public BasicValue.Resolution<?> resolve(
MetadataBuildingContext context,
JdbcTypeIndicators indicators) {
if ( isEmpty( localConfigParameters ) ) {
// we can use the re-usable resolution...
// we can use the reusable resolution...
if ( reusableResolution == null ) {
reusableResolution = createResolution( this.name, emptyMap(), indicators, context );
}
Expand Down Expand Up @@ -144,16 +144,15 @@ private static <T> BasicValue.Resolution<T> createResolution(
MetadataBuildingContext context) {
final var bootstrapContext = context.getBootstrapContext();
final var typeConfiguration = bootstrapContext.getTypeConfiguration();
final var 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 @@ -102,7 +102,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 @@ -338,8 +339,9 @@ private boolean applyCustomType(MemberDetails memberDetails, TypeDetails typeDet
final var modelContext = getSourceModelContext();
final var userTypeImpl = kind.mappingAccess.customType( memberDetails, modelContext );
if ( userTypeImpl != null ) {
applyExplicitType( userTypeImpl,
kind.mappingAccess.customTypeParameters( memberDetails, modelContext ) );
this.explicitCustomType = userTypeImpl;
this.explicitLocalCustomTypeParams = kind.mappingAccess.customTypeParameters( memberDetails, modelContext );
this.explicitCustomTypeAnnotation = kind.mappingAccess.customTypeAnnotation( memberDetails, modelContext );
// An explicit custom UserType has top precedence when we get to BasicValue resolution.
return true;
}
Expand All @@ -349,7 +351,8 @@ private boolean applyCustomType(MemberDetails memberDetails, TypeDetails typeDet
final var registeredUserTypeImpl =
getMetadataCollector().findRegisteredUserType( basicClass );
if ( registeredUserTypeImpl != null ) {
applyExplicitType( registeredUserTypeImpl, emptyMap() );
this.explicitCustomType = registeredUserTypeImpl;
this.explicitLocalCustomTypeParams = emptyMap();
return true;
}
}
Expand Down Expand Up @@ -384,11 +387,6 @@ private void prepareValue(MemberDetails value, TypeDetails typeDetails, @Nullabl
}
}

private void applyExplicitType(Class<? extends UserType<?>> userTypeImpl, Map<String,String> parameters) {
explicitCustomType = userTypeImpl;
explicitLocalTypeParams = parameters;
}

private void prepareCollectionId(MemberDetails attribute) {
final var collectionId = attribute.getDirectAnnotationUsage( CollectionId.class );
if ( collectionId == null ) {
Expand Down Expand Up @@ -1195,7 +1193,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 @@ -1211,6 +1209,10 @@ public void fillSimpleValue() {
basicValue.setTypeParameters( createDynamicParameterizedTypeParameters() );
}

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

if ( converterDescriptor != null ) {
basicValue.setJpaAttributeConverterDescriptor( converterDescriptor );
}
Expand Down Expand Up @@ -1282,8 +1284,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 @@ -1295,6 +1297,7 @@ private Map<String, Object> createDynamicParameterizedTypeParameters() {
private interface BasicMappingAccess {
Class<? extends UserType<?>> customType(MemberDetails attribute, ModelsContext context);
Map<String,String> customTypeParameters(MemberDetails attribute, ModelsContext context);
Annotation customTypeAnnotation(MemberDetails attribute, ModelsContext context);
}

private static class ValueMappingAccess implements BasicMappingAccess {
Expand All @@ -1311,6 +1314,12 @@ public Map<String,String> customTypeParameters(MemberDetails attribute, ModelsCo
final var customType = attribute.locateAnnotationUsage( Type.class, context );
return customType == null ? null : extractParameterMap( customType.parameters() );
}

@Override
public Annotation customTypeAnnotation(MemberDetails attribute, ModelsContext context) {
final var annotations = attribute.getMetaAnnotated( Type.class, context );
return annotations == null || annotations.isEmpty() ? null : annotations.get( 0 );
}
}

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

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

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

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

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

}

@Override
public Annotation customTypeAnnotation(MemberDetails attribute, ModelsContext context) {
final var annotations = attribute.getMetaAnnotated( MapKeyType.class, context );
return annotations == null || annotations.isEmpty() ? null : annotations.get( 0 );
}
}

private static class CollectionIdMappingAccess implements BasicMappingAccess {
Expand All @@ -1373,7 +1398,12 @@ public Class<? extends UserType<?>> customType(MemberDetails attribute, ModelsCo
public Map<String,String> customTypeParameters(MemberDetails attribute, ModelsContext context) {
final var customType = attribute.locateAnnotationUsage( CollectionIdType.class, context );
return customType == null ? null : extractParameterMap( customType.parameters() );
}

@Override
public Annotation customTypeAnnotation(MemberDetails attribute, ModelsContext context) {
final var annotations = attribute.getMetaAnnotated( CollectionIdType.class, context );
return annotations == null || annotations.isEmpty() ? null : annotations.get( 0 );
}
}

Expand All @@ -1389,6 +1419,11 @@ public Class<? extends UserType<?>> customType(MemberDetails attribute, ModelsCo
public Map<String,String> customTypeParameters(MemberDetails attribute, ModelsContext context) {
return emptyMap();
}

@Override
public Annotation customTypeAnnotation(MemberDetails attribute, ModelsContext 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 @@ -2029,9 +2029,9 @@ private BasicType<?> anyDiscriminatorType(Any anyBinding, TypeResolution discrim
);
}
else {
return metadataBuildingContext.getBootstrapContext()
.getTypeConfiguration().getBasicTypeRegistry()
.resolve( StandardBasicTypes.STRING );
return
metadataBuildingContext.getBootstrapContext().getTypeConfiguration().getBasicTypeRegistry()
.resolve( StandardBasicTypes.STRING );
}
}

Expand Down
38 changes: 31 additions & 7 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,8 @@
*/
package org.hibernate.mapping;

import java.lang.annotation.Annotation;
import java.lang.reflect.InvocationTargetException;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;
Expand Down Expand Up @@ -1055,13 +1057,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 var typeProperties = getCustomTypeProperties();
final var typeAnnotation = getTypeAnnotation();
resolution = new UserTypeResolution<>(
new CustomType<>(
getConfiguredUserTypeBean( explicitCustomType, getCustomTypeProperties() ),
getConfiguredUserTypeBean( explicitCustomType, typeProperties, typeAnnotation ),
getTypeConfiguration()
),
null,
getCustomTypeProperties()
typeProperties
);
}
}
Expand All @@ -1078,11 +1082,9 @@ private Properties getCustomTypeProperties() {
return properties;
}

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

if ( typeInstance instanceof TypeConfigurationAware configurationAware ) {
configurationAware.setTypeConfiguration( getTypeConfiguration() );
Expand All @@ -1103,6 +1105,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 var 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<? extends T> getUserTypeBean(Class<T> explicitCustomType, Properties properties) {
final var producer = getBootstrapContext().getCustomTypeProducer();
final var managedBeanRegistry = getManagedBeanRegistry();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,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 @@ -127,6 +128,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 @@ -792,11 +794,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 @@ -818,6 +828,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
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,13 @@
* And then use the {@code @TimePeriod} annotation to apply our {@code UserType}:
* <pre>&#64;TimePeriod Period period;</pre>
* <p>
* The {@code @TimePeriod} annotation might even have members:
* <pre>&#64;TimePeriod(precision=DAYS) Period period;</pre>
* <p>
* In this case, the implementation of {@code UserType} may declare a constructor
* which accepts the annotation type, and use the values assigned to the members
* to customize its behavior.
* <p>
* Finally, we could ask for our custom type to be used by default:
* <pre>&#64;TypeRegistration(basicClass = Period.class, userType = PeriodType.class)</pre>
* <p>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ public class BitSetHelper {
public static String bitSetToString(BitSet bitSet) {
StringBuilder builder = new StringBuilder();
for (long token : bitSet.toLongArray()) {
if (builder.length() > 0) {
if ( !builder.isEmpty() ) {
builder.append(DELIMITER);
}
builder.append(Long.toString(token, 2));
Expand Down
Loading