From 808544579c8304257da05eb40a253e85fc3fdb78 Mon Sep 17 00:00:00 2001 From: Steve Ebersole Date: Tue, 12 Mar 2024 16:07:25 -0500 Subject: [PATCH] HHH-17460 - Ongoing JPA 32 work --- .../boot/model/internal/EntityBinder.java | 61 ++-- .../hibernate/boot/spi/BootstrapContext.java | 3 + .../hibernate/engine/OptimisticLockStyle.java | 18 +- .../internal/CallbackDefinitionResolver.java | 279 ++++++++++++++++++ 4 files changed, 307 insertions(+), 54 deletions(-) create mode 100644 hibernate-core/src/main/java/org/hibernate/jpa/event/internal/CallbackDefinitionResolver.java diff --git a/hibernate-core/src/main/java/org/hibernate/boot/model/internal/EntityBinder.java b/hibernate-core/src/main/java/org/hibernate/boot/model/internal/EntityBinder.java index 14e991d3f3..9882f713b0 100644 --- a/hibernate-core/src/main/java/org/hibernate/boot/model/internal/EntityBinder.java +++ b/hibernate-core/src/main/java/org/hibernate/boot/model/internal/EntityBinder.java @@ -63,7 +63,6 @@ import org.hibernate.annotations.Tables; import org.hibernate.annotations.TypeBinderType; import org.hibernate.annotations.View; import org.hibernate.annotations.Where; -import org.hibernate.annotations.common.reflection.ReflectionManager; import org.hibernate.binder.TypeBinder; import org.hibernate.boot.model.IdentifierGeneratorDefinition; import org.hibernate.boot.model.NamedEntityGraphDefinition; @@ -74,6 +73,7 @@ import org.hibernate.boot.model.naming.ImplicitEntityNameSource; import org.hibernate.boot.model.naming.NamingStrategyHelper; import org.hibernate.boot.model.relational.QualifiedTableName; import org.hibernate.boot.models.HibernateAnnotations; +import org.hibernate.boot.models.categorize.spi.JpaEventListener; import org.hibernate.boot.spi.AccessType; import org.hibernate.boot.spi.InFlightMetadataCollector; import org.hibernate.boot.spi.MetadataBuildingContext; @@ -85,6 +85,7 @@ import org.hibernate.internal.CoreMessageLogger; import org.hibernate.internal.util.StringHelper; import org.hibernate.jdbc.Expectation; import org.hibernate.internal.util.collections.CollectionHelper; +import org.hibernate.jpa.event.internal.CallbackDefinitionResolver; import org.hibernate.jpa.event.spi.CallbackType; import org.hibernate.mapping.BasicValue; import org.hibernate.mapping.CheckConstraint; @@ -166,8 +167,6 @@ import static org.hibernate.internal.util.StringHelper.isEmpty; import static org.hibernate.internal.util.StringHelper.isNotEmpty; import static org.hibernate.internal.util.StringHelper.nullIfEmpty; import static org.hibernate.internal.util.StringHelper.unqualify; -import static org.hibernate.jpa.event.internal.CallbackDefinitionResolverLegacyImpl.resolveEmbeddableCallbacks; -import static org.hibernate.jpa.event.internal.CallbackDefinitionResolverLegacyImpl.resolveEntityCallbacks; import static org.hibernate.mapping.SimpleValue.DEFAULT_ID_GEN_STRATEGY; @@ -655,7 +654,7 @@ public class EntityBinder { propertyAccessor, context ); - final InheritanceState state = inheritanceStates.get( idPropertyOnBaseClass.getClassOrElementType() ); + final InheritanceState state = inheritanceStates.get( idPropertyOnBaseClass.getClassOrElementType().determineRawClass() ); if ( state == null ) { return false; //while it is likely a user error, let's consider it is something that might happen } @@ -687,11 +686,10 @@ public class EntityBinder { // Fill simple value and property since and Id is a property final PersistentClass persistentClass = propertyHolder.getPersistentClass(); - if ( !(persistentClass instanceof RootClass) ) { + if ( !( persistentClass instanceof RootClass rootClass ) ) { throw new AnnotationException( "Entity '" + persistentClass.getEntityName() + "' is a subclass in an entity inheritance hierarchy and may not redefine the identifier of the root entity" ); } - final RootClass rootClass = (RootClass) persistentClass; final Component id = fillEmbeddable( propertyHolder, inferredData, @@ -1225,17 +1223,29 @@ public class EntityBinder { } } + /** + * See {@link JpaEventListener} for a better (?) alternative + */ private static void bindCallbacks(ClassDetails entityClass, PersistentClass persistentClass, MetadataBuildingContext context) { - final ReflectionManager reflection = context.getBootstrapContext().getReflectionManager(); for ( CallbackType callbackType : CallbackType.values() ) { - persistentClass.addCallbackDefinitions( resolveEntityCallbacks( reflection, entityClass, callbackType ) ); + persistentClass.addCallbackDefinitions( CallbackDefinitionResolver.resolveEntityCallbacks( + context, + entityClass, + callbackType + ) ); } + context.getMetadataCollector().addSecondPass( persistentClasses -> { for ( Property property : persistentClass.getDeclaredProperties() ) { final Class mappedClass = persistentClass.getMappedClass(); if ( property.isComposite() ) { for ( CallbackType type : CallbackType.values() ) { - property.addCallbackDefinitions( resolveEmbeddableCallbacks( reflection, mappedClass, property, type ) ); + property.addCallbackDefinitions( CallbackDefinitionResolver.resolveEmbeddableCallbacks( + context, + mappedClass, + property, + type + ) ); } } } @@ -2101,39 +2111,6 @@ public class EntityBinder { return null; } - private static String tableMember(Class annotationType, T sqlAnnotation) { - if (SQLInsert.class == annotationType) { - return ((SQLInsert) sqlAnnotation).table(); - } - else if (SQLUpdate.class == annotationType) { - return ((SQLUpdate) sqlAnnotation).table(); - } - else if (SQLDelete.class == annotationType) { - return ((SQLDelete) sqlAnnotation).table(); - } - else if (SQLDeleteAll.class == annotationType) { - return ((SQLDeleteAll) sqlAnnotation).table(); - } - else { - throw new AssertionFailure("Unknown annotation type"); - } - } - - private static Annotation[] valueMember(Class repeatableType, T sqlAnnotation) { - if (SQLInserts.class == repeatableType) { - return ((SQLInserts) sqlAnnotation).value(); - } - else if (SQLUpdates.class == repeatableType) { - return ((SQLUpdates) sqlAnnotation).value(); - } - else if (SQLDeletes.class == repeatableType) { - return ((SQLDeletes) sqlAnnotation).value(); - } - else { - throw new AssertionFailure("Unknown annotation type"); - } - } - //Used for @*ToMany @JoinTable public Join addJoinTable(AnnotationUsage joinTable, PropertyHolder holder, boolean noDelayInPkColumnCreation) { return addJoin( diff --git a/hibernate-core/src/main/java/org/hibernate/boot/spi/BootstrapContext.java b/hibernate-core/src/main/java/org/hibernate/boot/spi/BootstrapContext.java index 9983050576..9ad568e877 100644 --- a/hibernate-core/src/main/java/org/hibernate/boot/spi/BootstrapContext.java +++ b/hibernate-core/src/main/java/org/hibernate/boot/spi/BootstrapContext.java @@ -164,8 +164,11 @@ public interface BootstrapContext { * @apiNote Supported for internal use only. This method will go away as * we migrate away from Hibernate Commons Annotations to Jandex for * annotation handling and XMl to annotation merging. + * + * @deprecated HCANN is deprecated in favor of hibernate-models */ @Internal + @Deprecated ReflectionManager getReflectionManager(); /** diff --git a/hibernate-core/src/main/java/org/hibernate/engine/OptimisticLockStyle.java b/hibernate-core/src/main/java/org/hibernate/engine/OptimisticLockStyle.java index 2395d68ea1..cb0310eee1 100644 --- a/hibernate-core/src/main/java/org/hibernate/engine/OptimisticLockStyle.java +++ b/hibernate-core/src/main/java/org/hibernate/engine/OptimisticLockStyle.java @@ -38,18 +38,12 @@ public enum OptimisticLockStyle { ALL; public static OptimisticLockStyle fromLockType(OptimisticLockType type) { - switch ( type ) { - case VERSION: - return VERSION; - case NONE: - return NONE; - case DIRTY: - return DIRTY; - case ALL: - return ALL; - default: - throw new AssertionFailure( "Unrecognized OptimisticLockType" ); - } + return switch ( type ) { + case VERSION -> VERSION; + case NONE -> NONE; + case DIRTY -> DIRTY; + case ALL -> ALL; + }; } public boolean isAllOrDirty() { diff --git a/hibernate-core/src/main/java/org/hibernate/jpa/event/internal/CallbackDefinitionResolver.java b/hibernate-core/src/main/java/org/hibernate/jpa/event/internal/CallbackDefinitionResolver.java new file mode 100644 index 0000000000..2665e2d9e0 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/jpa/event/internal/CallbackDefinitionResolver.java @@ -0,0 +1,279 @@ +/* + * 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.jpa.event.internal; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Target; +import java.lang.reflect.Method; +import java.util.ArrayList; +import java.util.List; + +import org.hibernate.boot.models.categorize.spi.GlobalRegistrations; +import org.hibernate.boot.models.categorize.spi.JpaEventListener; +import org.hibernate.boot.spi.MetadataBuildingContext; +import org.hibernate.internal.util.ReflectHelper; +import org.hibernate.internal.util.collections.CollectionHelper; +import org.hibernate.jpa.event.spi.CallbackDefinition; +import org.hibernate.jpa.event.spi.CallbackType; +import org.hibernate.mapping.Property; +import org.hibernate.models.spi.AnnotationUsage; +import org.hibernate.models.spi.ClassDetails; +import org.hibernate.models.spi.ClassDetailsRegistry; +import org.hibernate.models.spi.MethodDetails; +import org.hibernate.models.spi.SourceModelBuildingContext; +import org.hibernate.property.access.spi.Getter; + +import org.jboss.logging.Logger; + +import jakarta.persistence.Entity; +import jakarta.persistence.EntityListeners; +import jakarta.persistence.ExcludeDefaultListeners; +import jakarta.persistence.ExcludeSuperclassListeners; +import jakarta.persistence.MappedSuperclass; +import jakarta.persistence.PersistenceException; + +/** + * Resolves JPA callback definitions + * + * @author Steve Ebersole + */ +public final class CallbackDefinitionResolver { + private static final Logger log = Logger.getLogger( CallbackDefinitionResolver.class ); + + public static List resolveEntityCallbacks( + MetadataBuildingContext metadataBuildingContext, + ClassDetails entityClass, + CallbackType callbackType) { + final GlobalRegistrations globalRegistrations = metadataBuildingContext.getMetadataCollector().getGlobalRegistrations(); + final List globalListenerRegistrations = globalRegistrations.getEntityListenerRegistrations(); + + List callbackDefinitions = new ArrayList<>(); + List callbacksMethodNames = new ArrayList<>(); + List orderedListeners = new ArrayList<>(); + + ClassDetails currentClazz = entityClass; + boolean stopListeners = false; + boolean stopDefaultListeners = false; + + do { + CallbackDefinition callbackDefinition = null; + final List methodsDetailsList = currentClazz.getMethods(); + for ( MethodDetails methodDetails : methodsDetailsList ) { + if ( !methodDetails.hasAnnotationUsage( callbackType.getCallbackAnnotation() ) ) { + continue; + } + if ( callbacksMethodNames.contains( methodDetails.getName() ) ) { + continue; + } + + //overridden method, remove the superclass overridden method + if ( callbackDefinition == null ) { + final Method javaMethod = (Method) methodDetails.toJavaMember(); + callbackDefinition = new EntityCallback.Definition( javaMethod, callbackType ); + Class returnType = javaMethod.getReturnType(); + Class[] args = javaMethod.getParameterTypes(); + if ( returnType != Void.TYPE || args.length != 0 ) { + throw new RuntimeException( + "Callback methods annotated on the bean class must return void and take no arguments: " + + callbackType.getCallbackAnnotation().getName() + " - " + methodDetails + ); + } + ReflectHelper.ensureAccessibility( javaMethod ); + if ( log.isDebugEnabled() ) { + log.debugf( + "Adding %s as %s callback for entity %s", + methodDetails.getName(), + callbackType.getCallbackAnnotation().getSimpleName(), + entityClass.getName() + ); + } + callbackDefinitions.add( 0, callbackDefinition ); //superclass first + callbacksMethodNames.add( 0, methodDetails.getName() ); + } + else { + throw new PersistenceException( + "You can only annotate one callback method with " + + callbackType.getCallbackAnnotation().getName() + " in bean class: " + entityClass.getName() + ); + } + } + if ( !stopListeners ) { + applyListeners( currentClazz, orderedListeners ); + stopListeners = currentClazz.hasAnnotationUsage( ExcludeSuperclassListeners.class ); + stopDefaultListeners = currentClazz.hasAnnotationUsage( ExcludeDefaultListeners.class ); + } + + do { + currentClazz = currentClazz.getSuperClass(); + } + while ( currentClazz != null + && !( currentClazz.hasAnnotationUsage( Entity.class ) + || currentClazz.hasAnnotationUsage( MappedSuperclass.class ) ) + ); + } + while ( currentClazz != null ); + + //handle default listeners + if ( !stopDefaultListeners ) { + if ( CollectionHelper.isNotEmpty( globalListenerRegistrations ) ) { + int defaultListenerSize = globalListenerRegistrations.size(); + for ( int i = defaultListenerSize - 1; i >= 0; i-- ) { + orderedListeners.add( globalListenerRegistrations.get( i ).getCallbackClass() ); + } + } + } + + for ( ClassDetails listenerClassDetails : orderedListeners ) { + CallbackDefinition callbackDefinition = null; + if ( listenerClassDetails != null ) { + for ( MethodDetails methodDetails : listenerClassDetails.getMethods() ) { + if ( methodDetails.hasAnnotationUsage( callbackType.getCallbackAnnotation() ) ) { + final String methodName = methodDetails.getName(); + //overridden method, remove the superclass overridden method + if ( callbackDefinition == null ) { + final Method method = (Method) methodDetails.toJavaMember(); + callbackDefinition = new ListenerCallback.Definition( + listenerClassDetails.toJavaClass(), + method, + callbackType + ); + + final Class returnType = method.getReturnType(); + final Class[] args = method.getParameterTypes(); + if ( returnType != Void.TYPE || args.length != 1 ) { + throw new PersistenceException( + "Callback methods annotated in a listener bean class must return void and take one argument: " + + callbackType.getCallbackAnnotation().getName() + " - " + methodDetails + ); + } + ReflectHelper.ensureAccessibility( method ); + if ( log.isDebugEnabled() ) { + log.debugf( + "Adding %s as %s callback for entity %s", + methodName, + callbackType.getCallbackAnnotation().getSimpleName(), + entityClass.getName() + ); + } + callbackDefinitions.add( 0, callbackDefinition ); // listeners first + } + else { + throw new PersistenceException( + "You can only annotate one callback method with " + + callbackType.getCallbackAnnotation().getName() + + " in bean class: " + entityClass.getName() + + " and callback listener: " + listenerClassDetails.getName() + ); + } + } + } + } + } + return callbackDefinitions; + } + + public static List resolveEmbeddableCallbacks( + MetadataBuildingContext metadataBuildingContext, + Class entityClass, + Property embeddableProperty, + CallbackType callbackType) { + final SourceModelBuildingContext hibernateModelsContext = metadataBuildingContext.getMetadataCollector().getSourceModelBuildingContext(); + final ClassDetailsRegistry classDetailsRegistry = hibernateModelsContext.getClassDetailsRegistry(); + + final Class embeddableClass = embeddableProperty.getType().getReturnedClass(); + final ClassDetails embeddableClassDetails = classDetailsRegistry.getClassDetails( embeddableClass.getName() ); + + final Getter embeddableGetter = embeddableProperty.getGetter( entityClass ); + final List callbackDefinitions = new ArrayList<>(); + final List callbacksMethodNames = new ArrayList<>(); + ClassDetails currentClazz = embeddableClassDetails; + do { + CallbackDefinition callbackDefinition = null; + final List methodsDetailsList = currentClazz.getMethods(); + for ( MethodDetails methodDetails : methodsDetailsList ) { + if ( !methodDetails.hasAnnotationUsage( callbackType.getCallbackAnnotation() ) ) { + continue; + } + + final Method method = (Method) methodDetails.toJavaMember(); + final String methodName = method.getName(); + + if ( callbacksMethodNames.contains( methodName ) ) { + throw new PersistenceException( + "You can only annotate one callback method with " + + callbackType.getCallbackAnnotation().getName() + " in bean class: " + currentClazz.getName() + ); + } + + //overridden method, remove the superclass overridden method + if ( callbackDefinition == null ) { + callbackDefinition = new EmbeddableCallback.Definition( embeddableGetter, method, callbackType ); + Class returnType = method.getReturnType(); + Class[] args = method.getParameterTypes(); + if ( returnType != Void.TYPE || args.length != 0 ) { + throw new RuntimeException( + "Callback methods annotated on the bean class must return void and take no arguments: " + + callbackType.getCallbackAnnotation().getName() + " - " + methodDetails + ); + } + ReflectHelper.ensureAccessibility( method ); + if ( log.isDebugEnabled() ) { + log.debugf( + "Adding %s as %s callback for entity %s", + methodName, + callbackType.getCallbackAnnotation().getSimpleName(), + currentClazz.getName() + ); + } + callbackDefinitions.add( 0, callbackDefinition ); //superclass first + callbacksMethodNames.add( 0, methodName ); + } + } + + do { + currentClazz = currentClazz.getSuperClass(); + } + while ( currentClazz != null && !currentClazz.hasAnnotationUsage( MappedSuperclass.class ) ); + } + while ( currentClazz != null ); + + return callbackDefinitions; + } + + private static boolean useAnnotationAnnotatedByListener; + + static { + //check whether reading annotations of annotations is useful or not + useAnnotationAnnotatedByListener = false; + Target target = EntityListeners.class.getAnnotation( Target.class ); + if ( target != null ) { + for ( ElementType type : target.value() ) { + if ( type.equals( ElementType.ANNOTATION_TYPE ) ) { + useAnnotationAnnotatedByListener = true; + break; + } + } + } + } + + private static void applyListeners(ClassDetails currentClazz, List listOfListeners) { + final AnnotationUsage entityListeners = currentClazz.getAnnotationUsage( EntityListeners.class ); + if ( entityListeners != null ) { + final List listeners = entityListeners.getList( "value" ); + listOfListeners.addAll( listeners ); + } + + if ( useAnnotationAnnotatedByListener ) { + final List> metaAnnotatedUsageList = currentClazz.getMetaAnnotated( EntityListeners.class ); + for ( AnnotationUsage metaAnnotatedUsage : metaAnnotatedUsageList ) { + final AnnotationUsage metaAnnotatedListeners = metaAnnotatedUsage.getAnnotationDescriptor().getAnnotationUsage( EntityListeners.class ); + final List listeners = metaAnnotatedListeners.getList( "value" ); + listOfListeners.addAll( listeners ); + } + } + } +}