Skip to content

Commit

Permalink
HHH-13890 Add support for custom event types and listeners
Browse files Browse the repository at this point in the history
  • Loading branch information
gbadner authored and Sanne committed Apr 16, 2020
1 parent 588115b commit 2a4c10a
Show file tree
Hide file tree
Showing 5 changed files with 733 additions and 8 deletions.
Expand Up @@ -97,7 +97,7 @@ public class EventListenerRegistryImpl implements EventListenerRegistry, Stoppab

private final SessionFactoryImplementor sessionFactory;
private final CallbackRegistryImplementor callbackRegistry;
private final EventListenerGroupImpl[] registeredEventListeners;
private volatile EventListenerGroupImpl[] registeredEventListeners;
private CallbackBuilder callbackBuilder;

/**
Expand Down Expand Up @@ -160,9 +160,56 @@ public void prepare(MetadataImplementor metadata) {
}
}

@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" })
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 ) {
throw new HibernateException( "Unable to find listeners for type [" + eventType.eventName() + "]" );
}
Expand Down Expand Up @@ -218,7 +265,7 @@ private <T> T instantiateListener(Class<T> listenerClass) {
@Override
@SafeVarargs
public final <T> void setListeners(EventType<T> type, T... listeners) {
EventListenerGroupImpl<T> registeredListeners = getEventListenerGroup( type );
EventListenerGroupImpl<T> registeredListeners = getOrCreateEventListenerGroup( type );
registeredListeners.clear();
if ( listeners != null ) {
for ( T listener : listeners ) {
Expand All @@ -236,7 +283,7 @@ public final <T> void appendListeners(EventType<T> type, Class<? extends T>... l
@Override
@SafeVarargs
public final <T> void appendListeners(EventType<T> type, T... listeners) {
getEventListenerGroup( type ).appendListeners( listeners );
getOrCreateEventListenerGroup( type ).appendListeners( listeners );
}

@Override
Expand All @@ -248,7 +295,7 @@ public final <T> void prependListeners(EventType<T> type, Class<? extends T>...
@Override
@SafeVarargs
public final <T> void prependListeners(EventType<T> type, T... listeners) {
getEventListenerGroup( type ).prependListeners( listeners );
getOrCreateEventListenerGroup( type ).prependListeners( listeners );
}

private EventListenerGroupImpl[] buildListenerGroups() {
Expand Down
Expand Up @@ -10,19 +10,22 @@
import java.security.AccessController;
import java.security.PrivilegedAction;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicInteger;

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.
*
* @author Steve Ebersole
*/
public final class EventType<T> {

private static final Logger LOG = CoreLogging.logger( EventType.class );
private static AtomicInteger typeCounter = new AtomicInteger( 0 );

public static final EventType<LoadEventListener> LOAD = create( "load", LoadEventListener.class );
Expand Down Expand Up @@ -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<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) {
return new EventType<T>( name, listenerClass );
Expand All @@ -89,7 +132,7 @@ private static <T> EventType<T> create(String name, Class<T> listenerClass) {
new PrivilegedAction<Map<String, EventType>>() {
@Override
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() ) {
if ( EventType.class.isAssignableFrom( field.getType() ) ) {
try {
Expand Down
@@ -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 comments on commit 2a4c10a

Please sign in to comment.