package org.hibernate.jpa.event.internal;
import java.lang.annotation.Annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Target;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;
import javax.persistence.Entity;
import javax.persistence.EntityListeners;
import javax.persistence.ExcludeDefaultListeners;
import javax.persistence.ExcludeSuperclassListeners;
import javax.persistence.MappedSuperclass;
import javax.persistence.PersistenceException;
import org.hibernate.MappingException;
import org.hibernate.annotations.common.reflection.ClassLoadingException;
import org.hibernate.annotations.common.reflection.ReflectionManager;
import org.hibernate.annotations.common.reflection.XClass;
import org.hibernate.annotations.common.reflection.XMethod;
import org.hibernate.internal.util.ReflectHelper;
import org.hibernate.jpa.event.spi.Callback;
import org.hibernate.jpa.event.spi.CallbackBuilder;
import org.hibernate.jpa.event.spi.CallbackType;
import org.hibernate.mapping.Property;
import org.hibernate.property.access.spi.Getter;
import org.hibernate.resource.beans.spi.ManagedBeanRegistry;
import org.jboss.logging.Logger;
public class CallbackBuilderLegacyImpl implements CallbackBuilder {
private static final Logger log = Logger.getLogger( CallbackBuilderLegacyImpl.class );
private final ManagedBeanRegistry managedBeanRegistry;
private final ReflectionManager reflectionManager;
public CallbackBuilderLegacyImpl(ManagedBeanRegistry managedBeanRegistry, ReflectionManager reflectionManager) {
this.managedBeanRegistry = managedBeanRegistry;
this.reflectionManager = reflectionManager;
}
@Override
public void buildCallbacksForEntity(String entityClassName, CallbackRegistrar callbackRegistrar) {
final boolean debugEnabled = log.isDebugEnabled();
try {
final XClass entityXClass = reflectionManager.classForName( entityClassName );
final Class entityClass = reflectionManager.toClass( entityXClass );
for ( CallbackType callbackType : CallbackType.values() ) {
if ( callbackRegistrar.hasRegisteredCallbacks( entityClass, callbackType ) ) {
if ( debugEnabled ) {
log.debugf(
"CallbackRegistry reported that Class [%s] already had %s callbacks registered; " +
"assuming this means the class was mapped twice " +
"(using hbm.xml entity-name support) - skipping subsequent registrations",
entityClassName,
callbackType.getCallbackAnnotation().getSimpleName()
);
}
continue;
}
final Callback[] callbacks = resolveEntityCallbacks( entityXClass, callbackType, reflectionManager );
callbackRegistrar.registerCallbacks( entityClass, callbacks );
}
}
catch (ClassLoadingException e) {
throw new MappingException( "entity class not found: " + entityClassName, e );
}
}
@Override
public void buildCallbacksForEmbeddable(
Property embeddableProperty, String entityClassName, CallbackRegistrar callbackRegistrar) {
try {
final XClass entityXClass = reflectionManager.classForName( entityClassName );
final Class entityClass = reflectionManager.toClass( entityXClass );
for ( CallbackType callbackType : CallbackType.values() ) {
final Callback[] callbacks = resolveEmbeddableCallbacks(
entityClass,
embeddableProperty,
callbackType,
reflectionManager
);
callbackRegistrar.registerCallbacks( entityClass, callbacks );
}
}
catch (ClassLoadingException e) {
throw new MappingException( "Class not found: ", e );
}
}
@Override
public void release() {
}
@SuppressWarnings({"unchecked", "WeakerAccess"})
public Callback[] resolveEntityCallbacks(XClass beanClass, CallbackType callbackType, ReflectionManager reflectionManager) {
List<Callback> callbacks = new ArrayList<>();
List<String> callbacksMethodNames = new ArrayList<>();
List<Class> orderedListeners = new ArrayList<>();
XClass currentClazz = beanClass;
boolean stopListeners = false;
boolean stopDefaultListeners = false;
final boolean debugEnabled = log.isDebugEnabled();
do {
Callback callback = null;
List<XMethod> methods = currentClazz.getDeclaredMethods();
for ( final XMethod xMethod : methods ) {
if ( xMethod.isAnnotationPresent( callbackType.getCallbackAnnotation() ) ) {
Method method = reflectionManager.toMethod( xMethod );
final String methodName = method.getName();
if ( !callbacksMethodNames.contains( methodName ) ) {
if ( callback == null ) {
callback = new EntityCallback( 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() + " - " + xMethod
);
}
ReflectHelper.ensureAccessibility( method );
if ( debugEnabled ) {
log.debugf(
"Adding %s as %s callback for entity %s",
methodName,
callbackType.getCallbackAnnotation().getSimpleName(),
beanClass.getName()
);
}
callbacks.add( 0, callback );
callbacksMethodNames.add( 0, methodName );
}
else {
throw new PersistenceException(
"You can only annotate one callback method with "
+ callbackType.getCallbackAnnotation().getName() + " in bean class: " + beanClass.getName()
);
}
}
}
}
if ( !stopListeners ) {
getListeners( currentClazz, orderedListeners );
stopListeners = currentClazz.isAnnotationPresent( ExcludeSuperclassListeners.class );
stopDefaultListeners = currentClazz.isAnnotationPresent( ExcludeDefaultListeners.class );
}
do {
currentClazz = currentClazz.getSuperclass();
}
while ( currentClazz != null
&& !( currentClazz.isAnnotationPresent( Entity.class )
|| currentClazz.isAnnotationPresent( MappedSuperclass.class ) )
);
}
while ( currentClazz != null );
if ( !stopDefaultListeners ) {
List<Class> defaultListeners = (List<Class>) reflectionManager.getDefaults().get( EntityListeners.class );
if ( defaultListeners != null ) {
int defaultListenerSize = defaultListeners.size();
for ( int i = defaultListenerSize - 1; i >= 0; i-- ) {
orderedListeners.add( defaultListeners.get( i ) );
}
}
}
for ( Class listener : orderedListeners ) {
Callback callback = null;
if ( listener != null ) {
XClass xListener = reflectionManager.toXClass( listener );
callbacksMethodNames = new ArrayList<>();
List<XMethod> methods = xListener.getDeclaredMethods();
for ( final XMethod xMethod : methods ) {
if ( xMethod.isAnnotationPresent( callbackType.getCallbackAnnotation() ) ) {
final Method method = reflectionManager.toMethod( xMethod );
final String methodName = method.getName();
if ( !callbacksMethodNames.contains( methodName ) ) {
if ( callback == null ) {
callback = new ListenerCallback(
managedBeanRegistry.getBean( listener ),
method,
callbackType
);
Class returnType = method.getReturnType();
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() + " - " + method
);
}
ReflectHelper.ensureAccessibility( method );
if ( debugEnabled ) {
log.debugf(
"Adding %s as %s callback for entity %s",
methodName,
callbackType.getCallbackAnnotation().getSimpleName(),
beanClass.getName()
);
}
callbacks.add( 0, callback );
}
else {
throw new PersistenceException(
"You can only annotate one callback method with "
+ callbackType.getCallbackAnnotation().getName()
+ " in bean class: " + beanClass.getName()
+ " and callback listener: " + listener.getName()
);
}
}
}
}
}
}
return callbacks.toArray( new Callback[callbacks.size()] );
}
@SuppressWarnings({"unchecked", "WeakerAccess"})
public Callback[] resolveEmbeddableCallbacks(Class entityClass, Property embeddableProperty, CallbackType callbackType, ReflectionManager reflectionManager) {
final String embeddableClassName = embeddableProperty.getType().getReturnedClass().getName();
final XClass embeddableXClass = reflectionManager.classForName( embeddableClassName );
final Getter embeddableGetter = embeddableProperty.getGetter( entityClass );
final boolean debugEnabled = log.isDebugEnabled();
final List<Callback> callbacks = new ArrayList<>();
final List<String> callbacksMethodNames = new ArrayList<>();
XClass currentClazz = embeddableXClass;
do {
Callback callback = null;
List<XMethod> methods = currentClazz.getDeclaredMethods();
for ( final XMethod xMethod : methods ) {
if ( xMethod.isAnnotationPresent( callbackType.getCallbackAnnotation() ) ) {
Method method = reflectionManager.toMethod( xMethod );
final String methodName = method.getName();
if ( !callbacksMethodNames.contains( methodName ) ) {
if ( callback == null ) {
callback = new EmbeddableCallback( 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() + " - " + xMethod
);
}
ReflectHelper.ensureAccessibility( method );
if ( debugEnabled ) {
log.debugf(
"Adding %s as %s callback for entity %s",
methodName,
callbackType.getCallbackAnnotation().getSimpleName(),
embeddableXClass.getName()
);
}
callbacks.add( 0, callback );
callbacksMethodNames.add( 0, methodName );
}
else {
throw new PersistenceException(
"You can only annotate one callback method with "
+ callbackType.getCallbackAnnotation().getName() + " in bean class: " + embeddableXClass.getName()
);
}
}
}
}
do {
currentClazz = currentClazz.getSuperclass();
}
while ( currentClazz != null && !currentClazz.isAnnotationPresent( MappedSuperclass.class ) );
}
while ( currentClazz != null );
return callbacks.toArray( new Callback[callbacks.size()] );
}
private static boolean useAnnotationAnnotatedByListener;
static {
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;
}
}
}
}
private static void getListeners(XClass currentClazz, List<Class> orderedListeners) {
EntityListeners entityListeners = currentClazz.getAnnotation( EntityListeners.class );
if ( entityListeners != null ) {
Class[] classes = entityListeners.value();
int size = classes.length;
for ( int index = size - 1; index >= 0; index-- ) {
orderedListeners.add( classes[index] );
}
}
if ( useAnnotationAnnotatedByListener ) {
Annotation[] annotations = currentClazz.getAnnotations();
for ( Annotation annot : annotations ) {
entityListeners = annot.getClass().getAnnotation( EntityListeners.class );
if ( entityListeners != null ) {
Class[] classes = entityListeners.value();
int size = classes.length;
for ( int index = size - 1; index >= 0; index-- ) {
orderedListeners.add( classes[index] );
}
}
}
}
}
}