HHH-13890 Add support for custom event types and listeners
This commit is contained in:
parent
588115bb0a
commit
2a4c10a663
|
@ -97,7 +97,7 @@ public class EventListenerRegistryImpl implements EventListenerRegistry, Stoppab
|
||||||
|
|
||||||
private final SessionFactoryImplementor sessionFactory;
|
private final SessionFactoryImplementor sessionFactory;
|
||||||
private final CallbackRegistryImplementor callbackRegistry;
|
private final CallbackRegistryImplementor callbackRegistry;
|
||||||
private final EventListenerGroupImpl[] registeredEventListeners;
|
private volatile EventListenerGroupImpl[] registeredEventListeners;
|
||||||
private CallbackBuilder callbackBuilder;
|
private CallbackBuilder callbackBuilder;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -160,9 +160,56 @@ public class EventListenerRegistryImpl implements EventListenerRegistry, Stoppab
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings({ "unchecked", "rawtypes" })
|
||||||
|
private synchronized <T> EventListenerGroupImpl<T> getOrCreateEventListenerGroup(EventType<T> eventType) {
|
||||||
|
final int sizeOriginal = this.registeredEventListeners.length;
|
||||||
|
final EventListenerGroupImpl[] registeredEventListenersNew;
|
||||||
|
if ( eventType.ordinal() < sizeOriginal ) {
|
||||||
|
final EventListenerGroupImpl registeredEventListener = registeredEventListeners[ eventType.ordinal() ];
|
||||||
|
if ( registeredEventListener != null ) {
|
||||||
|
// eventType has already been registered;
|
||||||
|
return registeredEventListener; // EARLY RETURN
|
||||||
|
}
|
||||||
|
// eventType has not been registered yet.
|
||||||
|
// Its EventListenerGroupImpl will be created and added to registeredEventListeners below.
|
||||||
|
// There is already space for the new EventType in this.registeredEventListeners.
|
||||||
|
registeredEventListenersNew = this.registeredEventListeners;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// eventType is a custom EventType, and there is not enough space in
|
||||||
|
// registeredEventListeners to accommodate it.
|
||||||
|
|
||||||
|
// Allocate a new array to hold listener groups for *all* EventType values that currently exist.
|
||||||
|
// This way an existing, unregistered EventType with a larger ordinal will not require another
|
||||||
|
// allocation when it gets registered in the future.
|
||||||
|
final int sizeNew = Math.max( eventType.ordinal() + 1, EventType.values().size() );
|
||||||
|
registeredEventListenersNew = new EventListenerGroupImpl[sizeNew];
|
||||||
|
|
||||||
|
// First copy the existing listeners to registeredEventListenersNew.
|
||||||
|
System.arraycopy( this.registeredEventListeners, 0, registeredEventListenersNew, 0, sizeOriginal );
|
||||||
|
}
|
||||||
|
|
||||||
|
final EventListenerGroupImpl listenerGroup = new EventListenerGroupImpl(
|
||||||
|
eventType,
|
||||||
|
EventListenerRegistryImpl.this
|
||||||
|
);
|
||||||
|
registeredEventListenersNew[eventType.ordinal()] = listenerGroup;
|
||||||
|
|
||||||
|
// Now update the reference.
|
||||||
|
this.registeredEventListeners = registeredEventListenersNew;
|
||||||
|
|
||||||
|
return listenerGroup;
|
||||||
|
}
|
||||||
|
|
||||||
@SuppressWarnings({ "unchecked" })
|
@SuppressWarnings({ "unchecked" })
|
||||||
public <T> EventListenerGroupImpl<T> getEventListenerGroup(EventType<T> eventType) {
|
public <T> EventListenerGroupImpl<T> getEventListenerGroup(EventType<T> eventType) {
|
||||||
EventListenerGroupImpl<T> listeners = registeredEventListeners[ eventType.ordinal() ];
|
if ( registeredEventListeners.length < eventType.ordinal() + 1 ) {
|
||||||
|
// eventTpe is a custom EventType that has not been registered.
|
||||||
|
// registeredEventListeners array was not allocated enough space to
|
||||||
|
// accommodate it.
|
||||||
|
throw new HibernateException( "Unable to find listeners for type [" + eventType.eventName() + "]" );
|
||||||
|
}
|
||||||
|
final EventListenerGroupImpl<T> listeners = registeredEventListeners[ eventType.ordinal() ];
|
||||||
if ( listeners == null ) {
|
if ( listeners == null ) {
|
||||||
throw new HibernateException( "Unable to find listeners for type [" + eventType.eventName() + "]" );
|
throw new HibernateException( "Unable to find listeners for type [" + eventType.eventName() + "]" );
|
||||||
}
|
}
|
||||||
|
@ -218,7 +265,7 @@ public class EventListenerRegistryImpl implements EventListenerRegistry, Stoppab
|
||||||
@Override
|
@Override
|
||||||
@SafeVarargs
|
@SafeVarargs
|
||||||
public final <T> void setListeners(EventType<T> type, T... listeners) {
|
public final <T> void setListeners(EventType<T> type, T... listeners) {
|
||||||
EventListenerGroupImpl<T> registeredListeners = getEventListenerGroup( type );
|
EventListenerGroupImpl<T> registeredListeners = getOrCreateEventListenerGroup( type );
|
||||||
registeredListeners.clear();
|
registeredListeners.clear();
|
||||||
if ( listeners != null ) {
|
if ( listeners != null ) {
|
||||||
for ( T listener : listeners ) {
|
for ( T listener : listeners ) {
|
||||||
|
@ -236,7 +283,7 @@ public class EventListenerRegistryImpl implements EventListenerRegistry, Stoppab
|
||||||
@Override
|
@Override
|
||||||
@SafeVarargs
|
@SafeVarargs
|
||||||
public final <T> void appendListeners(EventType<T> type, T... listeners) {
|
public final <T> void appendListeners(EventType<T> type, T... listeners) {
|
||||||
getEventListenerGroup( type ).appendListeners( listeners );
|
getOrCreateEventListenerGroup( type ).appendListeners( listeners );
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -248,7 +295,7 @@ public class EventListenerRegistryImpl implements EventListenerRegistry, Stoppab
|
||||||
@Override
|
@Override
|
||||||
@SafeVarargs
|
@SafeVarargs
|
||||||
public final <T> void prependListeners(EventType<T> type, T... listeners) {
|
public final <T> void prependListeners(EventType<T> type, T... listeners) {
|
||||||
getEventListenerGroup( type ).prependListeners( listeners );
|
getOrCreateEventListenerGroup( type ).prependListeners( listeners );
|
||||||
}
|
}
|
||||||
|
|
||||||
private EventListenerGroupImpl[] buildListenerGroups() {
|
private EventListenerGroupImpl[] buildListenerGroups() {
|
||||||
|
|
|
@ -10,11 +10,14 @@ import java.lang.reflect.Field;
|
||||||
import java.security.AccessController;
|
import java.security.AccessController;
|
||||||
import java.security.PrivilegedAction;
|
import java.security.PrivilegedAction;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
import java.util.concurrent.atomic.AtomicInteger;
|
import java.util.concurrent.atomic.AtomicInteger;
|
||||||
|
|
||||||
import org.hibernate.HibernateException;
|
import org.hibernate.HibernateException;
|
||||||
|
import org.hibernate.internal.CoreLogging;
|
||||||
|
|
||||||
|
import org.jboss.logging.Logger;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Enumeration of the recognized types of events, including meta-information about each.
|
* Enumeration of the recognized types of events, including meta-information about each.
|
||||||
|
@ -22,7 +25,7 @@ import org.hibernate.HibernateException;
|
||||||
* @author Steve Ebersole
|
* @author Steve Ebersole
|
||||||
*/
|
*/
|
||||||
public final class EventType<T> {
|
public final class EventType<T> {
|
||||||
|
private static final Logger LOG = CoreLogging.logger( EventType.class );
|
||||||
private static AtomicInteger typeCounter = new AtomicInteger( 0 );
|
private static AtomicInteger typeCounter = new AtomicInteger( 0 );
|
||||||
|
|
||||||
public static final EventType<LoadEventListener> LOAD = create( "load", LoadEventListener.class );
|
public static final EventType<LoadEventListener> LOAD = create( "load", LoadEventListener.class );
|
||||||
|
@ -76,6 +79,46 @@ public final class EventType<T> {
|
||||||
public static final EventType<PostCollectionRemoveEventListener> POST_COLLECTION_REMOVE = create( "post-collection-remove", PostCollectionRemoveEventListener.class );
|
public static final EventType<PostCollectionRemoveEventListener> POST_COLLECTION_REMOVE = create( "post-collection-remove", PostCollectionRemoveEventListener.class );
|
||||||
public static final EventType<PostCollectionUpdateEventListener> POST_COLLECTION_UPDATE = create( "post-collection-update", PostCollectionUpdateEventListener.class );
|
public static final EventType<PostCollectionUpdateEventListener> POST_COLLECTION_UPDATE = create( "post-collection-update", PostCollectionUpdateEventListener.class );
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add a new event type.
|
||||||
|
*
|
||||||
|
* @param name - name of the custom event
|
||||||
|
* @param listenerClass - the base listener class or interface associated with the entity type
|
||||||
|
* @param <T> - listenerClass
|
||||||
|
* @return the custom {@link EventType}
|
||||||
|
*/
|
||||||
|
@SuppressWarnings({ "unchecked", "rawtypes" })
|
||||||
|
public static synchronized <T> EventType<T> addCustomEventType(String name, Class<T> listenerClass) {
|
||||||
|
if ( name == null || listenerClass == null ) {
|
||||||
|
throw new HibernateException( "Custom EventType name and associated class must be non-null." );
|
||||||
|
}
|
||||||
|
|
||||||
|
final EventType eventType = EVENT_TYPE_BY_NAME_MAP.computeIfAbsent(
|
||||||
|
name,
|
||||||
|
( e -> {
|
||||||
|
final EventType eventTypeNew = EventType.create( name, listenerClass );
|
||||||
|
LOG.debugf(
|
||||||
|
"Added custom EventType: [%s], ordinal=[%d], listener=[%s].",
|
||||||
|
name,
|
||||||
|
eventTypeNew.ordinal,
|
||||||
|
listenerClass.toString()
|
||||||
|
);
|
||||||
|
return eventTypeNew;
|
||||||
|
} )
|
||||||
|
);
|
||||||
|
// There's no way to know if there was a pre-existing EventType with
|
||||||
|
// the same name and listener, so ignore that case.
|
||||||
|
// Just check that listener is the same as listenerClass
|
||||||
|
if ( !listenerClass.equals( eventType.baseListenerInterface ) ) {
|
||||||
|
throw new HibernateException(
|
||||||
|
"Could not add EventType [" + name + "] with listener Class ["
|
||||||
|
+ "]. An EventType with that name already exists with listener ["
|
||||||
|
+ listenerClass.getName()
|
||||||
|
+ "]."
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return eventType;
|
||||||
|
}
|
||||||
|
|
||||||
private static <T> EventType<T> create(String name, Class<T> listenerClass) {
|
private static <T> EventType<T> create(String name, Class<T> listenerClass) {
|
||||||
return new EventType<T>( name, listenerClass );
|
return new EventType<T>( name, listenerClass );
|
||||||
|
@ -89,7 +132,7 @@ public final class EventType<T> {
|
||||||
new PrivilegedAction<Map<String, EventType>>() {
|
new PrivilegedAction<Map<String, EventType>>() {
|
||||||
@Override
|
@Override
|
||||||
public Map<String, EventType> run() {
|
public Map<String, EventType> run() {
|
||||||
final Map<String, EventType> typeByNameMap = new HashMap<String, EventType>();
|
final Map<String, EventType> typeByNameMap = new ConcurrentHashMap<>();
|
||||||
for ( Field field : EventType.class.getDeclaredFields() ) {
|
for ( Field field : EventType.class.getDeclaredFields() ) {
|
||||||
if ( EventType.class.isAssignableFrom( field.getType() ) ) {
|
if ( EventType.class.isAssignableFrom( field.getType() ) ) {
|
||||||
try {
|
try {
|
||||||
|
|
|
@ -0,0 +1,71 @@
|
||||||
|
/*
|
||||||
|
* 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.event;
|
||||||
|
|
||||||
|
import org.hibernate.HibernateException;
|
||||||
|
import org.hibernate.event.spi.EventType;
|
||||||
|
import org.hibernate.event.spi.LoadEventListener;
|
||||||
|
|
||||||
|
import org.hibernate.testing.TestForIssue;
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
import static org.junit.Assert.assertEquals;
|
||||||
|
import static org.junit.Assert.assertFalse;
|
||||||
|
import static org.junit.Assert.assertTrue;
|
||||||
|
import static org.junit.Assert.fail;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Gail Badner
|
||||||
|
*/
|
||||||
|
|
||||||
|
@TestForIssue( jiraKey = "HHH-13890" )
|
||||||
|
public class CustomEventTypeTest {
|
||||||
|
private final String EVENT_TYPE_NAME = "operation";
|
||||||
|
private final String OTHER_EVENT_TYPE_NAME = "other-operation";
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testAddCustomEventType() {
|
||||||
|
final int numberOfEventTypesOriginal = EventType.values().size();
|
||||||
|
|
||||||
|
try {
|
||||||
|
EventType.resolveEventTypeByName( EVENT_TYPE_NAME );
|
||||||
|
fail( "Should have thrown HibernateException" );
|
||||||
|
}
|
||||||
|
catch(HibernateException expected) {
|
||||||
|
}
|
||||||
|
|
||||||
|
final EventType<CustomListener> eventType = EventType.addCustomEventType( EVENT_TYPE_NAME, CustomListener.class );
|
||||||
|
assertEquals( EVENT_TYPE_NAME, eventType.eventName() );
|
||||||
|
assertEquals( CustomListener.class, eventType.baseListenerInterface() );
|
||||||
|
assertEquals( numberOfEventTypesOriginal, eventType.ordinal() );
|
||||||
|
assertTrue( EventType.values().contains( eventType ) );
|
||||||
|
assertEquals( numberOfEventTypesOriginal + 1, EventType.values().size() );
|
||||||
|
|
||||||
|
final EventType<OtherCustomListener> otherEventType = EventType.addCustomEventType( OTHER_EVENT_TYPE_NAME, OtherCustomListener.class );
|
||||||
|
assertEquals( OTHER_EVENT_TYPE_NAME, otherEventType.eventName() );
|
||||||
|
assertEquals( OtherCustomListener.class, otherEventType.baseListenerInterface() );
|
||||||
|
assertEquals( numberOfEventTypesOriginal + 1, otherEventType.ordinal() );
|
||||||
|
assertEquals( numberOfEventTypesOriginal + 2, EventType.values().size() );
|
||||||
|
|
||||||
|
// Adding an event type with the same name and base listener as one that exists, should be OK.
|
||||||
|
EventType.addCustomEventType( "load", LoadEventListener.class );
|
||||||
|
|
||||||
|
// Adding an event type with the same name but different listener as one that exists, should fail.
|
||||||
|
try {
|
||||||
|
EventType.addCustomEventType( "load", CustomListener.class );
|
||||||
|
fail( "Should have thrown HibernateException" );
|
||||||
|
}
|
||||||
|
catch (HibernateException expected) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public interface CustomListener {
|
||||||
|
}
|
||||||
|
|
||||||
|
public interface OtherCustomListener {
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,396 @@
|
||||||
|
/*
|
||||||
|
* 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.test.events;
|
||||||
|
|
||||||
|
import java.util.concurrent.atomic.AtomicInteger;
|
||||||
|
import javax.persistence.Entity;
|
||||||
|
import javax.persistence.Id;
|
||||||
|
|
||||||
|
import org.hibernate.HibernateException;
|
||||||
|
import org.hibernate.boot.Metadata;
|
||||||
|
import org.hibernate.boot.registry.BootstrapServiceRegistryBuilder;
|
||||||
|
import org.hibernate.engine.spi.SessionFactoryImplementor;
|
||||||
|
import org.hibernate.event.service.spi.EventListenerGroup;
|
||||||
|
import org.hibernate.event.service.spi.EventListenerRegistry;
|
||||||
|
import org.hibernate.event.spi.EventType;
|
||||||
|
import org.hibernate.integrator.spi.Integrator;
|
||||||
|
import org.hibernate.jpa.event.spi.CallbackRegistry;
|
||||||
|
import org.hibernate.jpa.event.spi.CallbackRegistryConsumer;
|
||||||
|
import org.hibernate.service.spi.SessionFactoryServiceRegistry;
|
||||||
|
|
||||||
|
import org.hibernate.testing.TestForIssue;
|
||||||
|
import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase;
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
import static org.junit.Assert.assertEquals;
|
||||||
|
import static org.junit.Assert.assertNotNull;
|
||||||
|
import static org.junit.Assert.fail;
|
||||||
|
|
||||||
|
@TestForIssue( jiraKey = "HHH-13890")
|
||||||
|
public class CustomEventTypeRegisterListenerTest extends BaseCoreFunctionalTestCase {
|
||||||
|
public enum Category {
|
||||||
|
CLOTHING,
|
||||||
|
FURNITURE
|
||||||
|
}
|
||||||
|
|
||||||
|
private final TheIntegrator theIntegrator = new TheIntegrator();
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void prepareBootstrapRegistryBuilder(BootstrapServiceRegistryBuilder builder) {
|
||||||
|
builder.applyIntegrator( theIntegrator );
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testSetListenerClasses() {
|
||||||
|
testNormalUsage( theIntegrator.eventTypeForSetListenerClasses(), UsesSetClasses.class );
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testSetListenerObjects() {
|
||||||
|
testNormalUsage( theIntegrator.eventTypeForSetListenerObjects(), UsesSetObjects.class );
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testAppendListenerClasses() {
|
||||||
|
testNormalUsage( theIntegrator.eventTypeForAppendListenerClasses(), UsesAppendClasses.class );
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testAppendListenerObjects() {
|
||||||
|
testNormalUsage( theIntegrator.eventTypeForAppendListenerObjects(), UsesAppendObjects.class );
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testPrependListenerClasses() {
|
||||||
|
testNormalUsage( theIntegrator.eventTypeForPrependListenerClasses(), UsesPrependClasses.class );
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testPrependListenerObjects() {
|
||||||
|
testNormalUsage( theIntegrator.eventTypeForPrependListenerObjects(), UsesPrependObjects.class );
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testUnregisteredEventType() {
|
||||||
|
final EventListenerRegistry eventListenerRegistry =
|
||||||
|
sessionFactory().getServiceRegistry().getService( EventListenerRegistry.class );
|
||||||
|
try {
|
||||||
|
eventListenerRegistry.getEventListenerGroup( theIntegrator.eventTypeUnregistered() );
|
||||||
|
fail( "HibernateException should have been thrown." );
|
||||||
|
}
|
||||||
|
catch (HibernateException expected) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private <T extends Listener> void testNormalUsage(EventType<T> eventType, Class<T> baseListenerClass) {
|
||||||
|
final Item clothing = new Item( Category.CLOTHING );
|
||||||
|
final Item furniture = new Item( Category.FURNITURE );
|
||||||
|
final Item other = new Item();
|
||||||
|
|
||||||
|
final EventListenerRegistry eventListenerRegistry =
|
||||||
|
sessionFactory().getServiceRegistry().getService( EventListenerRegistry.class );
|
||||||
|
final EventListenerGroup<T> group =
|
||||||
|
eventListenerRegistry.getEventListenerGroup( eventType );
|
||||||
|
for ( Object listener : group.listeners() ) {
|
||||||
|
assertNotNull( ( (ItemNameGeneratorListener) listener).getCallbackRegistry() );
|
||||||
|
}
|
||||||
|
|
||||||
|
final ItemNameGeneratorEvent clothingEvent = new ItemNameGeneratorEvent( clothing );
|
||||||
|
group.fireEventOnEachListener( clothingEvent, Listener::onGenerateItemName );
|
||||||
|
assertEquals( "C100", clothing.name );
|
||||||
|
|
||||||
|
final ItemNameGeneratorEvent furnitureEvent = new ItemNameGeneratorEvent( furniture );
|
||||||
|
group.fireEventOnEachListener( furnitureEvent, Listener::onGenerateItemName );
|
||||||
|
assertEquals( "F200", furniture.name );
|
||||||
|
|
||||||
|
final ItemNameGeneratorEvent otherEvent = new ItemNameGeneratorEvent( other );
|
||||||
|
group.fireEventOnEachListener( otherEvent, Listener::onGenerateItemName );
|
||||||
|
assertEquals( "O300", other.name );
|
||||||
|
}
|
||||||
|
|
||||||
|
@Entity(name = "Item")
|
||||||
|
public static class Item {
|
||||||
|
|
||||||
|
@Id
|
||||||
|
private int id;
|
||||||
|
|
||||||
|
private Category category;
|
||||||
|
|
||||||
|
private String name;
|
||||||
|
|
||||||
|
Item() {
|
||||||
|
}
|
||||||
|
Item(Category category) {
|
||||||
|
this.category = category;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getName() {
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setName(String name) {
|
||||||
|
this.name = name;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class ItemNameGeneratorEvent {
|
||||||
|
private Item item;
|
||||||
|
|
||||||
|
public ItemNameGeneratorEvent(Item item) {
|
||||||
|
this.item = item;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Item getItem() {
|
||||||
|
return item;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public interface Listener {
|
||||||
|
void onGenerateItemName(ItemNameGeneratorEvent event);
|
||||||
|
}
|
||||||
|
|
||||||
|
public interface ItemNameGeneratorListener extends Listener, CallbackRegistryConsumer {
|
||||||
|
CallbackRegistry getCallbackRegistry();
|
||||||
|
}
|
||||||
|
|
||||||
|
public interface UsesSetClasses extends Listener {
|
||||||
|
}
|
||||||
|
public interface UsesSetObjects extends Listener {
|
||||||
|
}
|
||||||
|
public interface UsesAppendClasses extends Listener {
|
||||||
|
}
|
||||||
|
public interface UsesAppendObjects extends Listener{
|
||||||
|
}
|
||||||
|
public interface UsesPrependClasses extends Listener {
|
||||||
|
}
|
||||||
|
public interface UsesPrependObjects extends Listener {
|
||||||
|
}
|
||||||
|
public interface Unregistered {
|
||||||
|
}
|
||||||
|
|
||||||
|
public static abstract class AbstractItemNameGeneratorListener implements ItemNameGeneratorListener {
|
||||||
|
private AtomicInteger counter;
|
||||||
|
private CallbackRegistry callbackRegistry = null;
|
||||||
|
|
||||||
|
protected AbstractItemNameGeneratorListener(int startValue) {
|
||||||
|
counter = new AtomicInteger( startValue );
|
||||||
|
}
|
||||||
|
|
||||||
|
public void onGenerateItemName(ItemNameGeneratorEvent event) {
|
||||||
|
if ( event.item.name == null && getCategory() == event.item.category ) {
|
||||||
|
event.item.name = getPrefix() + counter.getAndIncrement();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public abstract Category getCategory();
|
||||||
|
public abstract String getPrefix();
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void injectCallbackRegistry(CallbackRegistry callbackRegistry) {
|
||||||
|
this.callbackRegistry = callbackRegistry;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public CallbackRegistry getCallbackRegistry() {
|
||||||
|
return callbackRegistry;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static abstract class ClothingGeneratorListener extends AbstractItemNameGeneratorListener {
|
||||||
|
protected ClothingGeneratorListener() {
|
||||||
|
super( 100 );
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Category getCategory() {
|
||||||
|
return Category.CLOTHING;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getPrefix() {
|
||||||
|
return "C";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class ClothingGeneratorListenerSetClasses extends ClothingGeneratorListener implements UsesSetClasses {
|
||||||
|
}
|
||||||
|
public static class ClothingGeneratorListenerSetObjects extends ClothingGeneratorListener implements UsesSetObjects {
|
||||||
|
}
|
||||||
|
public static class ClothingGeneratorListenerAppendClasses extends ClothingGeneratorListener implements UsesAppendClasses {
|
||||||
|
}
|
||||||
|
public static class ClothingGeneratorListenerAppendObjects extends ClothingGeneratorListener implements UsesAppendObjects {
|
||||||
|
}
|
||||||
|
public static class ClothingGeneratorListenerPrependClasses extends ClothingGeneratorListener implements UsesPrependClasses {
|
||||||
|
}
|
||||||
|
public static class ClothingGeneratorListenerPrependObjects extends ClothingGeneratorListener implements UsesPrependObjects {
|
||||||
|
}
|
||||||
|
|
||||||
|
public static abstract class FurnitureGeneratorListener extends AbstractItemNameGeneratorListener {
|
||||||
|
protected FurnitureGeneratorListener() {
|
||||||
|
super( 200 );
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Category getCategory() {
|
||||||
|
return Category.FURNITURE;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getPrefix() {
|
||||||
|
return "F";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class FurnitureGeneratorListenerSetClasses extends FurnitureGeneratorListener implements UsesSetClasses {
|
||||||
|
}
|
||||||
|
public static class FurnitureGeneratorListenerSetObjects extends FurnitureGeneratorListener implements UsesSetObjects {
|
||||||
|
}
|
||||||
|
public static class FurnitureGeneratorListenerAppendClasses extends FurnitureGeneratorListener implements UsesAppendClasses {
|
||||||
|
}
|
||||||
|
public static class FurnitureGeneratorListenerAppendObjects extends FurnitureGeneratorListener implements UsesAppendObjects {
|
||||||
|
}
|
||||||
|
public static class FurnitureGeneratorListenerPrependClasses extends FurnitureGeneratorListener implements UsesPrependClasses {
|
||||||
|
}
|
||||||
|
public static class FurnitureGeneratorListenerPrependObjects extends FurnitureGeneratorListener implements UsesPrependObjects {
|
||||||
|
}
|
||||||
|
|
||||||
|
public static abstract class OtherGeneratorListener extends AbstractItemNameGeneratorListener {
|
||||||
|
protected OtherGeneratorListener() {
|
||||||
|
super( 300 );
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Category getCategory() {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getPrefix() {
|
||||||
|
return "O";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class OtherGeneratorListenerSetClasses extends OtherGeneratorListener implements UsesSetClasses {
|
||||||
|
}
|
||||||
|
public static class OtherGeneratorListenerSetObjects extends OtherGeneratorListener implements UsesSetObjects {
|
||||||
|
}
|
||||||
|
public static class OtherGeneratorListenerAppendClasses extends OtherGeneratorListener implements UsesAppendClasses {
|
||||||
|
}
|
||||||
|
public static class OtherGeneratorListenerAppendObjects extends OtherGeneratorListener implements UsesAppendObjects {
|
||||||
|
}
|
||||||
|
public static class OtherGeneratorListenerPrependClasses extends OtherGeneratorListener implements UsesPrependClasses {
|
||||||
|
}
|
||||||
|
public static class OtherGeneratorListenerPrependObjects extends OtherGeneratorListener implements UsesPrependObjects {
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class TheIntegrator implements Integrator {
|
||||||
|
|
||||||
|
private EventType<UsesSetClasses> eventTypeForSetListenerClasses;
|
||||||
|
private EventType<UsesSetObjects> eventTypeForSetListenerObjects;
|
||||||
|
|
||||||
|
private EventType<UsesPrependClasses> eventTypeForPrependListenerClasses;
|
||||||
|
private EventType<UsesPrependObjects> eventTypeForPrependListenerObjects;
|
||||||
|
|
||||||
|
private EventType<UsesAppendClasses> eventTypeForAppendListenerClasses;
|
||||||
|
private EventType<UsesAppendObjects> eventTypeForAppendListenerObjects;
|
||||||
|
|
||||||
|
private EventType<Unregistered> eventTypeUnregistered;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void integrate(
|
||||||
|
Metadata metadata,
|
||||||
|
SessionFactoryImplementor sessionFactory,
|
||||||
|
SessionFactoryServiceRegistry serviceRegistry) {
|
||||||
|
|
||||||
|
eventTypeForSetListenerClasses = EventType.addCustomEventType( "eventTypeForSetListenerClasses", UsesSetClasses.class );
|
||||||
|
eventTypeForSetListenerObjects = EventType.addCustomEventType( "eventTypeForSetListenerObjects", UsesSetObjects.class );
|
||||||
|
|
||||||
|
eventTypeForPrependListenerClasses = EventType.addCustomEventType( "eventTypeForPrependListenerClasses", UsesPrependClasses.class );
|
||||||
|
eventTypeForPrependListenerObjects = EventType.addCustomEventType( "eventTypeForPrependListenerObjects", UsesPrependObjects.class );
|
||||||
|
|
||||||
|
eventTypeForAppendListenerClasses = EventType.addCustomEventType( "eventTypeForAppendListenerClasses", UsesAppendClasses.class );
|
||||||
|
eventTypeForAppendListenerObjects = EventType.addCustomEventType( "eventTypeForAppendListenerObjects", UsesAppendObjects.class );
|
||||||
|
|
||||||
|
final EventListenerRegistry eventListenerRegistry = serviceRegistry.getService( EventListenerRegistry.class );
|
||||||
|
|
||||||
|
eventListenerRegistry.setListeners(
|
||||||
|
eventTypeForSetListenerClasses,
|
||||||
|
ClothingGeneratorListenerSetClasses.class,
|
||||||
|
FurnitureGeneratorListenerSetClasses.class,
|
||||||
|
OtherGeneratorListenerSetClasses.class
|
||||||
|
);
|
||||||
|
eventListenerRegistry.setListeners(
|
||||||
|
eventTypeForSetListenerObjects,
|
||||||
|
new ClothingGeneratorListenerSetObjects(),
|
||||||
|
new FurnitureGeneratorListenerSetObjects(),
|
||||||
|
new OtherGeneratorListenerSetObjects()
|
||||||
|
);
|
||||||
|
|
||||||
|
eventListenerRegistry.prependListeners(
|
||||||
|
eventTypeForPrependListenerClasses,
|
||||||
|
ClothingGeneratorListenerPrependClasses.class,
|
||||||
|
FurnitureGeneratorListenerPrependClasses.class,
|
||||||
|
OtherGeneratorListenerPrependClasses.class
|
||||||
|
);
|
||||||
|
eventListenerRegistry.prependListeners(
|
||||||
|
eventTypeForPrependListenerObjects,
|
||||||
|
new ClothingGeneratorListenerPrependObjects(),
|
||||||
|
new FurnitureGeneratorListenerPrependObjects(),
|
||||||
|
new OtherGeneratorListenerPrependObjects()
|
||||||
|
);
|
||||||
|
|
||||||
|
eventListenerRegistry.appendListeners(
|
||||||
|
eventTypeForAppendListenerClasses,
|
||||||
|
ClothingGeneratorListenerAppendClasses.class,
|
||||||
|
FurnitureGeneratorListenerAppendClasses.class,
|
||||||
|
OtherGeneratorListenerAppendClasses.class
|
||||||
|
);
|
||||||
|
eventListenerRegistry.appendListeners(
|
||||||
|
eventTypeForAppendListenerObjects,
|
||||||
|
new ClothingGeneratorListenerAppendObjects(),
|
||||||
|
new FurnitureGeneratorListenerAppendObjects(),
|
||||||
|
new OtherGeneratorListenerAppendObjects()
|
||||||
|
);
|
||||||
|
|
||||||
|
// add an EventType that does not get registered
|
||||||
|
eventTypeUnregistered = EventType.addCustomEventType( "unregistered", Unregistered.class );
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void disintegrate(
|
||||||
|
SessionFactoryImplementor sessionFactory, SessionFactoryServiceRegistry serviceRegistry) {
|
||||||
|
}
|
||||||
|
|
||||||
|
public EventType<UsesSetClasses> eventTypeForSetListenerClasses() {
|
||||||
|
return eventTypeForSetListenerClasses;
|
||||||
|
}
|
||||||
|
|
||||||
|
public EventType<UsesSetObjects> eventTypeForSetListenerObjects() {
|
||||||
|
return eventTypeForSetListenerObjects;
|
||||||
|
}
|
||||||
|
|
||||||
|
public EventType<UsesPrependClasses> eventTypeForPrependListenerClasses() {
|
||||||
|
return eventTypeForPrependListenerClasses;
|
||||||
|
}
|
||||||
|
|
||||||
|
public EventType<UsesPrependObjects> eventTypeForPrependListenerObjects() {
|
||||||
|
return eventTypeForPrependListenerObjects;
|
||||||
|
}
|
||||||
|
|
||||||
|
public EventType<UsesAppendClasses> eventTypeForAppendListenerClasses() {
|
||||||
|
return eventTypeForAppendListenerClasses;
|
||||||
|
}
|
||||||
|
|
||||||
|
public EventType<UsesAppendObjects> eventTypeForAppendListenerObjects() {
|
||||||
|
return eventTypeForAppendListenerObjects;
|
||||||
|
}
|
||||||
|
|
||||||
|
public EventType<Unregistered> eventTypeUnregistered() {
|
||||||
|
return eventTypeUnregistered;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,168 @@
|
||||||
|
/*
|
||||||
|
* 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.test.events;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.Iterator;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.concurrent.atomic.AtomicInteger;
|
||||||
|
|
||||||
|
import org.hibernate.boot.Metadata;
|
||||||
|
import org.hibernate.boot.MetadataSources;
|
||||||
|
import org.hibernate.boot.registry.BootstrapServiceRegistry;
|
||||||
|
import org.hibernate.boot.registry.BootstrapServiceRegistryBuilder;
|
||||||
|
import org.hibernate.boot.registry.StandardServiceRegistry;
|
||||||
|
import org.hibernate.boot.registry.StandardServiceRegistryBuilder;
|
||||||
|
import org.hibernate.engine.spi.SessionFactoryImplementor;
|
||||||
|
import org.hibernate.event.service.spi.EventListenerGroup;
|
||||||
|
import org.hibernate.event.service.spi.EventListenerRegistry;
|
||||||
|
import org.hibernate.event.spi.EventType;
|
||||||
|
import org.hibernate.integrator.spi.Integrator;
|
||||||
|
import org.hibernate.internal.CoreLogging;
|
||||||
|
import org.hibernate.internal.CoreMessageLogger;
|
||||||
|
import org.hibernate.service.ServiceRegistry;
|
||||||
|
import org.hibernate.service.spi.SessionFactoryServiceRegistry;
|
||||||
|
|
||||||
|
import org.hibernate.testing.TestForIssue;
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
import static org.junit.Assert.assertEquals;
|
||||||
|
import static org.junit.Assert.assertFalse;
|
||||||
|
import static org.junit.Assert.assertNotNull;
|
||||||
|
import static org.junit.Assert.assertTrue;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Concurrency test that registering a custom EventType does not interfere with
|
||||||
|
* looking up a registered EventListenerGroup.
|
||||||
|
*
|
||||||
|
* @author Gail Badner
|
||||||
|
*/
|
||||||
|
@TestForIssue( jiraKey = "HHH-13890")
|
||||||
|
public class EventTypeListenerRegistryConcurrencyTest {
|
||||||
|
private static CoreMessageLogger LOG = CoreLogging.messageLogger( EventTypeListenerRegistryConcurrencyTest.class );
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void test() {
|
||||||
|
final TheConcurrencyIntegrator integrator = new TheConcurrencyIntegrator();
|
||||||
|
BootstrapServiceRegistry bsr = new BootstrapServiceRegistryBuilder()
|
||||||
|
.applyIntegrator( integrator )
|
||||||
|
.build();
|
||||||
|
SessionFactoryImplementor sessionFactory = null;
|
||||||
|
try {
|
||||||
|
final StandardServiceRegistry ssr = new StandardServiceRegistryBuilder( bsr ).build();
|
||||||
|
sessionFactory = (SessionFactoryImplementor) new MetadataSources( ssr )
|
||||||
|
.buildMetadata()
|
||||||
|
.getSessionFactoryBuilder()
|
||||||
|
.build();
|
||||||
|
integrator.checkResults( sessionFactory.getServiceRegistry() );
|
||||||
|
}
|
||||||
|
finally {
|
||||||
|
if ( sessionFactory != null ) {
|
||||||
|
sessionFactory.close();
|
||||||
|
}
|
||||||
|
bsr.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class TheConcurrencyIntegrator implements Integrator {
|
||||||
|
private final int NUMBER_OF_EVENT_TYPES_NEW = 10000;
|
||||||
|
private final int NUMBER_OF_THREADS = 10;
|
||||||
|
private final AtomicInteger START_VALUE = new AtomicInteger( 0 );
|
||||||
|
private final List<Exception> exceptions = new ArrayList<>();
|
||||||
|
private final Set<EventType> customEventTypes = new HashSet<>( NUMBER_OF_EVENT_TYPES_NEW );
|
||||||
|
|
||||||
|
// Capture number of "standard" event types (before adding custom event types).
|
||||||
|
private final int numberEventTypesBefore = EventType.values().size();
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void integrate(
|
||||||
|
Metadata metadata,
|
||||||
|
SessionFactoryImplementor sessionFactory,
|
||||||
|
SessionFactoryServiceRegistry serviceRegistry) {
|
||||||
|
|
||||||
|
final EventListenerRegistry eventListenerRegistry = serviceRegistry.getService( EventListenerRegistry.class );
|
||||||
|
|
||||||
|
final Runnable createAndRegisterEventTypes = () -> {
|
||||||
|
for ( int i = START_VALUE.getAndIncrement();
|
||||||
|
i < NUMBER_OF_EVENT_TYPES_NEW;
|
||||||
|
i += NUMBER_OF_THREADS ) {
|
||||||
|
final EventType eventType = EventType.addCustomEventType(
|
||||||
|
"event" + i,
|
||||||
|
DummyListener.class
|
||||||
|
);
|
||||||
|
try {
|
||||||
|
eventListenerRegistry.setListeners( eventType, new DummyListener() );
|
||||||
|
eventListenerRegistry.getEventListenerGroup( eventType );
|
||||||
|
}
|
||||||
|
catch (Exception ex) {
|
||||||
|
LOG.info( ex );
|
||||||
|
exceptions.add( ex );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
final Runnable eventListenerGroupsGetter = () -> {
|
||||||
|
while( true ) {
|
||||||
|
try {
|
||||||
|
assertNotNull( eventListenerRegistry.getEventListenerGroup( EventType.AUTO_FLUSH ) );
|
||||||
|
}
|
||||||
|
catch (Exception ex) {
|
||||||
|
exceptions.add( ex );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
final Thread[] threadsCreateAndRegisterEventTypes = new Thread[NUMBER_OF_THREADS];
|
||||||
|
final Thread[] threadsEventListenerGroupsGetter = new Thread[NUMBER_OF_THREADS];
|
||||||
|
for ( int i = 0 ; i < NUMBER_OF_THREADS; i++ ) {
|
||||||
|
threadsCreateAndRegisterEventTypes[i] = new Thread( createAndRegisterEventTypes );
|
||||||
|
threadsEventListenerGroupsGetter[i] = new Thread( eventListenerGroupsGetter );
|
||||||
|
}
|
||||||
|
|
||||||
|
for ( int i = 0 ; i < NUMBER_OF_THREADS; i++ ) {
|
||||||
|
threadsCreateAndRegisterEventTypes[i].start();
|
||||||
|
threadsEventListenerGroupsGetter[i].start();
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
for ( int i = 0; i < NUMBER_OF_THREADS; i++ ) {
|
||||||
|
threadsCreateAndRegisterEventTypes[i].join();
|
||||||
|
threadsEventListenerGroupsGetter[i].interrupt();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (InterruptedException ex) {
|
||||||
|
LOG.info( ex );
|
||||||
|
exceptions.add( ex );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void disintegrate(
|
||||||
|
SessionFactoryImplementor sessionFactory,
|
||||||
|
SessionFactoryServiceRegistry serviceRegistry) {
|
||||||
|
}
|
||||||
|
|
||||||
|
public void checkResults(ServiceRegistry serviceRegistry) {
|
||||||
|
LOG.info( exceptions );
|
||||||
|
assertTrue( exceptions.isEmpty() );
|
||||||
|
assertEquals( numberEventTypesBefore + NUMBER_OF_EVENT_TYPES_NEW, EventType.values().size() );
|
||||||
|
final EventListenerRegistry eventListenerRegistry = serviceRegistry.getService( EventListenerRegistry.class );
|
||||||
|
for ( EventType eventType : customEventTypes) {
|
||||||
|
final EventListenerGroup eventListenerGroup = eventListenerRegistry.getEventListenerGroup( eventType );
|
||||||
|
final Iterator iterator = eventListenerGroup.listeners().iterator();
|
||||||
|
assertTrue( iterator.hasNext() );
|
||||||
|
assertTrue( DummyListener.class.isInstance( iterator.next() ) );
|
||||||
|
assertFalse( iterator.hasNext() );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class DummyListener {
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue