This is an automated email from the ASF dual-hosted git repository. borinquenkid pushed a commit to branch test/document-datamapping-events in repository https://gitbox.apache.org/repos/asf/grails-core.git
commit 623be84e7126607de7f0b92e4a579f9f58803e52 Author: Walter Duque de Estrada <[email protected]> AuthorDate: Fri Aug 14 10:44:29 2026 -0500 Fix DomainEventListener IntelliJ warnings - entityEvents field made final (never reassigned) - Removed ZERO_PARAMS constant (zero references anywhere in the repo) - Parameterized raw ConnectionSourcesProvider/Class usages - supportsEventType now null-safe: Spring 7's SmartApplicationListener.supportsEventType declares its parameter @Nullable, and this override would NPE via Class.isAssignableFrom(null) - invokeEvent's ea.refresh() call is now guarded by the same ea != null check already used earlier in the method, closing a latent NPE path - Removed the two switch branches in onPersistenceEvent (SaveOrUpdate, Validation) that were duplicates of the default branch - Reordered each before/after method pair so the 2-arg overload holds the real logic and is canonical; the 3-arg overload (whose event parameter has been unused since the dead 1-arg-hook branch was removed) is now @Deprecated, forwards to the 2-arg overload, and is scheduled for removal in 9.0. onPersistenceEvent now calls the 2-arg overloads directly instead of the newly-deprecated 3-arg ones. Added tests for the deprecated overloads' delegation and the null eventType case. Full suite, jacoco, and codeStyle all clean. --- .../datastore/gorm/events/DomainEventListener.java | 123 ++++++++++++--------- .../gorm/events/DomainEventListenerSpec.groovy | 36 ++++++ 2 files changed, 108 insertions(+), 51 deletions(-) diff --git a/grails-datamapping-core/src/main/groovy/org/grails/datastore/gorm/events/DomainEventListener.java b/grails-datamapping-core/src/main/groovy/org/grails/datastore/gorm/events/DomainEventListener.java index fca82d846f..75899037c9 100644 --- a/grails-datamapping-core/src/main/groovy/org/grails/datastore/gorm/events/DomainEventListener.java +++ b/grails-datamapping-core/src/main/groovy/org/grails/datastore/gorm/events/DomainEventListener.java @@ -58,10 +58,8 @@ import org.grails.datastore.mapping.model.config.GormProperties; public class DomainEventListener extends AbstractPersistenceEventListener implements MappingContext.Listener { - private Map<PersistentEntity, Map<String, Method>> entityEvents = new ConcurrentHashMap<>(); + private final Map<PersistentEntity, Map<String, Method>> entityEvents = new ConcurrentHashMap<>(); - @SuppressWarnings("rawtypes") - public static final Class[] ZERO_PARAMS = {}; public static final String EVENT_BEFORE_INSERT = "beforeInsert"; private static final String EVENT_BEFORE_UPDATE = "beforeUpdate"; private static final String EVENT_BEFORE_DELETE = "beforeDelete"; @@ -84,15 +82,15 @@ public class DomainEventListener extends AbstractPersistenceEventListener } datastore.getMappingContext().addMappingContextListener(this); - if (datastore instanceof ConnectionSourcesProvider) { - autowireEntities = ((ConnectionSourcesProvider) datastore).getConnectionSources().getDefaultConnectionSource().getSettings().isAutowire(); + if (datastore instanceof ConnectionSourcesProvider<?, ?>) { + autowireEntities = ((ConnectionSourcesProvider<?, ?>) datastore).getConnectionSources().getDefaultConnectionSource().getSettings().isAutowire(); } else { autowireEntities = false; } } - protected DomainEventListener(ConnectionSourcesProvider connectionSourcesProvider, final MappingContext mappingContext) { + protected DomainEventListener(ConnectionSourcesProvider<?, ?> connectionSourcesProvider, final MappingContext mappingContext) { super(null); for (PersistentEntity entity : mappingContext.getPersistentEntities()) { @@ -106,54 +104,41 @@ public class DomainEventListener extends AbstractPersistenceEventListener protected void onPersistenceEvent(final AbstractPersistenceEvent event) { switch (event.getEventType()) { case PreInsert: - if (!beforeInsert(event.getEntity(), event.getEntityAccess(), (PreInsertEvent) event)) { + if (!beforeInsert(event.getEntity(), event.getEntityAccess())) { event.cancel(); } break; case PostInsert: - afterInsert(event.getEntity(), event.getEntityAccess(), (PostInsertEvent) event); + afterInsert(event.getEntity(), event.getEntityAccess()); break; case PreUpdate: - if (!beforeUpdate(event.getEntity(), event.getEntityAccess(), (PreUpdateEvent) event)) { + if (!beforeUpdate(event.getEntity(), event.getEntityAccess())) { event.cancel(); } break; case PostUpdate: - afterUpdate(event.getEntity(), event.getEntityAccess(), (PostUpdateEvent) event); + afterUpdate(event.getEntity(), event.getEntityAccess()); break; case PreDelete: - if (!beforeDelete(event.getEntity(), event.getEntityAccess(), (PreDeleteEvent) event)) { + if (!beforeDelete(event.getEntity(), event.getEntityAccess())) { event.cancel(); } break; case PostDelete: - afterDelete(event.getEntity(), event.getEntityAccess(), (PostDeleteEvent) event); + afterDelete(event.getEntity(), event.getEntityAccess()); break; case PreLoad: - beforeLoad(event.getEntity(), event.getEntityAccess(), (PreLoadEvent) event); + beforeLoad(event.getEntity(), event.getEntityAccess()); break; case PostLoad: - afterLoad(event.getEntity(), event.getEntityAccess(), (PostLoadEvent) event); - break; - case SaveOrUpdate: - break; - case Validation: + afterLoad(event.getEntity(), event.getEntityAccess()); break; default: break; } } - /** - * @deprecated Use {@link #beforeInsert(org.grails.datastore.mapping.model.PersistentEntity, org.grails.datastore.mapping.engine.EntityAccess, org.grails.datastore.mapping.engine.event.PreInsertEvent)} instead - */ - @Deprecated public boolean beforeInsert(final PersistentEntity entity, final EntityAccess ea) { - return beforeInsert(entity, ea, null); - } - - public boolean beforeInsert(final PersistentEntity entity, final EntityAccess ea, PreInsertEvent event) { - if (entity.isVersioned()) { try { setVersion(ea); @@ -166,8 +151,16 @@ public class DomainEventListener extends AbstractPersistenceEventListener return invokeEvent(EVENT_BEFORE_INSERT, entity, ea); } + /** + * @deprecated the {@code event} parameter is unused; use {@link #beforeInsert(PersistentEntity, EntityAccess)} instead. Scheduled for removal in 9.0. + */ + @Deprecated + public boolean beforeInsert(final PersistentEntity entity, final EntityAccess ea, @SuppressWarnings("unused") PreInsertEvent event) { + return beforeInsert(entity, ea); + } + protected void setVersion(final EntityAccess ea) { - final Class versionType = ea.getPersistentEntity().getVersion().getType(); + final Class<?> versionType = ea.getPersistentEntity().getVersion().getType(); if (Number.class.isAssignableFrom(versionType)) { ea.setProperty(GormProperties.VERSION, 0); } @@ -183,43 +176,63 @@ public class DomainEventListener extends AbstractPersistenceEventListener return invokeEvent(EVENT_BEFORE_UPDATE, entity, ea); } - public boolean beforeUpdate(final PersistentEntity entity, final EntityAccess ea, PreUpdateEvent event) { - return invokeEvent(EVENT_BEFORE_UPDATE, entity, ea); + /** + * @deprecated the {@code event} parameter is unused; use {@link #beforeUpdate(PersistentEntity, EntityAccess)} instead. Scheduled for removal in 9.0. + */ + @Deprecated + public boolean beforeUpdate(final PersistentEntity entity, final EntityAccess ea, @SuppressWarnings("unused") PreUpdateEvent event) { + return beforeUpdate(entity, ea); } public boolean beforeDelete(final PersistentEntity entity, final EntityAccess ea) { return invokeEvent(EVENT_BEFORE_DELETE, entity, ea); } - public boolean beforeDelete(final PersistentEntity entity, final EntityAccess ea, PreDeleteEvent event) { - return invokeEvent(EVENT_BEFORE_DELETE, entity, ea); + /** + * @deprecated the {@code event} parameter is unused; use {@link #beforeDelete(PersistentEntity, EntityAccess)} instead. Scheduled for removal in 9.0. + */ + @Deprecated + public boolean beforeDelete(final PersistentEntity entity, final EntityAccess ea, @SuppressWarnings("unused") PreDeleteEvent event) { + return beforeDelete(entity, ea); } public void beforeLoad(final PersistentEntity entity, final EntityAccess ea) { - beforeLoad(entity, ea, null); - } - - public void beforeLoad(final PersistentEntity entity, final EntityAccess ea, PreLoadEvent event) { invokeEvent(EVENT_BEFORE_LOAD, entity, ea); } - public void afterDelete(final PersistentEntity entity, final EntityAccess ea) { - afterDelete(entity, ea, null); + /** + * @deprecated the {@code event} parameter is unused; use {@link #beforeLoad(PersistentEntity, EntityAccess)} instead. Scheduled for removal in 9.0. + */ + @Deprecated + public void beforeLoad(final PersistentEntity entity, final EntityAccess ea, @SuppressWarnings("unused") PreLoadEvent event) { + beforeLoad(entity, ea); } - public void afterDelete(final PersistentEntity entity, final EntityAccess ea, PostDeleteEvent event) { + public void afterDelete(final PersistentEntity entity, final EntityAccess ea) { invokeEvent(EVENT_AFTER_DELETE, entity, ea); } - public void afterInsert(final PersistentEntity entity, final EntityAccess ea) { - afterInsert(entity, ea, null); + /** + * @deprecated the {@code event} parameter is unused; use {@link #afterDelete(PersistentEntity, EntityAccess)} instead. Scheduled for removal in 9.0. + */ + @Deprecated + public void afterDelete(final PersistentEntity entity, final EntityAccess ea, @SuppressWarnings("unused") PostDeleteEvent event) { + afterDelete(entity, ea); } - public void afterInsert(final PersistentEntity entity, final EntityAccess ea, PostInsertEvent event) { + public void afterInsert(final PersistentEntity entity, final EntityAccess ea) { activateDirtyChecking(ea); invokeEvent(EVENT_AFTER_INSERT, entity, ea); } + /** + * @deprecated the {@code event} parameter is unused; use {@link #afterInsert(PersistentEntity, EntityAccess)} instead. Scheduled for removal in 9.0. + */ + @Deprecated + public void afterInsert(final PersistentEntity entity, final EntityAccess ea, @SuppressWarnings("unused") PostInsertEvent event) { + afterInsert(entity, ea); + } + private void activateDirtyChecking(EntityAccess ea) { Object e = ea.getEntity(); if (e instanceof DirtyCheckable) { @@ -228,19 +241,19 @@ public class DomainEventListener extends AbstractPersistenceEventListener } public void afterUpdate(final PersistentEntity entity, final EntityAccess ea) { - afterUpdate(entity, ea, null); - } - - public void afterUpdate(final PersistentEntity entity, final EntityAccess ea, PostUpdateEvent event) { activateDirtyChecking(ea); // reset dirty checking invokeEvent(EVENT_AFTER_UPDATE, entity, ea); } - public void afterLoad(final PersistentEntity entity, final EntityAccess ea) { - afterLoad(entity, ea, null); + /** + * @deprecated the {@code event} parameter is unused; use {@link #afterUpdate(PersistentEntity, EntityAccess)} instead. Scheduled for removal in 9.0. + */ + @Deprecated + public void afterUpdate(final PersistentEntity entity, final EntityAccess ea, @SuppressWarnings("unused") PostUpdateEvent event) { + afterUpdate(entity, ea); } - public void afterLoad(final PersistentEntity entity, final EntityAccess ea, PostLoadEvent event) { + public void afterLoad(final PersistentEntity entity, final EntityAccess ea) { activateDirtyChecking(ea); if (autowireEntities || (entity != null && entity.getMapping().getMappedForm().isAutowire())) { autowireBeanProperties(ea.getEntity()); @@ -248,6 +261,14 @@ public class DomainEventListener extends AbstractPersistenceEventListener invokeEvent(EVENT_AFTER_LOAD, entity, ea); } + /** + * @deprecated the {@code event} parameter is unused; use {@link #afterLoad(PersistentEntity, EntityAccess)} instead. Scheduled for removal in 9.0. + */ + @Deprecated + public void afterLoad(final PersistentEntity entity, final EntityAccess ea, @SuppressWarnings("unused") PostLoadEvent event) { + afterLoad(entity, ea); + } + protected void autowireBeanProperties(final Object entity) { ConfigurableApplicationContext applicationContext = datastore.getApplicationContext(); if (applicationContext != null) { @@ -271,7 +292,7 @@ public class DomainEventListener extends AbstractPersistenceEventListener * java.lang.Class) */ public boolean supportsEventType(Class<? extends ApplicationEvent> eventType) { - return AbstractPersistenceEvent.class.isAssignableFrom(eventType); + return eventType != null && AbstractPersistenceEvent.class.isAssignableFrom(eventType); } private boolean invokeEvent(String eventName, PersistentEntity entity, EntityAccess ea) { @@ -294,7 +315,7 @@ public class DomainEventListener extends AbstractPersistenceEventListener } boolean booleanResult = (result instanceof Boolean) ? (Boolean) result : true; - if (booleanResult && REFRESH_EVENTS.contains(eventName)) { + if (ea != null && booleanResult && REFRESH_EVENTS.contains(eventName)) { ea.refresh(); } return booleanResult; diff --git a/grails-datamapping-core/src/test/groovy/org/grails/datastore/gorm/events/DomainEventListenerSpec.groovy b/grails-datamapping-core/src/test/groovy/org/grails/datastore/gorm/events/DomainEventListenerSpec.groovy index 319f27100c..01e6f61ee8 100644 --- a/grails-datamapping-core/src/test/groovy/org/grails/datastore/gorm/events/DomainEventListenerSpec.groovy +++ b/grails-datamapping-core/src/test/groovy/org/grails/datastore/gorm/events/DomainEventListenerSpec.groovy @@ -123,6 +123,14 @@ class DomainEventListenerSpec extends Specification { !listener.supportsEventType(PayloadApplicationEvent) } + void "supportsEventType rejects a null event type rather than throwing, matching SmartApplicationListener's nullable contract"() { + given: + DomainEventListener listener = new DomainEventListener(plainDatastore(Stub(MappingContext) { getPersistentEntities() >> [] })) + + expect: + !listener.supportsEventType(null) + } + void "beforeInsert sets an initial numeric version to 0 when the entity is versioned"() { given: PersistentEntity entity = entityFor(NoHooksDomain, true, Long) @@ -248,6 +256,34 @@ class DomainEventListenerSpec extends Specification { 'afterLoad' | 'afterLoad' } + @Unroll + @SuppressWarnings('deprecation') + void "the deprecated 3-arg #methodName(entity, ea, event) overload delegates to the 2-arg overload, ignoring the event argument"() { + given: + RecordingDomain domain = new RecordingDomain() + PersistentEntity entity = entityFor(RecordingDomain) + DomainEventListener listener = new DomainEventListener(plainDatastore(Stub(MappingContext) { getPersistentEntities() >> [] })) + listener.persistentEntityAdded(entity) + EntityAccess ea = Stub(EntityAccess) { getEntity() >> domain } + + when: + listener."$methodName"(entity, ea, null) + + then: + domain.invoked == [hookName] + + where: + methodName | hookName + 'beforeInsert' | 'beforeInsert' + 'beforeUpdate' | 'beforeUpdate' + 'beforeDelete' | 'beforeDelete' + 'beforeLoad' | 'beforeLoad' + 'afterInsert' | 'afterInsert' + 'afterUpdate' | 'afterUpdate' + 'afterDelete' | 'afterDelete' + 'afterLoad' | 'afterLoad' + } + void "afterInsert activates dirty checking on entities that implement DirtyCheckable"() { given: DirtyCheckableDomain domain = Spy(DirtyCheckableDomain)
