Skip to content
Merged
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
Original file line number Diff line number Diff line change
Expand Up @@ -1193,6 +1193,9 @@ else if ( aggregateComponent != null ) {
}

public void fillSimpleValue() {

basicValue.setMemberDetails( memberDetails );

basicValue.setExplicitTypeParams( explicitLocalCustomTypeParams );

// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Expand Down
136 changes: 123 additions & 13 deletions hibernate-core/src/main/java/org/hibernate/mapping/BasicValue.java
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,20 @@

import java.lang.annotation.Annotation;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.ParameterizedType;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;
import java.util.function.Consumer;
import java.util.function.Function;

import org.hibernate.AnnotationException;
import org.hibernate.AssertionFailure;
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;
Expand Down Expand Up @@ -65,13 +70,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;
Expand All @@ -85,6 +92,7 @@

/**
* @author Steve Ebersole
* @author Yanming Zhou
*/
public class BasicValue extends SimpleValue
implements JdbcTypeIndicators, Resolvable, JpaAttributeConverterCreationContext {
Expand Down Expand Up @@ -1080,9 +1088,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,
Expand All @@ -1104,8 +1113,37 @@ 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 creationContext = 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;
}
};
final var typeInstance = instantiateUserType( explicitCustomType, creationContext, typeAnnotation );

if ( typeInstance instanceof AnnotationBasedUserType<?, ?> annotationBased ) {
if ( typeAnnotation == null ) {
throw new AnnotationException( String.format( "Custom type '%s' implements 'AnnotationBasedUserType' but no custom type annotation is present",
typeInstance.getClass().getName() ) );
}
initializeAnnotationBasedUserType( properties, typeAnnotation, memberDetails, annotationBased );
}

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

private <A extends Annotation> void initializeAnnotationBasedUserType(Properties properties,
Annotation typeAnnotation,
MemberDetails memberDetails,
AnnotationBasedUserType<A, ?> 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 <A extends Annotation> A castAnnotationType(
Annotation typeAnnotation, AnnotationBasedUserType<A, ?> 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 = (A) typeAnnotation;
return castAnnotation;
}
}
}
throw new AssertionFailure( "Could not find implementing interface" );
}

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, UserTypeCreationContext creationContext, Annotation typeAnnotation) {
try {
if ( typeAnnotation != null ) {
// attempt to instantiate it with the annotation and context object as constructor arguments
try {
final var constructor = customType.getDeclaredConstructor( typeAnnotation.annotationType(),
UserTypeCreationContext.class );
constructor.setAccessible( true );
return constructor.newInstance( typeAnnotation, creationContext );
}
catch (NoSuchMethodException ignored) {
// 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
}
}
}

// attempt to instantiate it with the context object as a constructor argument
try {
final var constructor = customType.getDeclaredConstructor( typeAnnotation.annotationType() );
final var constructor = customType.getDeclaredConstructor( UserTypeCreationContext.class );
constructor.setAccessible( true );
return constructor.newInstance( typeAnnotation );
return constructor.newInstance( creationContext );
}
catch ( NoSuchMethodException ignored ) {
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 );
}

}
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()
? getUserTypeBean( customType, creationContext.getParameters() ).getBeanInstance()
: FallbackBeanInstanceProducer.INSTANCE.produceBeanInstance( customType );
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -75,9 +76,10 @@ 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<?,?> ) ) {
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" );
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,7 @@ public abstract class SimpleValue implements KeyValue {
private String typeName;
private Properties typeParameters;
private Annotation typeAnnotation;
private MemberDetails memberDetails;
private boolean isVersion;
private boolean isNationalized;
private boolean isLob;
Expand Down Expand Up @@ -135,6 +136,7 @@ protected SimpleValue(SimpleValue original) {
this.typeName = original.typeName;
this.typeParameters = original.typeParameters == null ? null : new Properties( original.typeParameters );
this.typeAnnotation = original.typeAnnotation;
this.memberDetails = original.memberDetails;
this.isVersion = original.isVersion;
this.isNationalized = original.isNationalized;
this.isLob = original.isLob;
Expand Down Expand Up @@ -809,6 +811,10 @@ public void setTypeAnnotation(Annotation typeAnnotation) {
this.typeAnnotation = typeAnnotation;
}

public void setMemberDetails(MemberDetails memberDetails) {
this.memberDetails = memberDetails;
}

public Properties getTypeParameters() {
return typeParameters;
}
Expand All @@ -817,6 +823,10 @@ public Annotation getTypeAnnotation() {
return typeAnnotation;
}

public MemberDetails getMemberDetails() {
return memberDetails;
}

public void copyTypeFrom(SimpleValue sourceValue ) {
setTypeName( sourceValue.getTypeName() );
setTypeParameters( sourceValue.getTypeParameters() );
Expand All @@ -840,6 +850,7 @@ public boolean isSame(SimpleValue other) {
&& Objects.equals( typeName, other.typeName )
&& Objects.equals( typeParameters, other.typeParameters )
&& Objects.equals( typeAnnotation, other.typeAnnotation )
&& Objects.equals( memberDetails, other.memberDetails )
&& 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
@@ -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);
}
20 changes: 18 additions & 2 deletions hibernate-core/src/main/java/org/hibernate/usertype/UserType.java
Original file line number Diff line number Diff line change
Expand Up @@ -214,8 +214,23 @@
* }
* </pre>
* <p>
* Every implementor of {@code UserType} must be immutable and must
* declare a public default constructor.
* Every implementor of {@code UserType} must be immutable.
* <p>
* A custom type may receive parameters from its type annotation.
* The custom type may either:
* <ul>
* <li>implement {@link AnnotationBasedUserType}, and receive the
* annotation as an argument to
* {@link AnnotationBasedUserType#initialize},
* <li>declare a constructor with the same signature as
* {@link AnnotationBasedUserType#initialize},
* <li>declare a constructor which accepts just the annotation instance,
* or
* <li>declare a constructor which accepts just the {@link UserTypeCreationContext},
* or
* <li>declare only a default constructor, in which case it does not
* receive parameters.
* </ul>
* <p>
* A custom type implemented as a {@code UserType} is treated as a
* non-composite value, and does not have persistent attributes which
Expand All @@ -242,6 +257,7 @@
*
* @see org.hibernate.type.Type
* @see org.hibernate.type.CustomType
* @see org.hibernate.usertype.AnnotationBasedUserType
*
* @see org.hibernate.annotations.Type
* @see org.hibernate.annotations.TypeRegistration
Expand Down
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();

}
Loading