HHH-15959 add TypeBinders + fix multiple AttributeBinders on a single field

This commit is contained in:
Gavin 2023-01-01 18:21:07 +01:00 committed by Gavin King
parent e48a8120a9
commit df5980226c
14 changed files with 219 additions and 53 deletions

View File

@ -24,6 +24,8 @@ import static java.lang.annotation.RetentionPolicy.RUNTIME;
* will be called when the annotation is discovered by Hibernate.
*
* @author Gavin King
*
* @see TypeBinderType
*/
@Target(ANNOTATION_TYPE)
@Retention(RUNTIME)

View File

@ -0,0 +1,38 @@
/*
* Hibernate, Relational Persistence for Idiomatic Java
*
* License: GNU Lesser General Public License (LGPL), version 2.1 or later.
* See the lgpl.txt file in the root directory or <http://www.gnu.org/licenses/lgpl-2.1.html>.
*/
package org.hibernate.annotations;
import org.hibernate.Incubating;
import org.hibernate.binder.TypeBinder;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
import static java.lang.annotation.ElementType.ANNOTATION_TYPE;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
/**
* Associates a user-defined annotation with a {@link TypeBinder},
* allowing the annotation to drive some custom model binding.
* <p>
* The user-defined annotation may be used to annotate entity and
* embeddable classes. The {@code TypeBinder} will be called when
* the annotation is discovered by Hibernate.
*
* @author Gavin King
*
* @see AttributeBinderType
*/
@Target(ANNOTATION_TYPE)
@Retention(RUNTIME)
@Incubating
public @interface TypeBinderType {
/**
* @return a type which implements {@link TypeBinder}
*/
Class<? extends TypeBinder<?>> binder();
}

View File

@ -0,0 +1,48 @@
/*
* Hibernate, Relational Persistence for Idiomatic Java
*
* License: GNU Lesser General Public License (LGPL), version 2.1 or later.
* See the lgpl.txt file in the root directory or <http://www.gnu.org/licenses/lgpl-2.1.html>.
*/
package org.hibernate.binder;
import org.hibernate.Incubating;
import org.hibernate.boot.spi.MetadataBuildingContext;
import org.hibernate.mapping.Component;
import org.hibernate.mapping.PersistentClass;
import java.lang.annotation.Annotation;
/**
* Allows a user-written annotation to drive some customized model binding.
* <p>
* An implementation of this interface interacts directly with model objects
* like {@link PersistentClass} and {@link Component} to implement the
* semantics of some {@link org.hibernate.annotations.TypeBinderType
* custom mapping annotation}.
*
* @see org.hibernate.annotations.TypeBinderType
*
* @author Gavin King
*/
@Incubating
public interface TypeBinder<A extends Annotation> {
/**
* Perform some custom configuration of the model relating to the given annotated
* {@link PersistentClass entity class}.
*
* @param annotation an annotation of the entity class that is declared as an
* {@link org.hibernate.annotations.TypeBinderType}
* @param persistentClass the entity class
*/
void bind(A annotation, MetadataBuildingContext buildingContext, PersistentClass persistentClass);
/**
* Perform some custom configuration of the model relating to the given annotated
* {@link Component embeddable class}.
*
* @param annotation an annotation of the embeddable class that is declared as an
* {@link org.hibernate.annotations.TypeBinderType}
* @param embeddableClass the embeddable class
*/
void bind(A annotation, MetadataBuildingContext buildingContext, Component embeddableClass);
}

View File

@ -37,6 +37,5 @@ public class AttributeAccessorBinder implements AttributeBinder<AttributeAccesso
else {
throw new AnnotationException("'@AttributeAccessor' annotation must specify a 'strategy'");
}
}
}

View File

@ -6,6 +6,7 @@
*/
/**
* Built-in implementations of {@link org.hibernate.binder.AttributeBinder}.
* Built-in implementations of {@link org.hibernate.binder.AttributeBinder}
* and {@link org.hibernate.binder.TypeBinder}.
*/
package org.hibernate.binder.internal;

View File

@ -6,13 +6,23 @@
*/
/**
* This package defines an easy way to extend Hibernate with
* user-defined annotations that define customized O/R mappings
* of annotated entity attributes.
* <p>
* The meta-annotation
* {@link org.hibernate.annotations.AttributeBinderType}
* associates an {@link org.hibernate.binder.AttributeBinder}
* with a user-written annotation.
* This package defines an easy way to extend Hibernate with user-defined
* annotations that define customized O/R mappings of annotated entities
* and annotated entity attributes.
* <ul>
* <li>The meta-annotation
* {@link org.hibernate.annotations.TypeBinderType @TypeBinderType}
* associates a {@link org.hibernate.binder.TypeBinder} with a
* user-written annotation which targets entity and embeddable
* {@linkplain java.lang.annotation.ElementType#TYPE types}.
* <li>The meta-annotation
* {@link org.hibernate.annotations.AttributeBinderType @AttributeBinderType}
* associates an {@link org.hibernate.binder.AttributeBinder}
* with a user-written annotation which targets
* {@linkplain java.lang.annotation.ElementType#FIELD fields} and
* properties of entity types and embeddable classes.
*
* @see org.hibernate.binder.AttributeBinder
* @see org.hibernate.binder.TypeBinder
*/
package org.hibernate.binder;

View File

@ -19,10 +19,12 @@ import jakarta.persistence.OneToMany;
import jakarta.persistence.OneToOne;
import org.hibernate.AnnotationException;
import org.hibernate.annotations.Instantiator;
import org.hibernate.annotations.TypeBinderType;
import org.hibernate.annotations.common.reflection.XAnnotatedElement;
import org.hibernate.annotations.common.reflection.XClass;
import org.hibernate.annotations.common.reflection.XMethod;
import org.hibernate.annotations.common.reflection.XProperty;
import org.hibernate.binder.TypeBinder;
import org.hibernate.boot.spi.AccessType;
import org.hibernate.boot.spi.MetadataBuildingContext;
import org.hibernate.boot.spi.PropertyData;
@ -37,6 +39,7 @@ import org.hibernate.property.access.spi.PropertyAccessStrategy;
import org.hibernate.resource.beans.spi.ManagedBeanRegistry;
import org.hibernate.usertype.CompositeUserType;
import java.lang.annotation.Annotation;
import java.lang.reflect.Constructor;
import java.lang.reflect.Type;
import java.util.ArrayList;
@ -45,6 +48,7 @@ import java.util.List;
import java.util.Map;
import static org.hibernate.boot.model.internal.BinderHelper.isGlobalGeneratorNameGlobal;
import static org.hibernate.boot.model.internal.HCANNHelper.findContainingAnnotations;
import static org.hibernate.boot.model.internal.PropertyBinder.addElementsOfClass;
import static org.hibernate.boot.model.internal.PropertyBinder.processElementAnnotations;
import static org.hibernate.boot.model.internal.GeneratorBinder.buildGenerators;
@ -109,22 +113,31 @@ public class EmbeddableBinder {
actualColumns = null;
}
return bindEmbeddable(
return createEmbeddedProperty(
inferredData,
propertyHolder,
entityBinder.getPropertyAccessor( property ),
entityBinder,
isIdentifierMapper,
context,
isComponentEmbedded,
propertyBinder.isId(),
inheritanceStatePerClass,
referencedEntityName,
propertyName,
determineCustomInstantiator( property, returnedClass, context ),
compositeUserType,
actualColumns,
columns
bindEmbeddable(
inferredData,
propertyHolder,
entityBinder.getPropertyAccessor( property ),
entityBinder,
isIdentifierMapper,
context,
isComponentEmbedded,
propertyBinder.isId(),
inheritanceStatePerClass,
referencedEntityName,
propertyName,
determineCustomInstantiator( property, returnedClass, context ),
compositeUserType,
actualColumns,
columns
)
);
}
@ -134,7 +147,7 @@ public class EmbeddableBinder {
|| returnedClass.isAnnotationPresent( Embeddable.class );
}
private static PropertyBinder bindEmbeddable(
private static Component bindEmbeddable(
PropertyData inferredData,
PropertyHolder propertyHolder,
AccessType propertyAccessor,
@ -189,18 +202,44 @@ public class EmbeddableBinder {
component.setKey( true );
checkEmbeddedId( inferredData, propertyHolder, referencedEntityName, component );
}
callTypeBinders( component, context, inferredData.getPropertyClass() );
return component;
}
private static void callTypeBinders(Component component, MetadataBuildingContext context, XClass annotatedClass ) {
for ( Annotation containingAnnotation : findContainingAnnotations( annotatedClass, TypeBinderType.class) ) {
final TypeBinderType binderType = containingAnnotation.annotationType().getAnnotation( TypeBinderType.class );
try {
final TypeBinder binder = binderType.binder().newInstance();
binder.bind( containingAnnotation, context, component );
}
catch ( Exception e ) {
throw new AnnotationException( "error processing @TypeBinderType annotation '" + containingAnnotation + "'", e );
}
}
}
private static PropertyBinder createEmbeddedProperty(
PropertyData inferredData,
PropertyHolder propertyHolder,
EntityBinder entityBinder,
MetadataBuildingContext context,
boolean isComponentEmbedded,
boolean isId,
Map<XClass, InheritanceState> inheritanceStatePerClass,
Component component) {
final PropertyBinder binder = new PropertyBinder();
binder.setDeclaringClass( inferredData.getDeclaringClass() );
binder.setName( inferredData.getPropertyName() );
binder.setValue( component );
binder.setValue(component);
binder.setProperty( inferredData.getProperty() );
binder.setAccessType( inferredData.getDefaultAccess() );
binder.setEmbedded( isComponentEmbedded );
binder.setHolder( propertyHolder );
binder.setId( isId );
binder.setEntityBinder( entityBinder );
binder.setInheritanceStatePerClass( inheritanceStatePerClass );
binder.setBuildingContext( context );
binder.setEmbedded(isComponentEmbedded);
binder.setHolder(propertyHolder);
binder.setId(isId);
binder.setEntityBinder(entityBinder);
binder.setInheritanceStatePerClass(inheritanceStatePerClass);
binder.setBuildingContext(context);
binder.makePropertyAndBind();
return binder;
}

View File

@ -77,10 +77,12 @@ import org.hibernate.annotations.SelectBeforeUpdate;
import org.hibernate.annotations.Subselect;
import org.hibernate.annotations.Synchronize;
import org.hibernate.annotations.Tables;
import org.hibernate.annotations.TypeBinderType;
import org.hibernate.annotations.Where;
import org.hibernate.annotations.common.reflection.ReflectionManager;
import org.hibernate.annotations.common.reflection.XAnnotatedElement;
import org.hibernate.annotations.common.reflection.XClass;
import org.hibernate.binder.TypeBinder;
import org.hibernate.boot.model.IdentifierGeneratorDefinition;
import org.hibernate.boot.model.NamedEntityGraphDefinition;
import org.hibernate.boot.model.internal.InheritanceState.ElementsToProcess;
@ -131,6 +133,7 @@ import static org.hibernate.boot.model.internal.GeneratorBinder.makeIdGenerator;
import static org.hibernate.boot.model.internal.BinderHelper.toAliasEntityMap;
import static org.hibernate.boot.model.internal.BinderHelper.toAliasTableMap;
import static org.hibernate.boot.model.internal.EmbeddableBinder.fillEmbeddable;
import static org.hibernate.boot.model.internal.HCANNHelper.findContainingAnnotations;
import static org.hibernate.boot.model.internal.InheritanceState.getInheritanceStateOfSuperEntity;
import static org.hibernate.boot.model.internal.PropertyBinder.addElementsOfClass;
import static org.hibernate.boot.model.internal.PropertyBinder.processElementAnnotations;
@ -233,6 +236,20 @@ public class EntityBinder {
// comment, checkConstraint, and indexes are processed here
entityBinder.processComplementaryTableDefinitions();
bindCallbacks( clazzToProcess, persistentClass, context );
entityBinder.callTypeBinders( persistentClass );
}
private void callTypeBinders(PersistentClass persistentClass) {
for ( Annotation containingAnnotation : findContainingAnnotations( annotatedClass, TypeBinderType.class ) ) {
final TypeBinderType binderType = containingAnnotation.annotationType().getAnnotation( TypeBinderType.class );
try {
final TypeBinder binder = binderType.binder().newInstance();
binder.bind( containingAnnotation, context, persistentClass );
}
catch ( Exception e ) {
throw new AnnotationException( "error processing @TypeBinderType annotation '" + containingAnnotation + "'", e );
}
}
}
private void handleIdentifier(

View File

@ -9,6 +9,8 @@ package org.hibernate.boot.model.internal;
import java.lang.annotation.Annotation;
import java.lang.reflect.AnnotatedElement;
import java.lang.reflect.Member;
import java.util.ArrayList;
import java.util.List;
import org.hibernate.Internal;
import org.hibernate.annotations.common.reflection.XAnnotatedElement;
@ -156,21 +158,20 @@ public final class HCANNHelper {
*
* @implNote Searches only one level deep
*/
public static <A extends Annotation, T extends Annotation> A findContainingAnnotation(
public static List<Annotation> findContainingAnnotations(
XAnnotatedElement annotatedElement,
Class<T> annotationType) {
Class<? extends Annotation> annotationType) {
final List<Annotation> result = new ArrayList<>();
final Annotation[] annotations = annotatedElement.getAnnotations();
for ( Annotation annotation : annotations ) {
// annotation = @Sequence
final T metaAnn = annotation.annotationType().getAnnotation( annotationType );
final Annotation metaAnn = annotation.annotationType().getAnnotation( annotationType );
if ( metaAnn != null ) {
//noinspection unchecked
return (A) annotation;
result.add( annotation );
}
}
return null;
return result;
}
}

View File

@ -84,11 +84,12 @@ import static org.hibernate.boot.model.internal.EmbeddableBinder.createComposite
import static org.hibernate.boot.model.internal.EmbeddableBinder.createEmbeddable;
import static org.hibernate.boot.model.internal.EmbeddableBinder.isEmbedded;
import static org.hibernate.boot.model.internal.HCANNHelper.findAnnotation;
import static org.hibernate.boot.model.internal.HCANNHelper.findContainingAnnotation;
import static org.hibernate.boot.model.internal.HCANNHelper.findContainingAnnotations;
import static org.hibernate.boot.model.internal.TimeZoneStorageHelper.resolveTimeZoneStorageCompositeUserType;
import static org.hibernate.boot.model.internal.ToOneBinder.bindManyToOne;
import static org.hibernate.boot.model.internal.ToOneBinder.bindOneToOne;
import static org.hibernate.internal.util.StringHelper.qualify;
import static org.hibernate.internal.util.collections.CollectionHelper.combine;
import static org.hibernate.mapping.Constraint.hashedName;
/**
@ -269,15 +270,14 @@ public class PropertyBinder {
@SuppressWarnings({"rawtypes", "unchecked"})
private void callAttributeBinders(Property prop) {
final Annotation containingAnnotation = findContainingAnnotation( property, AttributeBinderType.class);
if ( containingAnnotation != null ) {
for ( Annotation containingAnnotation : findContainingAnnotations(property, AttributeBinderType.class ) ) {
final AttributeBinderType binderType = containingAnnotation.annotationType().getAnnotation( AttributeBinderType.class );
try {
final AttributeBinder binder = binderType.binder().newInstance();
binder.bind( containingAnnotation, buildingContext, entityBinder.getPersistentClass(), prop );
}
catch (Exception e) {
throw new AnnotationException( "error processing @AttributeBinderType annotation", e );
catch ( Exception e ) {
throw new AnnotationException( "error processing @AttributeBinderType annotation '" + containingAnnotation + "'", e );
}
}
}
@ -1270,16 +1270,21 @@ public class PropertyBinder {
+ "' belongs to an '@IdClass' and may not be annotated '@Id' or '@EmbeddedId'" );
}
final XProperty idProperty = inferredData.getProperty();
final Annotation idGeneratorAnnotation = findContainingAnnotation( idProperty, IdGeneratorType.class );
final Annotation generatorAnnotation = findContainingAnnotation( idProperty, ValueGenerationType.class );
//TODO: validate that we don't have too many generator annotations and throw
if ( idGeneratorAnnotation != null ) {
idValue.setCustomIdGeneratorCreator( identifierGeneratorCreator( idProperty, idGeneratorAnnotation ) );
final List<Annotation> idGeneratorAnnotations = findContainingAnnotations( idProperty, IdGeneratorType.class );
final List<Annotation> generatorAnnotations = findContainingAnnotations( idProperty, ValueGenerationType.class );
generatorAnnotations.removeAll( idGeneratorAnnotations );
if ( idGeneratorAnnotations.size() + generatorAnnotations.size() > 1 ) {
throw new AnnotationException( "Property '"+ getPath( propertyHolder, inferredData )
+ "' has too many generator annotations " + combine( idGeneratorAnnotations, generatorAnnotations ) );
}
else if ( generatorAnnotation != null ) {
if ( !idGeneratorAnnotations.isEmpty() ) {
idValue.setCustomIdGeneratorCreator( identifierGeneratorCreator( idProperty, idGeneratorAnnotations.get(0) ) );
}
else if ( !generatorAnnotations.isEmpty() ) {
// idValue.setCustomGeneratorCreator( generatorCreator( idProperty, generatorAnnotation ) );
throw new AnnotationException( "Property '"+ getPath( propertyHolder, inferredData )
+ "' is annotated '" + generatorAnnotation.annotationType() + "' which is not an '@IdGeneratorType'" );
+ "' is annotated '" + generatorAnnotations.get(0).annotationType()
+ "' which is not an '@IdGeneratorType'" );
}
else {
final XClass entityClass = inferredData.getClassOrElement();

View File

@ -51,9 +51,9 @@ import static org.hibernate.id.IdentifierGeneratorHelper.POST_INSERT_INDICATOR;
/**
* A mapping model object that represents an {@linkplain jakarta.persistence.Embeddable embeddable class}.
* <p>
* Note that the name of this class is historical and unfortunate. An embeddable class holds a "component"
* of the state of an entity. It has absolutely nothing to do with modularity in software engineering.
*
* @apiNote The name of this class is historical and unfortunate. An embeddable class holds a "component"
* of the state of an entity. It has absolutely nothing to do with modularity in software engineering.
*
* @author Gavin King
* @author Steve Ebersole

View File

@ -73,7 +73,7 @@ public class Table implements Serializable, ContributableDatabaseObject {
private List<Function<SqlStringGenerationContext, InitCommand>> initCommandProducers;
@Deprecated(since="6.2") @Remove
@Deprecated(since="6.2", forRemoval = true)
public Table() {
this( "orm" );
}

View File

@ -133,7 +133,10 @@
</li>
<li>
{@link org.hibernate.context.spi} defines support for context-bound "current" sessions
and contextual multi-tenancy,
and contextual multi-tenancy, and
</li>
<li>
{@link org.hibernate.binder} allows for user-defined mapping annotations.
</li>
</ul>
<p>

View File

@ -133,7 +133,10 @@
</li>
<li>
{@link org.hibernate.context.spi} defines support for context-bound "current" sessions
and contextual multi-tenancy,
and contextual multi-tenancy, and
</li>
<li>
{@link org.hibernate.binder} allows for user-defined mapping annotations.
</li>
</ul>
<p>