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}
* 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.
* 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,7 +113,15 @@ public class EmbeddableBinder {
actualColumns = null;
}
return bindEmbeddable(
return createEmbeddedProperty(
inferredData,
propertyHolder,
entityBinder,
context,
isComponentEmbedded,
propertyBinder.isId(),
inheritanceStatePerClass,
bindEmbeddable(
inferredData,
propertyHolder,
entityBinder.getPropertyAccessor( property ),
@ -125,6 +137,7 @@ public class EmbeddableBinder {
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,8 +51,8 @@ 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"
*
* @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

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>