Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -453,6 +453,10 @@ default boolean isXmlFunctionsEnabled() {
}

/**
* Should HQL integer division HQL should produce an integer on
* Oracle, MySQL, and MariaDB, where the {@code /} operator produces
* a non-integer.
*
* @see org.hibernate.cfg.AvailableSettings#PORTABLE_INTEGER_DIVISION
*/
@Override
Expand All @@ -461,11 +465,18 @@ default boolean isPortableIntegerDivisionEnabled() {
}

/**
* The number of {@link org.hibernate.stat.QueryStatistics} entries
* that should be stored by {@link org.hibernate.stat.Statistics}.
*
* @see org.hibernate.cfg.StatisticsSettings#QUERY_STATISTICS_MAX_SIZE
*/
int getQueryStatisticsMaxSize();

/**
* Should JPA entity lifecycle callbacks be processed by
* the {@link org.hibernate.event.spi.EventEngine} and
* {@link org.hibernate.jpa.event.spi.CallbackRegistry}?
*
* @see org.hibernate.cfg.PersistenceSettings#JPA_CALLBACKS_ENABLED
*/
boolean areJPACallbacksEnabled();
Expand Down
169 changes: 86 additions & 83 deletions hibernate-core/src/main/java/org/hibernate/event/spi/EventEngine.java
Original file line number Diff line number Diff line change
Expand Up @@ -5,23 +5,26 @@
package org.hibernate.event.spi;

import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.function.Consumer;

import org.hibernate.HibernateException;
import org.hibernate.boot.registry.classloading.spi.ClassLoaderService;
import org.hibernate.boot.spi.MetadataImplementor;
import org.hibernate.boot.spi.SessionFactoryOptions;
import org.hibernate.engine.spi.SessionFactoryImplementor;
import org.hibernate.event.service.internal.EventListenerRegistryImpl;
import org.hibernate.event.service.spi.EventListenerGroup;
import org.hibernate.event.service.spi.EventListenerRegistry;
import org.hibernate.internal.util.collections.CollectionHelper;
import org.hibernate.jpa.event.internal.CallbacksFactory;
import org.hibernate.jpa.event.spi.CallbackRegistry;
import org.hibernate.service.spi.ServiceRegistryImplementor;
import org.hibernate.service.spi.Stoppable;

import static java.util.Collections.unmodifiableMap;
import static org.hibernate.internal.util.collections.CollectionHelper.isNotEmpty;
import static org.hibernate.jpa.event.internal.CallbacksFactory.buildCallbackRegistry;

/**
* Composite for the things related to Hibernate's event system.
*
Expand All @@ -36,102 +39,39 @@ public class EventEngine {

public EventEngine(MetadataImplementor mappings, SessionFactoryImplementor sessionFactory) {

final SessionFactoryOptions sessionFactoryOptions = sessionFactory.getSessionFactoryOptions();
final ServiceRegistryImplementor serviceRegistry = sessionFactory.getServiceRegistry();

// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// resolve (JPA) callback handlers

callbackRegistry = CallbacksFactory.buildCallbackRegistry( sessionFactory.getSessionFactoryOptions(),
sessionFactory.getServiceRegistry(), mappings.getEntityBindings() );

callbackRegistry = buildCallbackRegistry( sessionFactoryOptions, serviceRegistry, mappings.getEntityBindings() );

// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// resolve event types and listeners

final EventListenerRegistryImpl.Builder listenerRegistryBuilder = new EventListenerRegistryImpl.Builder(
callbackRegistry,
sessionFactory.getSessionFactoryOptions().isJpaBootstrap()
);
final EventListenerRegistryImpl.Builder listenerRegistryBuilder =
new EventListenerRegistryImpl.Builder( callbackRegistry, sessionFactoryOptions.isJpaBootstrap() );

final Map<String,EventType<?>> eventTypes = new HashMap<>();
EventType.registerStandardTypes( eventTypes );

final EventEngineContributions contributionManager = new EventEngineContributions() {
@Override
public <T> EventType<T> findEventType(String name) {
//noinspection unchecked
return (EventType<T>) eventTypes.get( name );
}

@Override
public <T> EventType<T> contributeEventType(String name, Class<T> listenerRole) {
final EventType<T> eventType = registerEventType( name, listenerRole );

listenerRegistryBuilder.prepareListeners( eventType );

return eventType;
}

private <T> EventType<T> registerEventType(String name, Class<T> listenerRole) {
if ( name == null ) {
throw new HibernateException( "Custom event-type name must be non-null." );
}

if ( listenerRole == null ) {
throw new HibernateException( "Custom event-type listener role must be non-null." );
}

// make sure it does not match an existing name...
if ( eventTypes.containsKey( name ) ) {
final EventType<?> existing = eventTypes.get( name );
throw new HibernateException(
"Custom event-type already registered: " + name + " => " + existing
);
}

final EventType<T> eventType = EventType.create(
name,
listenerRole,
eventTypes.size()
);

eventTypes.put( name, eventType );
return eventType;
}

@Override @SafeVarargs
public final <T> EventType<T> contributeEventType(String name, Class<T> listenerRole, T... defaultListeners) {
final EventType<T> eventType = contributeEventType( name, listenerRole );

if ( defaultListeners != null ) {
listenerRegistryBuilder.getListenerGroup( eventType ).appendListeners( defaultListeners );
}

return eventType;
}

@Override
public <T> void configureListeners(
EventType<T> eventType,
Consumer<EventListenerGroup<T>> action) {
if ( ! eventTypes.containsValue( eventType ) ) {
throw new HibernateException( "EventType [" + eventType + "] not registered" );
}
callContributors( serviceRegistry, new ContributionManager( eventTypes, listenerRegistryBuilder ) );

action.accept( listenerRegistryBuilder.getListenerGroup( eventType ) );
}
};
registeredEventTypes = unmodifiableMap( eventTypes );
listenerRegistry = listenerRegistryBuilder.buildRegistry( registeredEventTypes );
}

private static void callContributors(
ServiceRegistryImplementor serviceRegistry, EventEngineContributions contributionManager) {
final Collection<EventEngineContributor> discoveredContributors =
sessionFactory.getServiceRegistry()
.requireService( ClassLoaderService.class )
serviceRegistry.requireService( ClassLoaderService.class )
.loadJavaServices( EventEngineContributor.class );
if ( CollectionHelper.isNotEmpty( discoveredContributors ) ) {
if ( isNotEmpty( discoveredContributors ) ) {
for ( EventEngineContributor contributor : discoveredContributors ) {
contributor.contribute( contributionManager );
}
}

this.registeredEventTypes = Collections.unmodifiableMap( eventTypes );
this.listenerRegistry = listenerRegistryBuilder.buildRegistry( registeredEventTypes );
}

public Collection<EventType<?>> getRegisteredEventTypes() {
Expand All @@ -152,10 +92,73 @@ public CallbackRegistry getCallbackRegistry() {
}

public void stop() {
if ( listenerRegistry instanceof Stoppable ) {
( (Stoppable) listenerRegistry ).stop();
if ( listenerRegistry instanceof Stoppable stoppable ) {
stoppable.stop();
}

callbackRegistry.release();
}

private static class ContributionManager implements EventEngineContributions {
private final Map<String, EventType<?>> eventTypes;
private final EventListenerRegistryImpl.Builder listenerRegistryBuilder;

public ContributionManager(
Map<String, EventType<?>> eventTypes,
EventListenerRegistryImpl.Builder listenerRegistryBuilder) {
this.eventTypes = eventTypes;
this.listenerRegistryBuilder = listenerRegistryBuilder;
}

@Override
public <T> EventType<T> findEventType(String name) {
//noinspection unchecked
return (EventType<T>) eventTypes.get( name );
}

@Override
public <T> EventType<T> contributeEventType(String name, Class<T> listenerRole) {
final EventType<T> eventType = registerEventType( name, listenerRole );
listenerRegistryBuilder.prepareListeners( eventType );
return eventType;
}

private <T> EventType<T> registerEventType(String name, Class<T> listenerRole) {
if ( name == null ) {
throw new HibernateException( "Custom event-type name must be non-null." );
}
else if ( listenerRole == null ) {
throw new HibernateException( "Custom event-type listener role must be non-null." );
}
// make sure it does not match an existing name...
else if ( eventTypes.containsKey( name ) ) {
final EventType<?> existing = eventTypes.get( name );
throw new HibernateException(
"Custom event-type already registered: " + name + " => " + existing
);
}
else {
final EventType<T> eventType = EventType.create( name, listenerRole, eventTypes.size() );
eventTypes.put( name, eventType );
return eventType;
}
}

@Override
@SafeVarargs
public final <T> EventType<T> contributeEventType(String name, Class<T> listenerRole, T... defaultListeners) {
final EventType<T> eventType = contributeEventType( name, listenerRole );
if ( defaultListeners != null ) {
listenerRegistryBuilder.getListenerGroup( eventType ).appendListeners( defaultListeners );
}
return eventType;
}

@Override
public <T> void configureListeners(EventType<T> eventType, Consumer<EventListenerGroup<T>> action) {
if ( !eventTypes.containsValue( eventType ) ) {
throw new HibernateException( "EventType [" + eventType + "] not registered" );
}
action.accept( listenerRegistryBuilder.getListenerGroup( eventType ) );
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -23,69 +23,73 @@

/**
* The intent of this class is to use a lighter implementation
* when JPA callbacks are disabled via
* {@link SessionFactoryOptions#areJPACallbacksEnabled()}
* when standard JPA entity lifecycle callbacks are disabled via
* {@link SessionFactoryOptions#areJPACallbacksEnabled()}.
*/
public final class CallbacksFactory {
private static final Logger log = Logger.getLogger( CallbacksFactory.class );

public static CallbackRegistry buildCallbackRegistry(SessionFactoryOptions options, ServiceRegistry serviceRegistry, Collection<PersistentClass> entityBindings) {
if ( !jpaCallBacksEnabled( options ) ) {
public static CallbackRegistry buildCallbackRegistry(
SessionFactoryOptions options, ServiceRegistry serviceRegistry, Collection<PersistentClass> entityBindings) {
if ( !options.areJPACallbacksEnabled() ) {
return new EmptyCallbackRegistryImpl();
}
ManagedBeanRegistry beanRegistry = serviceRegistry.getService( ManagedBeanRegistry.class );
CallbackRegistryImpl.Builder registryBuilder = new CallbackRegistryImpl.Builder();
Set<Class<?>> entityClasses = new HashSet<>();
final ManagedBeanRegistry beanRegistry = serviceRegistry.getService( ManagedBeanRegistry.class );
final CallbackRegistryImpl.Builder registryBuilder = new CallbackRegistryImpl.Builder();
final Set<Class<?>> entityClasses = new HashSet<>();

for ( PersistentClass persistentClass : entityBindings ) {
if ( persistentClass.getClassName() == null ) {
// we can have dynamic (non-java class) mapping
continue;
}

Class<?> entityClass = persistentClass.getMappedClass();

if ( !entityClasses.add( entityClass ) ) {
// this most likely means we have a class mapped multiple times using the hbm.xml
// "entity name" feature
if ( log.isDebugEnabled() ) {
log.debugf(
"Class [%s] already has callbacks registered; " +
"assuming this means the class was mapped twice " +
"(using hbm.xml entity-name support) - skipping subsequent registrations" +
"to avoid duplicates",
entityClass.getName()
);
if ( persistentClass.getClassName() != null ) {
final Class<?> entityClass = persistentClass.getMappedClass();
if ( !entityClasses.add( entityClass ) ) {
// this most likely means we have a class mapped multiple
// times using the hbm.xml "entity name" feature
if ( log.isDebugEnabled() ) {
log.debugf(
"Class [%s] already has callbacks registered; " +
"assuming this means the class was mapped twice " +
"(using hbm.xml entity-name support) - skipping subsequent registrations" +
"to avoid duplicates",
entityClass.getName()
);
}
}
else {
registerAllCallbacks( persistentClass, registryBuilder, entityClass, beanRegistry );
}
continue;
}

registryBuilder.registerCallbacks( persistentClass.getMappedClass(),
buildCallbacks( persistentClass.getCallbackDefinitions(), beanRegistry ) );

for ( Property property : persistentClass.getDeclaredProperties() ) {
registryBuilder.registerCallbacks( persistentClass.getMappedClass(),
buildCallbacks( property.getCallbackDefinitions(), beanRegistry ) );
}
// else we can have dynamic (non-java class) mapping
}

return registryBuilder.build();
}

private static void registerAllCallbacks(
PersistentClass persistentClass,
CallbackRegistryImpl.Builder registryBuilder,
Class<?> entityClass,
ManagedBeanRegistry beanRegistry) {
registryBuilder.registerCallbacks( entityClass,
buildCallbacks( persistentClass.getCallbackDefinitions(), beanRegistry ) );

for ( Property property : persistentClass.getDeclaredProperties() ) {
registryBuilder.registerCallbacks( entityClass,
buildCallbacks( property.getCallbackDefinitions(), beanRegistry ) );
}
}

private static Callback[] buildCallbacks(List<CallbackDefinition> callbackDefinitions,
ManagedBeanRegistry beanRegistry) {
if ( callbackDefinitions == null || callbackDefinitions.isEmpty() ) {
return null;
}
List<Callback> callbacks = new ArrayList<>();
for ( CallbackDefinition definition : callbackDefinitions ) {
callbacks.add( definition.createCallback( beanRegistry ) );
else {
final List<Callback> callbacks = new ArrayList<>();
for ( CallbackDefinition definition : callbackDefinitions ) {
callbacks.add( definition.createCallback( beanRegistry ) );
}
return callbacks.toArray( new Callback[0] );
}
return callbacks.toArray( new Callback[0] );
}

private static boolean jpaCallBacksEnabled(SessionFactoryOptions options) {
return options.areJPACallbacksEnabled();
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
package org.hibernate.jpa.event.spi;

/**
* Registry of Callbacks by entity and type
* Registry of JPA entity lifecycle callbacks by entity and type.
*
* @author Steve Ebersole
*/
Expand Down
Loading