HHH-8706 - Delay registering of the entity listener class as long as possible

This commit is contained in:
Steve Ebersole 2016-01-22 19:17:06 -06:00
parent 92bce3913b
commit 6076298ce9
29 changed files with 549 additions and 228 deletions

View File

@ -33,5 +33,5 @@ public class Person {
joinColumns = @JoinColumn(name = "PERSON_ID", foreignKey = @ForeignKey(name = "PERSON_ID_FK")),
inverseJoinColumns = @JoinColumn(name = "PHONE_ID", foreignKey = @ForeignKey(name = "PHONE_ID_FK"))
)
private List<Phone> phones = new ArrayList<>();
private List<Phone> phones = new ArrayList<Phone>();
}

View File

@ -26,5 +26,5 @@ public class SchemaTwoEntity {
@OneToMany
@JoinColumn
private Set<SchemaOneEntity> schemaOneEntities = new HashSet<>();
private Set<SchemaOneEntity> schemaOneEntities = new HashSet<SchemaOneEntity>();
}

View File

@ -11,7 +11,7 @@ import java.io.Serializable;
import org.hibernate.event.internal.DefaultDeleteEventListener;
import org.hibernate.event.spi.DeleteEvent;
import org.hibernate.event.spi.EventSource;
import org.hibernate.jpa.event.internal.jpa.CallbackRegistryConsumer;
import org.hibernate.jpa.event.spi.jpa.CallbackRegistryConsumer;
import org.hibernate.jpa.event.spi.jpa.CallbackRegistry;
import org.hibernate.persister.entity.EntityPersister;

View File

@ -12,7 +12,7 @@ import org.hibernate.engine.spi.EntityEntry;
import org.hibernate.engine.spi.SessionImplementor;
import org.hibernate.engine.spi.Status;
import org.hibernate.event.internal.DefaultFlushEntityEventListener;
import org.hibernate.jpa.event.internal.jpa.CallbackRegistryConsumer;
import org.hibernate.jpa.event.spi.jpa.CallbackRegistryConsumer;
import org.hibernate.jpa.event.spi.jpa.CallbackRegistry;
import org.hibernate.metadata.ClassMetadata;
import org.hibernate.persister.entity.EntityPersister;

View File

@ -10,7 +10,7 @@ import java.io.Serializable;
import org.hibernate.event.internal.DefaultMergeEventListener;
import org.hibernate.event.spi.EventSource;
import org.hibernate.jpa.event.internal.jpa.CallbackRegistryConsumer;
import org.hibernate.jpa.event.spi.jpa.CallbackRegistryConsumer;
import org.hibernate.jpa.event.spi.jpa.CallbackRegistry;
/**

View File

@ -15,7 +15,7 @@ import org.hibernate.engine.spi.CascadingAction;
import org.hibernate.engine.spi.CascadingActions;
import org.hibernate.event.internal.DefaultPersistEventListener;
import org.hibernate.event.spi.EventSource;
import org.hibernate.jpa.event.internal.jpa.CallbackRegistryConsumer;
import org.hibernate.jpa.event.spi.jpa.CallbackRegistryConsumer;
import org.hibernate.jpa.event.spi.jpa.CallbackRegistry;
import org.hibernate.type.CollectionType;

View File

@ -8,8 +8,9 @@ package org.hibernate.jpa.event.internal.core;
import org.hibernate.event.spi.PostDeleteEvent;
import org.hibernate.event.spi.PostDeleteEventListener;
import org.hibernate.jpa.event.internal.jpa.CallbackRegistryConsumer;
import org.hibernate.jpa.event.spi.jpa.CallbackRegistryConsumer;
import org.hibernate.jpa.event.spi.jpa.CallbackRegistry;
import org.hibernate.jpa.event.spi.jpa.CallbackType;
import org.hibernate.persister.entity.EntityPersister;
/**
@ -37,6 +38,6 @@ public class JpaPostDeleteEventListener implements PostDeleteEventListener, Call
@Override
public boolean requiresPostCommitHanding(EntityPersister persister) {
return callbackRegistry.hasPostRemoveCallbacks( persister.getMappedClass() );
return callbackRegistry.hasRegisteredCallbacks( persister.getMappedClass(), CallbackType.POST_REMOVE );
}
}

View File

@ -8,8 +8,9 @@ package org.hibernate.jpa.event.internal.core;
import org.hibernate.event.spi.PostInsertEvent;
import org.hibernate.event.spi.PostInsertEventListener;
import org.hibernate.jpa.event.internal.jpa.CallbackRegistryConsumer;
import org.hibernate.jpa.event.spi.jpa.CallbackRegistryConsumer;
import org.hibernate.jpa.event.spi.jpa.CallbackRegistry;
import org.hibernate.jpa.event.spi.jpa.CallbackType;
import org.hibernate.persister.entity.EntityPersister;
/**
@ -40,6 +41,6 @@ public class JpaPostInsertEventListener implements PostInsertEventListener, Call
@Override
public boolean requiresPostCommitHanding(EntityPersister persister) {
return callbackRegistry.hasPostCreateCallbacks( persister.getMappedClass() );
return callbackRegistry.hasRegisteredCallbacks( persister.getMappedClass(), CallbackType.POST_PERSIST );
}
}

View File

@ -8,7 +8,7 @@ package org.hibernate.jpa.event.internal.core;
import org.hibernate.event.spi.PostLoadEvent;
import org.hibernate.event.spi.PostLoadEventListener;
import org.hibernate.jpa.event.internal.jpa.CallbackRegistryConsumer;
import org.hibernate.jpa.event.spi.jpa.CallbackRegistryConsumer;
import org.hibernate.jpa.event.spi.jpa.CallbackRegistry;
/**

View File

@ -17,8 +17,9 @@ import org.hibernate.event.spi.PostCollectionUpdateEvent;
import org.hibernate.event.spi.PostCollectionUpdateEventListener;
import org.hibernate.event.spi.PostUpdateEvent;
import org.hibernate.event.spi.PostUpdateEventListener;
import org.hibernate.jpa.event.internal.jpa.CallbackRegistryConsumer;
import org.hibernate.jpa.event.spi.jpa.CallbackRegistryConsumer;
import org.hibernate.jpa.event.spi.jpa.CallbackRegistry;
import org.hibernate.jpa.event.spi.jpa.CallbackType;
import org.hibernate.persister.entity.EntityPersister;
/**
@ -62,7 +63,7 @@ public class JpaPostUpdateEventListener
@Override
public boolean requiresPostCommitHanding(EntityPersister persister) {
return callbackRegistry.hasPostUpdateCallbacks( persister.getMappedClass() );
return callbackRegistry.hasRegisteredCallbacks( persister.getMappedClass(), CallbackType.POST_UPDATE );
}
@Override

View File

@ -10,7 +10,7 @@ import java.io.Serializable;
import org.hibernate.event.internal.DefaultSaveEventListener;
import org.hibernate.event.spi.EventSource;
import org.hibernate.jpa.event.internal.jpa.CallbackRegistryConsumer;
import org.hibernate.jpa.event.spi.jpa.CallbackRegistryConsumer;
import org.hibernate.jpa.event.spi.jpa.CallbackRegistry;
/**

View File

@ -10,7 +10,7 @@ import java.io.Serializable;
import org.hibernate.event.internal.DefaultSaveOrUpdateEventListener;
import org.hibernate.event.spi.EventSource;
import org.hibernate.jpa.event.internal.jpa.CallbackRegistryConsumer;
import org.hibernate.jpa.event.spi.jpa.CallbackRegistryConsumer;
import org.hibernate.jpa.event.spi.jpa.CallbackRegistry;
/**

View File

@ -0,0 +1,31 @@
/*
* 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.jpa;
import org.hibernate.jpa.event.spi.jpa.Callback;
import org.hibernate.jpa.event.spi.jpa.CallbackType;
/**
* @author Steve Ebersole
*/
public abstract class AbstractCallback implements Callback {
private final CallbackType callbackType;
public AbstractCallback(CallbackType callbackType) {
this.callbackType = callbackType;
}
@Override
public boolean isActive() {
return true;
}
@Override
public CallbackType getCallbackType() {
return callbackType;
}
}

View File

@ -1,76 +0,0 @@
/*
* 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.jpa;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import javax.enterprise.context.spi.CreationalContext;
import javax.enterprise.inject.spi.AnnotatedType;
import javax.enterprise.inject.spi.BeanManager;
import javax.enterprise.inject.spi.InjectionTarget;
import org.hibernate.jpa.event.spi.jpa.ListenerFactory;
/**
* CID-based implementation of the ListenerFactory contract. Listener instances are kept in a map keyed by Class
* to achieve singleton-ness.
*
* @author Steve Ebersole
*/
public class BeanManagerListenerFactory implements ListenerFactory {
private final BeanManager beanManager;
private final Map<Class,BeanMetaData> listeners = new ConcurrentHashMap<Class, BeanMetaData>();
public static BeanManagerListenerFactory fromBeanManagerReference(Object beanManagerReference) {
return new BeanManagerListenerFactory( ( BeanManager ) beanManagerReference );
}
public BeanManagerListenerFactory(BeanManager beanManager) {
this.beanManager = beanManager;
}
@Override
public <T> T buildListener(Class<T> listenerClass) {
BeanMetaData<T> beanMetaData = listeners.get( listenerClass );
if ( beanMetaData == null ) {
beanMetaData = new BeanMetaData<T>( listenerClass );
listeners.put( listenerClass, beanMetaData );
}
return beanMetaData.instance;
}
@Override
public void release() {
for ( BeanMetaData beanMetaData : listeners.values() ) {
beanMetaData.release();
}
listeners.clear();
}
private class BeanMetaData<T> {
private final InjectionTarget<T> injectionTarget;
private final CreationalContext<T> creationalContext;
private final T instance;
private BeanMetaData(Class<T> listenerClass) {
AnnotatedType<T> annotatedType = beanManager.createAnnotatedType( listenerClass );
this.injectionTarget = beanManager.createInjectionTarget( annotatedType );
this.creationalContext = beanManager.createCreationalContext( null );
this.instance = injectionTarget.produce( creationalContext );
injectionTarget.inject( this.instance, creationalContext );
injectionTarget.postConstruct( this.instance );
}
private void release() {
injectionTarget.preDestroy( instance );
injectionTarget.dispose( instance );
creationalContext.release();
}
}
}

View File

@ -8,16 +8,11 @@ package org.hibernate.jpa.event.internal.jpa;
import java.util.HashMap;
import javax.persistence.PersistenceException;
import javax.persistence.PostLoad;
import javax.persistence.PostPersist;
import javax.persistence.PostRemove;
import javax.persistence.PostUpdate;
import javax.persistence.PrePersist;
import javax.persistence.PreRemove;
import javax.persistence.PreUpdate;
import org.hibernate.jpa.event.spi.jpa.Callback;
import org.hibernate.jpa.event.spi.jpa.CallbackRegistry;
import org.hibernate.jpa.event.spi.jpa.CallbackType;
import org.hibernate.jpa.event.spi.jpa.EntityCallbackBuilder;
/**
* Keep track of all lifecycle callbacks and listeners for a given persistence unit
@ -26,7 +21,7 @@ import org.hibernate.jpa.event.spi.jpa.CallbackRegistry;
* @author Steve Ebersole
*/
@SuppressWarnings({"unchecked", "serial"})
public class CallbackRegistryImpl implements CallbackRegistry {
public class CallbackRegistryImpl implements CallbackRegistry, EntityCallbackBuilder.CallbackRegistrar {
private HashMap<Class, Callback[]> preCreates = new HashMap<Class, Callback[]>();
private HashMap<Class, Callback[]> postCreates = new HashMap<Class, Callback[]>();
private HashMap<Class, Callback[]> preRemoves = new HashMap<Class, Callback[]>();
@ -36,13 +31,27 @@ public class CallbackRegistryImpl implements CallbackRegistry {
private HashMap<Class, Callback[]> postLoads = new HashMap<Class, Callback[]>();
@Override
public void preCreate(Object bean) {
callback( preCreates.get( bean.getClass() ), bean );
public boolean hasRegisteredCallbacks(Class entityClass, CallbackType callbackType) {
final HashMap<Class, Callback[]> map = determineAppropriateCallbackMap( callbackType );
return notEmpty( map.get( entityClass ) );
}
@Override
public boolean hasPostCreateCallbacks(Class entityClass) {
return notEmpty( preCreates.get( entityClass ) );
public void registerCallbacks(Class entityClass, Callback[] callbacks) {
if ( callbacks == null || callbacks.length == 0 ) {
return;
}
final HashMap<Class, Callback[]> map = determineAppropriateCallbackMap( callbacks[0].getCallbackType() );
if ( map.containsKey( entityClass ) ) {
throw new PersistenceException( "Error build callback listeners; entity [" + entityClass.getName() + " was already processed" );
}
map.put( entityClass, callbacks );
}
@Override
public void preCreate(Object bean) {
callback( preCreates.get( bean.getClass() ), bean );
}
private boolean notEmpty(Callback[] callbacks) {
@ -59,11 +68,6 @@ public class CallbackRegistryImpl implements CallbackRegistry {
return callback( preUpdates.get( bean.getClass() ), bean );
}
@Override
public boolean hasPostUpdateCallbacks(Class entityClass) {
return notEmpty( postUpdates.get( entityClass ) );
}
@Override
public void postUpdate(Object bean) {
callback( postUpdates.get( bean.getClass() ), bean );
@ -74,11 +78,6 @@ public class CallbackRegistryImpl implements CallbackRegistry {
callback( preRemoves.get( bean.getClass() ), bean );
}
@Override
public boolean hasPostRemoveCallbacks(Class entityClass) {
return notEmpty( postRemoves.get( entityClass ) );
}
@Override
public void postRemove(Object bean) {
callback( postRemoves.get( bean.getClass() ), bean );
@ -101,56 +100,36 @@ public class CallbackRegistryImpl implements CallbackRegistry {
}
}
@Override
public boolean hasRegisteredCallbacks(Class entityClass, Class annotationClass) {
final HashMap<Class, Callback[]> map = determineAppropriateCallbackMap( annotationClass );
return map != null && map.containsKey( entityClass );
}
/**
* Great care should be taken calling this. Not a fan of it being public, but that is needed because of
* @param entityClass
* @param annotationClass
* @param callbacks
*/
public void addEntityCallbacks(Class entityClass, Class annotationClass, Callback[] callbacks) {
final HashMap<Class, Callback[]> map = determineAppropriateCallbackMap( annotationClass );
if ( map.containsKey( entityClass ) ) {
throw new PersistenceException( "Error build callback listeners; entity [" + entityClass.getName() + " was already processed" );
}
map.put( entityClass, callbacks );
}
private HashMap<Class, Callback[]> determineAppropriateCallbackMap(Class annotationClass) {
if ( PrePersist.class.equals( annotationClass ) ) {
private HashMap<Class, Callback[]> determineAppropriateCallbackMap(CallbackType callbackType) {
if ( callbackType == CallbackType.PRE_PERSIST ) {
return preCreates;
}
if ( PostPersist.class.equals( annotationClass ) ) {
if ( callbackType == CallbackType.POST_PERSIST ) {
return postCreates;
}
if ( PreRemove.class.equals( annotationClass ) ) {
if ( callbackType == CallbackType.PRE_REMOVE ) {
return preRemoves;
}
if ( PostRemove.class.equals( annotationClass ) ) {
if ( callbackType == CallbackType.POST_REMOVE ) {
return postRemoves;
}
if ( PreUpdate.class.equals( annotationClass ) ) {
if ( callbackType == CallbackType.PRE_UPDATE ) {
return preUpdates;
}
if ( PostUpdate.class.equals( annotationClass ) ) {
if ( callbackType == CallbackType.POST_UPDATE ) {
return postUpdates;
}
if ( PostLoad.class.equals( annotationClass ) ) {
if ( callbackType == CallbackType.POST_LOAD ) {
return postLoads;
}
throw new PersistenceException( "Unrecognized JPA callback annotation [" + annotationClass.getName() + "]" );
throw new PersistenceException( "Unrecognized JPA callback type [" + callbackType + "]" );
}
public void release() {
@ -165,4 +144,54 @@ public class CallbackRegistryImpl implements CallbackRegistry {
postLoads.clear();
}
// deprecations ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@Override
public boolean hasPostCreateCallbacks(Class entityClass) {
return notEmpty( preCreates.get( entityClass ) );
}
@Override
public boolean hasPostUpdateCallbacks(Class entityClass) {
return notEmpty( postUpdates.get( entityClass ) );
}
@Override
public boolean hasPostRemoveCallbacks(Class entityClass) {
return notEmpty( postRemoves.get( entityClass ) );
}
@Override
public boolean hasRegisteredCallbacks(Class entityClass, Class annotationClass) {
final HashMap<Class, Callback[]> map = determineAppropriateCallbackMap( toCallbackType( annotationClass ) );
return map != null && map.containsKey( entityClass );
}
private CallbackType toCallbackType(Class annotationClass) {
if ( annotationClass == CallbackType.POST_LOAD.getCallbackAnnotation() ) {
return CallbackType.POST_LOAD;
}
else if ( annotationClass == CallbackType.PRE_PERSIST.getCallbackAnnotation() ) {
return CallbackType.PRE_PERSIST;
}
else if ( annotationClass == CallbackType.POST_PERSIST.getCallbackAnnotation() ) {
return CallbackType.POST_PERSIST;
}
else if ( annotationClass == CallbackType.PRE_UPDATE.getCallbackAnnotation() ) {
return CallbackType.PRE_UPDATE;
}
else if ( annotationClass == CallbackType.POST_UPDATE.getCallbackAnnotation() ) {
return CallbackType.POST_UPDATE;
}
else if ( annotationClass == CallbackType.PRE_REMOVE.getCallbackAnnotation() ) {
return CallbackType.PRE_REMOVE;
}
else if ( annotationClass == CallbackType.POST_REMOVE.getCallbackAnnotation() ) {
return CallbackType.POST_REMOVE;
}
throw new PersistenceException( "Unrecognized JPA callback annotation [" + annotationClass + "]" );
}
}

View File

@ -10,6 +10,7 @@ import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import org.hibernate.jpa.event.spi.jpa.Callback;
import org.hibernate.jpa.event.spi.jpa.CallbackType;
/**
* Represents a JPA callback on the entity itself
@ -17,10 +18,11 @@ import org.hibernate.jpa.event.spi.jpa.Callback;
* @author <a href="mailto:kabir.khan@jboss.org">Kabir Khan</a>
* @author Steve Ebersole
*/
public class EntityCallback implements Callback {
private Method callbackMethod;
public class EntityCallback extends AbstractCallback implements Callback {
private final Method callbackMethod;
public EntityCallback(Method callbackMethod) {
public EntityCallback(Method callbackMethod, CallbackType callbackType) {
super( callbackType );
this.callbackMethod = callbackMethod;
}
@ -43,9 +45,4 @@ public class EntityCallback implements Callback {
throw new RuntimeException( e );
}
}
@Override
public boolean isActive() {
return true;
}
}

View File

@ -25,33 +25,36 @@ import org.hibernate.annotations.common.reflection.ReflectionManager;
import org.hibernate.annotations.common.reflection.XClass;
import org.hibernate.annotations.common.reflection.XMethod;
import org.hibernate.jpa.event.spi.jpa.Callback;
import org.hibernate.jpa.event.spi.jpa.CallbackType;
import org.hibernate.jpa.event.spi.jpa.EntityCallbackBuilder;
import org.hibernate.jpa.event.spi.jpa.ListenerFactory;
import org.jboss.logging.Logger;
/**
* @author <a href="mailto:kabir.khan@jboss.org">Kabir Khan</a>
* EntityCallbackBuilder implementation using HCANN ReflectionManager. "legacy" in that
* we want to move to Jandex instead.
*
* @author Steve Ebersole
*/
public class LegacyCallbackProcessor implements CallbackProcessor {
private static final Logger log = Logger.getLogger( LegacyCallbackProcessor.class );
public class EntityCallbackBuilderLegacyImpl implements EntityCallbackBuilder {
private static final Logger log = Logger.getLogger( EntityCallbackBuilderLegacyImpl.class );
private final ListenerFactory jpaListenerFactory;
private final ReflectionManager reflectionManager;
public LegacyCallbackProcessor(ListenerFactory jpaListenerFactory, ReflectionManager reflectionManager) {
public EntityCallbackBuilderLegacyImpl(ListenerFactory jpaListenerFactory, ReflectionManager reflectionManager) {
this.jpaListenerFactory = jpaListenerFactory;
this.reflectionManager = reflectionManager;
}
@Override
public void processCallbacksForEntity(Object entityObject, CallbackRegistryImpl callbackRegistry) {
final String entityClassName = (String) entityObject;
public void buildCallbacksForEntity(String entityClassName, CallbackRegistrar callbackRegistrar) {
try {
final XClass entityXClass = reflectionManager.classForName( entityClassName );
final Class entityClass = reflectionManager.toClass( entityXClass );
for ( Class annotationClass : CALLBACK_ANNOTATION_CLASSES ) {
if ( callbackRegistry.hasRegisteredCallbacks( entityClass, annotationClass ) ) {
for ( CallbackType callbackType : CallbackType.values() ) {
if ( callbackRegistrar.hasRegisteredCallbacks( entityClass, callbackType ) ) {
// this most likely means we have a class mapped multiple times using the hbm.xml
// "entity name" feature
log.debugf(
@ -59,12 +62,12 @@ public class LegacyCallbackProcessor implements CallbackProcessor {
"assuming this means the class was mapped twice " +
"(using hbm.xml entity-name support) - skipping subsequent registrations",
entityClassName,
annotationClass.getSimpleName()
callbackType.getCallbackAnnotation().getSimpleName()
);
continue;
}
final Callback[] callbacks = resolveCallbacks( entityXClass, annotationClass, reflectionManager );
callbackRegistry.addEntityCallbacks( entityClass, annotationClass, callbacks );
final Callback[] callbacks = resolveCallbacks( entityXClass, callbackType, reflectionManager );
callbackRegistrar.registerCallbacks( entityClass, callbacks );
}
}
catch (ClassLoadingException e) {
@ -72,7 +75,12 @@ public class LegacyCallbackProcessor implements CallbackProcessor {
}
}
public Callback[] resolveCallbacks(XClass beanClass, Class annotation, ReflectionManager reflectionManager) {
@Override
public void release() {
// nothign to do
}
public Callback[] resolveCallbacks(XClass beanClass, CallbackType callbackType, ReflectionManager reflectionManager) {
List<Callback> callbacks = new ArrayList<Callback>();
List<String> callbacksMethodNames = new ArrayList<String>(); //used to track overridden methods
List<Class> orderedListeners = new ArrayList<Class>();
@ -83,26 +91,26 @@ public class LegacyCallbackProcessor implements CallbackProcessor {
Callback callback = null;
List<XMethod> methods = currentClazz.getDeclaredMethods();
for ( final XMethod xMethod : methods ) {
if ( xMethod.isAnnotationPresent( annotation ) ) {
if ( xMethod.isAnnotationPresent( callbackType.getCallbackAnnotation() ) ) {
Method method = reflectionManager.toMethod( xMethod );
final String methodName = method.getName();
if ( !callbacksMethodNames.contains( methodName ) ) {
//overridden method, remove the superclass overridden method
if ( callback == null ) {
callback = new EntityCallback( method );
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: " + annotation
.getName() + " - " + xMethod
"Callback methods annotated on the bean class must return void and take no arguments: "
+ callbackType.getCallbackAnnotation().getName() + " - " + xMethod
);
}
method.setAccessible( true );
log.debugf(
"Adding %s as %s callback for entity %s",
methodName,
annotation.getSimpleName(),
callbackType.getCallbackAnnotation().getSimpleName(),
beanClass.getName()
);
callbacks.add( 0, callback ); //superclass first
@ -111,7 +119,7 @@ public class LegacyCallbackProcessor implements CallbackProcessor {
else {
throw new PersistenceException(
"You can only annotate one callback method with "
+ annotation.getName() + " in bean class: " + beanClass.getName()
+ callbackType.getCallbackAnnotation().getName() + " in bean class: " + beanClass.getName()
);
}
}
@ -152,20 +160,24 @@ public class LegacyCallbackProcessor implements CallbackProcessor {
callbacksMethodNames = new ArrayList<String>();
List<XMethod> methods = xListener.getDeclaredMethods();
for ( final XMethod xMethod : methods ) {
if ( xMethod.isAnnotationPresent( annotation ) ) {
if ( xMethod.isAnnotationPresent( callbackType.getCallbackAnnotation() ) ) {
final Method method = reflectionManager.toMethod( xMethod );
final String methodName = method.getName();
if ( !callbacksMethodNames.contains( methodName ) ) {
//overridden method, remove the superclass overridden method
if ( callback == null ) {
callback = new ListenerCallback( jpaListenerFactory.buildListener( listener ), method );
callback = new ListenerCallback(
jpaListenerFactory.buildListener( 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: " + annotation
.getName() + " - " + method
"Callback methods annotated in a listener bean class must return void and take one argument: "
+ callbackType.getCallbackAnnotation().getName() + " - " + method
);
}
if ( !method.isAccessible() ) {
@ -174,7 +186,7 @@ public class LegacyCallbackProcessor implements CallbackProcessor {
log.debugf(
"Adding %s as %s callback for entity %s",
methodName,
annotation.getSimpleName(),
callbackType.getCallbackAnnotation().getSimpleName(),
beanClass.getName()
);
callbacks.add( 0, callback ); // listeners first
@ -182,8 +194,9 @@ public class LegacyCallbackProcessor implements CallbackProcessor {
else {
throw new PersistenceException(
"You can only annotate one callback method with "
+ annotation.getName() + " in bean class: " + beanClass.getName() + " and callback listener: "
+ listener.getName()
+ callbackType.getCallbackAnnotation().getName()
+ " in bean class: " + beanClass.getName()
+ " and callback listener: " + listener.getName()
);
}
}
@ -232,9 +245,4 @@ public class LegacyCallbackProcessor implements CallbackProcessor {
}
}
}
@Override
public void release() {
// nothing to do here
}
}

View File

@ -10,6 +10,8 @@ import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import org.hibernate.jpa.event.spi.jpa.Callback;
import org.hibernate.jpa.event.spi.jpa.CallbackType;
import org.hibernate.jpa.event.spi.jpa.Listener;
/**
* Represents a JPA callback using a dedicated listener
@ -17,11 +19,12 @@ import org.hibernate.jpa.event.spi.jpa.Callback;
* @author <a href="mailto:kabir.khan@jboss.org">Kabir Khan</a>
* @author Steve Ebersole
*/
public class ListenerCallback implements Callback {
public class ListenerCallback extends AbstractCallback implements Callback {
private final Method callbackMethod;
private final Object listenerInstance;
private final Listener listenerInstance;
public ListenerCallback(Object listenerInstance, Method callbackMethod) {
public ListenerCallback(Listener listenerInstance, Method callbackMethod, CallbackType callbackType) {
super( callbackType );
this.listenerInstance = listenerInstance;
this.callbackMethod = callbackMethod;
}
@ -29,7 +32,7 @@ public class ListenerCallback implements Callback {
@Override
public boolean performCallback(Object entity) {
try {
callbackMethod.invoke( listenerInstance, entity );
callbackMethod.invoke( listenerInstance.getListener(), entity );
return true;
}
catch (InvocationTargetException e) {
@ -45,9 +48,4 @@ public class ListenerCallback implements Callback {
throw new RuntimeException( e );
}
}
@Override
public boolean isActive() {
return true;
}
}

View File

@ -0,0 +1,161 @@
/*
* 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.jpa;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import javax.enterprise.context.spi.CreationalContext;
import javax.enterprise.inject.spi.AnnotatedType;
import javax.enterprise.inject.spi.BeanManager;
import javax.enterprise.inject.spi.InjectionTarget;
import org.hibernate.HibernateException;
import org.hibernate.jpa.event.spi.jpa.ExtendedBeanManager;
import org.hibernate.jpa.event.spi.jpa.Listener;
import org.hibernate.jpa.event.spi.jpa.ListenerFactory;
/**
* CDI-based implementation of the ListenerFactory contract. Listener instances are
* kept in a map keyed by Class to ensure single-instance-ness.
*
* @author Steve Ebersole
*/
public class ListenerFactoryBeanManagerImpl implements ListenerFactory, ExtendedBeanManager.LifecycleListener {
private final BeanManager beanManager;
private final boolean extendedBm;
private final Map<Class,ListenerImplementor> listenerMap = new ConcurrentHashMap<Class, ListenerImplementor>();
public static ListenerFactoryBeanManagerImpl fromBeanManagerReference(Object reference) {
return new ListenerFactoryBeanManagerImpl( (BeanManager) reference );
}
public ListenerFactoryBeanManagerImpl(BeanManager beanManager) {
this.beanManager = beanManager;
if ( beanManager instanceof ExtendedBeanManager ) {
( (ExtendedBeanManager) beanManager ).registerLifecycleListener( this );
extendedBm = true;
}
else {
extendedBm = false;
}
}
@Override
@SuppressWarnings("unchecked")
public <T> Listener<T> buildListener(Class<T> listenerClass) {
ListenerImplementor listenerImpl = listenerMap.get( listenerClass );
if ( listenerImpl == null ) {
listenerImpl = makeListener( listenerClass );
listenerMap.put( listenerClass, listenerImpl );
}
return (Listener<T>) listenerImpl;
}
private <T> ListenerImplementor makeListener(Class<T> listenerClass) {
if ( extendedBm ) {
return new ListenerExtendedImpl<T>( listenerClass );
}
else {
return new ListenerBasicImpl<T>( listenerClass );
}
}
@Override
public void release() {
for ( ListenerImplementor listenerImpl : listenerMap.values() ) {
listenerImpl.release();
}
listenerMap.clear();
}
@Override
public void beanManagerInitialized() {
for ( ListenerImplementor listenerImpl : listenerMap.values() ) {
// if the entries are not ListenerExtendedImpl instances we have serious issues...
( (ListenerExtendedImpl) listenerImpl ).initialize();
}
}
private interface ListenerImplementor<T> extends Listener<T> {
void release();
}
private class ListenerBasicImpl<T> implements ListenerImplementor<T> {
private final InjectionTarget<T> injectionTarget;
private final CreationalContext<T> creationalContext;
private final T listenerInstance;
private ListenerBasicImpl(Class<T> listenerClass) {
AnnotatedType<T> annotatedType = beanManager.createAnnotatedType( listenerClass );
this.injectionTarget = beanManager.createInjectionTarget( annotatedType );
this.creationalContext = beanManager.createCreationalContext( null );
this.listenerInstance = injectionTarget.produce( creationalContext );
injectionTarget.inject( this.listenerInstance, creationalContext );
injectionTarget.postConstruct( this.listenerInstance );
}
@Override
public T getListener() {
return listenerInstance;
}
public void release() {
injectionTarget.preDestroy( listenerInstance );
injectionTarget.dispose( listenerInstance );
creationalContext.release();
}
}
private class ListenerExtendedImpl<T> implements ListenerImplementor<T> {
private final Class<T> listenerClass;
private boolean initialized = false;
private InjectionTarget<T> injectionTarget;
private CreationalContext<T> creationalContext;
private T listenerInstance;
private ListenerExtendedImpl(Class<T> listenerClass) {
this.listenerClass = listenerClass;
}
public void initialize() {
AnnotatedType<T> annotatedType = beanManager.createAnnotatedType( listenerClass );
this.injectionTarget = beanManager.createInjectionTarget( annotatedType );
this.creationalContext = beanManager.createCreationalContext( null );
this.listenerInstance = injectionTarget.produce( creationalContext );
injectionTarget.inject( this.listenerInstance, creationalContext );
injectionTarget.postConstruct( this.listenerInstance );
this.initialized = true;
}
@Override
public T getListener() {
if ( !initialized ) {
throw new HibernateException( "CDI not initialized as expected" );
}
return listenerInstance;
}
public void release() {
if ( !initialized ) {
// log
return;
}
injectionTarget.preDestroy( listenerInstance );
injectionTarget.dispose( listenerInstance );
creationalContext.release();
}
}
}

View File

@ -9,6 +9,7 @@ package org.hibernate.jpa.event.internal.jpa;
import java.util.concurrent.ConcurrentHashMap;
import javax.persistence.PersistenceException;
import org.hibernate.jpa.event.spi.jpa.Listener;
import org.hibernate.jpa.event.spi.jpa.ListenerFactory;
/**
@ -17,17 +18,18 @@ import org.hibernate.jpa.event.spi.jpa.ListenerFactory;
*
* @author Steve Ebersole
*/
public class StandardListenerFactory implements ListenerFactory {
public class ListenerFactoryStandardImpl implements ListenerFactory {
private final ConcurrentHashMap listenerInstances = new ConcurrentHashMap();
private final ConcurrentHashMap<Class,Listener> listenerInstances = new ConcurrentHashMap<Class,Listener>();
@Override
@SuppressWarnings("unchecked")
public <T> T buildListener(Class<T> listenerClass) {
Object listenerInstance = listenerInstances.get( listenerClass );
if ( listenerInstance == null ) {
public <T> Listener<T> buildListener(Class<T> listenerClass) {
Listener listenerImpl = listenerInstances.get( listenerClass );
if ( listenerImpl == null ) {
try {
listenerInstance = listenerClass.newInstance();
T listenerInstance = listenerClass.newInstance();
listenerImpl = new ListenerImpl( listenerInstance );
}
catch (Exception e) {
throw new PersistenceException(
@ -35,16 +37,32 @@ public class StandardListenerFactory implements ListenerFactory {
e
);
}
Object existing = listenerInstances.putIfAbsent( listenerClass, listenerInstance );
Listener existing = listenerInstances.putIfAbsent(
listenerClass,
listenerImpl
);
if ( existing != null ) {
listenerInstance = existing;
listenerImpl = existing;
}
}
return (T) listenerInstance;
return (Listener<T>) listenerImpl;
}
@Override
public void release() {
listenerInstances.clear();
}
private static class ListenerImpl<T> implements Listener<T> {
private final T listenerInstance;
public ListenerImpl(T listenerInstance) {
this.listenerInstance = listenerInstance;
}
@Override
public T getListener() {
return listenerInstance;
}
}
}

View File

@ -40,11 +40,11 @@ import org.hibernate.jpa.event.internal.core.JpaPostLoadEventListener;
import org.hibernate.jpa.event.internal.core.JpaPostUpdateEventListener;
import org.hibernate.jpa.event.internal.core.JpaSaveEventListener;
import org.hibernate.jpa.event.internal.core.JpaSaveOrUpdateEventListener;
import org.hibernate.jpa.event.internal.jpa.CallbackProcessor;
import org.hibernate.jpa.event.internal.jpa.CallbackRegistryConsumer;
import org.hibernate.jpa.event.internal.jpa.EntityCallbackBuilderLegacyImpl;
import org.hibernate.jpa.event.spi.jpa.CallbackRegistryConsumer;
import org.hibernate.jpa.event.internal.jpa.CallbackRegistryImpl;
import org.hibernate.jpa.event.internal.jpa.LegacyCallbackProcessor;
import org.hibernate.jpa.event.internal.jpa.StandardListenerFactory;
import org.hibernate.jpa.event.internal.jpa.ListenerFactoryStandardImpl;
import org.hibernate.jpa.event.spi.jpa.EntityCallbackBuilder;
import org.hibernate.jpa.event.spi.jpa.ListenerFactory;
import org.hibernate.mapping.PersistentClass;
import org.hibernate.service.spi.ServiceRegistryImplementor;
@ -57,7 +57,7 @@ import org.hibernate.service.spi.SessionFactoryServiceRegistry;
*/
public class JpaIntegrator implements Integrator {
private ListenerFactory jpaListenerFactory;
private CallbackProcessor callbackProcessor;
private EntityCallbackBuilder entityCallbackBuilder;
private CallbackRegistryImpl callbackRegistry;
private static final DuplicationStrategy JPA_DUPLICATION_STRATEGY = new JPADuplicationStrategy();
@ -128,16 +128,16 @@ public class JpaIntegrator implements Integrator {
this.callbackRegistry = new CallbackRegistryImpl();
final Object beanManagerRef = sessionFactory.getSessionFactoryOptions().getBeanManagerReference();
this.jpaListenerFactory = beanManagerRef == null
? new StandardListenerFactory()
? new ListenerFactoryStandardImpl()
: buildBeanManagerListenerFactory( beanManagerRef );
this.callbackProcessor = new LegacyCallbackProcessor( jpaListenerFactory, reflectionManager );
this.entityCallbackBuilder = new EntityCallbackBuilderLegacyImpl( jpaListenerFactory, reflectionManager );
for ( PersistentClass persistentClass : metadata.getEntityBindings() ) {
if ( persistentClass.getClassName() == null ) {
// we can have non java class persisted by hibernate
continue;
}
callbackProcessor.processCallbacksForEntity( persistentClass.getClassName(), callbackRegistry );
entityCallbackBuilder.buildCallbacksForEntity( persistentClass.getClassName(), callbackRegistry );
}
for ( EventType eventType : EventType.values() ) {
@ -150,7 +150,7 @@ public class JpaIntegrator implements Integrator {
}
}
private static final String CDI_LISTENER_FACTORY_CLASS = "org.hibernate.jpa.event.internal.jpa.BeanManagerListenerFactory";
private static final String CDI_LISTENER_FACTORY_CLASS = "org.hibernate.jpa.event.internal.jpa.ListenerFactoryBeanManagerImpl";
private ListenerFactory buildBeanManagerListenerFactory(Object beanManagerRef) {
try {
@ -191,8 +191,8 @@ public class JpaIntegrator implements Integrator {
if ( callbackRegistry != null ) {
callbackRegistry.release();
}
if ( callbackProcessor != null ) {
callbackProcessor.release();
if ( entityCallbackBuilder != null ) {
entityCallbackBuilder.release();
}
if ( jpaListenerFactory != null ) {
jpaListenerFactory.release();

View File

@ -9,12 +9,29 @@ package org.hibernate.jpa.event.spi.jpa;
import java.io.Serializable;
/**
* Represents a JPA event callback.
* Represents a JPA event callback (the method).
* <p/>
* Generally there are 2 flavors of this; either an annotated method on the entity itself
* or an annotated method on a separate "listener" class. This contract unifies both of
* these cases.
*
* @author <a href="mailto:kabir.khan@jboss.org">Kabir Khan</a>
* @author Steve Ebersole
*/
public interface Callback extends Serializable {
/**
* Is this callback active (will it do anything)?
*
* @return {@code true} if the callback is active, {@code false} otherwise.
*
* @deprecated I can actually find no usages of this method and have no idea
* why it is here :)
*/
@Deprecated
boolean isActive();
CallbackType getCallbackType();
/**
* Contract for performing the callback
*
@ -22,12 +39,5 @@ public interface Callback extends Serializable {
*
* @return Did a callback actually happen?
*/
public boolean performCallback(Object entity);
/**
* Is this callback active (will it do anything)?
*
* @return
*/
public boolean isActive();
boolean performCallback(Object entity);
}

View File

@ -9,22 +9,57 @@ package org.hibernate.jpa.event.spi.jpa;
import java.io.Serializable;
/**
* Registry of Callbacks by entity and type
*
* @author Steve Ebersole
*/
public interface CallbackRegistry extends Serializable {
/**
* Do we have any registered callbacks of the given type for the given entity?
*
* @param entityClass The entity Class to check against
* @param callbackType The type of callback to look for
*
* @return {@code true} indicates there are already registered callbacks of
* that type for that class; {@code false} indicates there are not.
*/
boolean hasRegisteredCallbacks(Class entityClass, CallbackType callbackType);
void preCreate(Object entity);
boolean hasPostCreateCallbacks(Class entityClass);
void postCreate(Object entity);
boolean preUpdate(Object entity);
boolean hasPostUpdateCallbacks(Class entityClass);
void postUpdate(Object entity);
void preRemove(Object entity);
boolean hasPostRemoveCallbacks(Class entityClass);
void postRemove(Object entity);
boolean postLoad(Object entity);
/**
* @deprecated Use {@link #hasRegisteredCallbacks(Class, CallbackType)} instead passing
* {@link CallbackType#POST_PERSIST}
*/
@Deprecated
boolean hasPostCreateCallbacks(Class entityClass);
/**
* @deprecated Use {@link #hasRegisteredCallbacks(Class, CallbackType)} instead passing
* {@link CallbackType#POST_UPDATE}
*/
@Deprecated
boolean hasPostUpdateCallbacks(Class entityClass);
/**
* @deprecated Use {@link #hasRegisteredCallbacks(Class, CallbackType)} instead passing
* {@link CallbackType#POST_REMOVE}
*/
@Deprecated
boolean hasPostRemoveCallbacks(Class entityClass);
/**
* @deprecated Use {@link #hasRegisteredCallbacks(Class, CallbackType)} instead.
*/
@Deprecated
boolean hasRegisteredCallbacks(Class entityClass, Class annotationClass);
}

View File

@ -4,10 +4,9 @@
* 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.jpa;
package org.hibernate.jpa.event.spi.jpa;
import org.hibernate.jpa.event.internal.core.HibernateEntityManagerEventListener;
import org.hibernate.jpa.event.spi.jpa.CallbackRegistry;
/**
* Contract for injecting the registry of Callbacks into event listeners.
@ -21,5 +20,5 @@ public interface CallbackRegistryConsumer extends HibernateEntityManagerEventLis
*
* @param callbackRegistry The CallbackRegistry
*/
public void injectCallbackRegistry(CallbackRegistry callbackRegistry);
void injectCallbackRegistry(CallbackRegistry callbackRegistry);
}

View File

@ -0,0 +1,40 @@
/*
* 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.spi.jpa;
import java.lang.annotation.Annotation;
import javax.persistence.PostLoad;
import javax.persistence.PostPersist;
import javax.persistence.PostRemove;
import javax.persistence.PostUpdate;
import javax.persistence.PrePersist;
import javax.persistence.PreRemove;
import javax.persistence.PreUpdate;
/**
* @author Steve Ebersole
*/
public enum CallbackType {
PRE_UPDATE( PreUpdate.class ),
POST_UPDATE( PostUpdate.class ),
PRE_PERSIST( PrePersist.class ),
POST_PERSIST( PostPersist.class ),
PRE_REMOVE( PreRemove.class ),
POST_REMOVE( PostRemove.class ),
POST_LOAD( PostLoad.class )
;
private Class<? extends Annotation> callbackAnnotation;
CallbackType(Class<? extends Annotation> callbackAnnotation) {
this.callbackAnnotation = callbackAnnotation;
}
public Class<? extends Annotation> getCallbackAnnotation() {
return callbackAnnotation;
}
}

View File

@ -0,0 +1,32 @@
/*
* 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.spi.jpa;
/**
* Contract for walking an entity hierarchy and building a list of JPA callbacks
*
* @author Steve Ebersole
*/
public interface EntityCallbackBuilder {
/**
* Represents the target of JPA callback registrations as part the EntityCallbackBuilder
*/
interface CallbackRegistrar extends CallbackRegistry {
/**
* Register the callback against the given entity.
*
* @param entityClass The entity Class to register the Callbacks against
* @param callbacks The Callbacks to register against the given entity Class
*/
void registerCallbacks(Class entityClass, Callback[] callbacks);
}
void buildCallbacksForEntity(String entityName, CallbackRegistrar callbackRegistrar);
void release();
}

View File

@ -0,0 +1,20 @@
/*
* 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.spi.jpa;
/**
* @author Steve Ebersole
*/
public interface ExtendedBeanManager {
void registerLifecycleListener(LifecycleListener lifecycleListener);
/**
*/
interface LifecycleListener {
void beanManagerInitialized();
}
}

View File

@ -0,0 +1,17 @@
/*
* 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.spi.jpa;
/**
* Encapsulates access to the listener instance for listener callbacks
* ({@link javax.persistence.EntityListeners}).
*
* @author Steve Ebersole
*/
public interface Listener<T> {
T getListener();
}

View File

@ -7,12 +7,11 @@
package org.hibernate.jpa.event.spi.jpa;
/**
* Factory for building instances user-specified event listeners.
* Factory for building instances of callback listener classes.
*
* @author Steve Ebersole
*/
public interface ListenerFactory {
public <T> T buildListener(Class<T> listenerClass);
public void release();
<T> Listener<T> buildListener(Class<T> listenerClass);
void release();
}