You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@isis.apache.org by ah...@apache.org on 2021/07/30 20:01:52 UTC

[isis] branch master updated: ISIS-2794: fixes LoadedLifecycleEvents - also adding tests

This is an automated email from the ASF dual-hosted git repository.

ahuber pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/isis.git


The following commit(s) were added to refs/heads/master by this push:
     new ea748af  ISIS-2794: fixes LoadedLifecycleEvents - also adding tests
ea748af is described below

commit ea748af95084e90ac63378a30217e5f728016153
Author: Andi Huber <ah...@apache.org>
AuthorDate: Fri Jul 30 22:01:42 2021 +0200

    ISIS-2794: fixes LoadedLifecycleEvents - also adding tests
---
 .../apache/isis/applib/events/EventObjectBase.java |  10 +-
 .../events/lifecycle/AbstractLifecycleEvent.java   |   4 +-
 ...acker.java => PersistenceLifecycleTracker.java} |  10 +-
 .../jpa/applib/integration/IsisEntityListener.java |  16 +-
 .../jpa/integration/IsisModuleJpaIntegration.java  |   4 +-
 ... => PersistenceLifecycleEventPublisherJpa.java} |  24 ++-
 .../LifecycleEventPublishingTestAbstract.java      | 173 +++++++++++++++++++--
 .../publishing/PublishingTestAbstract.java         |   5 +
 .../jdo/JdoLifecycleEventPublishingTest.java       |   7 +-
 .../jpa/JpaLifecycleEventPublishingTest.java       |   9 +-
 .../publishing/PublishingTestFactoryAbstract.java  |   1 +
 .../publishing/PublishingTestFactoryJdo.java       |  16 +-
 .../publishing/PublishingTestFactoryJpa.java       |  15 +-
 ...onfiguration_usingLifecycleEventPublishing.java |  27 ++--
 .../apache/isis/testdomain/util/dto/BookDto.java   |  17 ++
 .../event/LifecycleEventSubscriberForTesting.java  |  10 +-
 .../isis/testdomain/util/kv/KVStoreForTesting.java |   2 +
 17 files changed, 269 insertions(+), 81 deletions(-)

diff --git a/api/applib/src/main/java/org/apache/isis/applib/events/EventObjectBase.java b/api/applib/src/main/java/org/apache/isis/applib/events/EventObjectBase.java
index f97107c..7515930 100644
--- a/api/applib/src/main/java/org/apache/isis/applib/events/EventObjectBase.java
+++ b/api/applib/src/main/java/org/apache/isis/applib/events/EventObjectBase.java
@@ -22,7 +22,7 @@ import javax.annotation.Nullable;
 
 import org.apache.isis.commons.internal.exceptions._Exceptions;
 
-import static org.apache.isis.commons.internal.base._With.requires;
+import lombok.NonNull;
 
 /**
  * @since 2.0 {@index}
@@ -39,7 +39,7 @@ public abstract class EventObjectBase<T> {
      *
      * @param    source    The object on which the Event initially occurred.
      */
-    protected EventObjectBase(@Nullable T source) {
+    protected EventObjectBase(@Nullable final T source) {
         this.source = source;
     }
 
@@ -59,11 +59,10 @@ public abstract class EventObjectBase<T> {
      *
      * @param source non-null
      */
-    public void initSource(T source) {
+    public void initSource(final @NonNull T source) {
         if(this.source!=null) {
-            throw _Exceptions.unrecoverable(getClass().getName() + " cannot init when source is already set");
+            throw _Exceptions.illegalState(getClass().getName() + " cannot init when source is already set");
         }
-        requires(source, "source");
         this.source = source;
     }
 
@@ -72,6 +71,7 @@ public abstract class EventObjectBase<T> {
      *
      * @return  a String representation of this EventObject
      */
+    @Override
     public String toString() {
         return getClass().getName() + "[source=" + source + "]";
     }
diff --git a/api/applib/src/main/java/org/apache/isis/applib/events/lifecycle/AbstractLifecycleEvent.java b/api/applib/src/main/java/org/apache/isis/applib/events/lifecycle/AbstractLifecycleEvent.java
index 33b9a45..05cfc31 100644
--- a/api/applib/src/main/java/org/apache/isis/applib/events/lifecycle/AbstractLifecycleEvent.java
+++ b/api/applib/src/main/java/org/apache/isis/applib/events/lifecycle/AbstractLifecycleEvent.java
@@ -18,8 +18,6 @@
  */
 package org.apache.isis.applib.events.lifecycle;
 
-import javax.annotation.Nullable;
-
 import org.apache.isis.applib.events.EventObjectBase;
 
 /**
@@ -34,7 +32,7 @@ public abstract class AbstractLifecycleEvent<S> extends EventObjectBase<S> {
         this(null);
     }
 
-    public AbstractLifecycleEvent(S source) {
+    public AbstractLifecycleEvent(final S source) {
         super(source);
     }
 
diff --git a/core/transaction/src/main/java/org/apache/isis/core/transaction/changetracking/PropertyChangeTracker.java b/core/transaction/src/main/java/org/apache/isis/core/transaction/changetracking/PersistenceLifecycleTracker.java
similarity index 98%
rename from core/transaction/src/main/java/org/apache/isis/core/transaction/changetracking/PropertyChangeTracker.java
rename to core/transaction/src/main/java/org/apache/isis/core/transaction/changetracking/PersistenceLifecycleTracker.java
index 6090ca0e..f571890 100644
--- a/core/transaction/src/main/java/org/apache/isis/core/transaction/changetracking/PropertyChangeTracker.java
+++ b/core/transaction/src/main/java/org/apache/isis/core/transaction/changetracking/PersistenceLifecycleTracker.java
@@ -39,18 +39,20 @@ import lombok.NonNull;
  * @apiNote Introduced for JPA (EclipseLink implementation). More lightweight than
  * {@link EntityChangeTracker}
  */
-public interface PropertyChangeTracker {
+public interface PersistenceLifecycleTracker {
 
-    void onPreUpdate(ManagedObject entity, Can<PropertyChangeRecord> changeRecords);
+    void onPostLoad(ManagedObject entity);
 
     void onPrePersist(ManagedObject entity);
 
-    void onPreRemove(ManagedObject entity);
-
     void onPostPersist(ManagedObject entity);
 
+    void onPreUpdate(ManagedObject entity, Can<PropertyChangeRecord> changeRecords);
+
     void onPostUpdate(ManagedObject entity);
 
+    void onPreRemove(ManagedObject entity);
+
     //void onPostRemove(ManagedObject entity);
 
     // -- PUBLISHING PAYLOAD FACTORIES
diff --git a/persistence/jpa/applib/src/main/java/org/apache/isis/persistence/jpa/applib/integration/IsisEntityListener.java b/persistence/jpa/applib/src/main/java/org/apache/isis/persistence/jpa/applib/integration/IsisEntityListener.java
index 2f029d4..d2a8770 100644
--- a/persistence/jpa/applib/src/main/java/org/apache/isis/persistence/jpa/applib/integration/IsisEntityListener.java
+++ b/persistence/jpa/applib/src/main/java/org/apache/isis/persistence/jpa/applib/integration/IsisEntityListener.java
@@ -38,7 +38,7 @@ import org.apache.isis.core.metamodel.objectmanager.ObjectManager;
 import org.apache.isis.core.transaction.changetracking.EntityChangeTracker;
 import org.apache.isis.core.transaction.changetracking.PreAndPostValue;
 import org.apache.isis.core.transaction.changetracking.PropertyChangeRecord;
-import org.apache.isis.core.transaction.changetracking.PropertyChangeTracker;
+import org.apache.isis.core.transaction.changetracking.PersistenceLifecycleTracker;
 import org.apache.isis.persistence.jpa.applib.services.JpaSupportService;
 
 import lombok.val;
@@ -64,7 +64,7 @@ public class IsisEntityListener {
 
     // not managed by Spring (directly)
     @Inject private ServiceInjector serviceInjector;
-    @Inject private PropertyChangeTracker propertyChangePublisher;
+    @Inject private PersistenceLifecycleTracker persistenceLifecycleTracker;
     @Inject private Provider<JpaSupportService> jpaSupportServiceProvider;
     @Inject private ObjectManager objectManager;
 
@@ -72,7 +72,7 @@ public class IsisEntityListener {
         log.debug("onPrePersist: {}", entityPojo);
         serviceInjector.injectServicesInto(entityPojo);
         val entity = objectManager.adapt(entityPojo);
-        propertyChangePublisher.onPrePersist(entity);
+        persistenceLifecycleTracker.onPrePersist(entity);
     }
 
     @PreUpdate void onPreUpdate(final Object entityPojo) {
@@ -111,7 +111,7 @@ public class IsisEntityListener {
             })
             .collect(Can.toCan()); // a Can<T> only collects non-null elements
 
-            propertyChangePublisher.onPreUpdate(entity, propertyChangeRecords);
+            persistenceLifecycleTracker.onPreUpdate(entity, propertyChangeRecords);
 
         });
     }
@@ -120,19 +120,19 @@ public class IsisEntityListener {
         log.debug("onAnyRemove: {}", entityPojo);
         serviceInjector.injectServicesInto(entityPojo);
         val entity = objectManager.adapt(entityPojo);
-        propertyChangePublisher.onPreRemove(entity);
+        persistenceLifecycleTracker.onPreRemove(entity);
     }
 
     @PostPersist void onPostPersist(final Object entityPojo) {
         log.debug("onPostPersist: {}", entityPojo);
         val entity = objectManager.adapt(entityPojo);
-        propertyChangePublisher.onPostPersist(entity);
+        persistenceLifecycleTracker.onPostPersist(entity);
     }
 
     @PostUpdate void onPostUpdate(final Object entityPojo) {
         log.debug("onPostUpdate: {}", entityPojo);
         val entity = objectManager.adapt(entityPojo);
-        propertyChangePublisher.onPostUpdate(entity);
+        persistenceLifecycleTracker.onPostUpdate(entity);
     }
 
     @PostRemove void onPostRemove(final Object entityPojo) {
@@ -142,6 +142,8 @@ public class IsisEntityListener {
     @PostLoad void onPostLoad(final Object entityPojo) {
         log.debug("onPostLoad: {}", entityPojo);
         serviceInjector.injectServicesInto(entityPojo);
+        val entity = objectManager.adapt(entityPojo);
+        persistenceLifecycleTracker.onPostLoad(entity);
     }
 
 }
diff --git a/persistence/jpa/integration/src/main/java/org/apache/isis/persistence/jpa/integration/IsisModuleJpaIntegration.java b/persistence/jpa/integration/src/main/java/org/apache/isis/persistence/jpa/integration/IsisModuleJpaIntegration.java
index 77f9bc5..b4454cd 100644
--- a/persistence/jpa/integration/src/main/java/org/apache/isis/persistence/jpa/integration/IsisModuleJpaIntegration.java
+++ b/persistence/jpa/integration/src/main/java/org/apache/isis/persistence/jpa/integration/IsisModuleJpaIntegration.java
@@ -23,7 +23,7 @@ import org.springframework.context.annotation.Configuration;
 import org.springframework.context.annotation.Import;
 
 import org.apache.isis.core.runtime.IsisModuleCoreRuntime;
-import org.apache.isis.persistence.jpa.integration.changetracking.PropertyChangePublisherJpa;
+import org.apache.isis.persistence.jpa.integration.changetracking.PersistenceLifecycleEventPublisherJpa;
 import org.apache.isis.persistence.jpa.integration.metamodel.JpaProgrammingModel;
 import org.apache.isis.persistence.jpa.integration.services.JpaSupportServiceUsingSpring;
 import org.apache.isis.persistence.jpa.integration.typeconverters.JavaAwtBufferedImageByteArrayConverter;
@@ -39,7 +39,7 @@ import org.apache.isis.persistence.jpa.integration.typeconverters.JavaAwtBuffere
 
         // @Service's
         JpaSupportServiceUsingSpring.class,
-        PropertyChangePublisherJpa.class,
+        PersistenceLifecycleEventPublisherJpa.class,
 
 //        DataNucleusSettings.class,
 //        ExceptionRecognizerForSQLIntegrityConstraintViolationUniqueOrIndexException.class,
diff --git a/persistence/jpa/integration/src/main/java/org/apache/isis/persistence/jpa/integration/changetracking/PropertyChangePublisherJpa.java b/persistence/jpa/integration/src/main/java/org/apache/isis/persistence/jpa/integration/changetracking/PersistenceLifecycleEventPublisherJpa.java
similarity index 83%
rename from persistence/jpa/integration/src/main/java/org/apache/isis/persistence/jpa/integration/changetracking/PropertyChangePublisherJpa.java
rename to persistence/jpa/integration/src/main/java/org/apache/isis/persistence/jpa/integration/changetracking/PersistenceLifecycleEventPublisherJpa.java
index fd90dea..c7b377d 100644
--- a/persistence/jpa/integration/src/main/java/org/apache/isis/persistence/jpa/integration/changetracking/PropertyChangePublisherJpa.java
+++ b/persistence/jpa/integration/src/main/java/org/apache/isis/persistence/jpa/integration/changetracking/PersistenceLifecycleEventPublisherJpa.java
@@ -12,6 +12,8 @@ import org.apache.isis.applib.services.eventbus.EventBusService;
 import org.apache.isis.applib.services.metrics.MetricsService;
 import org.apache.isis.commons.collections.Can;
 import org.apache.isis.core.metamodel.facets.object.callbacks.CallbackFacet;
+import org.apache.isis.core.metamodel.facets.object.callbacks.LoadedCallbackFacet;
+import org.apache.isis.core.metamodel.facets.object.callbacks.LoadedLifecycleEventFacet;
 import org.apache.isis.core.metamodel.facets.object.callbacks.PersistedCallbackFacet;
 import org.apache.isis.core.metamodel.facets.object.callbacks.PersistedLifecycleEventFacet;
 import org.apache.isis.core.metamodel.facets.object.callbacks.PersistingCallbackFacet;
@@ -26,26 +28,26 @@ import org.apache.isis.core.metamodel.spec.ManagedObject;
 import org.apache.isis.core.transaction.changetracking.EntityPropertyChangePublisher;
 import org.apache.isis.core.transaction.changetracking.PersistenceCallbackHandlerAbstract;
 import org.apache.isis.core.transaction.changetracking.PropertyChangeRecord;
-import org.apache.isis.core.transaction.changetracking.PropertyChangeTracker;
+import org.apache.isis.core.transaction.changetracking.PersistenceLifecycleTracker;
 
 /**
  * @since 2.0 {@index}
  */
 @Service
-@Named("isis.transaction.PropertyChangePublisherJpa")
+@Named("isis.transaction.PersistenceLifecycleEventPublisherJpa")
 @Priority(PriorityPrecedence.EARLY)
 @Qualifier("jpa")
 //@Log4j2
-public class PropertyChangePublisherJpa
+public class PersistenceLifecycleEventPublisherJpa
 extends PersistenceCallbackHandlerAbstract
 implements
     MetricsService,
-    PropertyChangeTracker {
+    PersistenceLifecycleTracker {
 
     private final EntityPropertyChangePublisher entityPropertyChangePublisher;
 
     @Inject
-    public PropertyChangePublisherJpa(
+    public PersistenceLifecycleEventPublisherJpa(
             final EventBusService eventBusService,
             final EntityPropertyChangePublisher entityPropertyChangePublisher) {
         super(eventBusService);
@@ -71,7 +73,7 @@ implements
         postLifecycleEventIfRequired(entity, UpdatingLifecycleEventFacet.class);
 
         entityPropertyChangePublisher.publishChangedProperties(
-                PropertyChangeTracker
+                PersistenceLifecycleTracker
                 .publishingPayloadForUpdate(entity, changeRecords));
 
     }
@@ -82,7 +84,7 @@ implements
         postLifecycleEventIfRequired(entity, RemovingLifecycleEventFacet.class);
 
         entityPropertyChangePublisher.publishChangedProperties(
-                PropertyChangeTracker
+                PersistenceLifecycleTracker
                 .publishingPayloadForDeletion(entity));
     }
 
@@ -92,7 +94,7 @@ implements
         postLifecycleEventIfRequired(entity, PersistedLifecycleEventFacet.class);
 
         entityPropertyChangePublisher.publishChangedProperties(
-                PropertyChangeTracker
+                PersistenceLifecycleTracker
                 .publishingPayloadForCreation(entity));
     }
 
@@ -102,6 +104,12 @@ implements
         postLifecycleEventIfRequired(entity, UpdatedLifecycleEventFacet.class);
     }
 
+    @Override
+    public void onPostLoad(final ManagedObject entity) {
+        CallbackFacet.callCallback(entity, LoadedCallbackFacet.class);
+        postLifecycleEventIfRequired(entity, LoadedLifecycleEventFacet.class);
+    }
+
     // -- METRICS
 
     @Override
diff --git a/regressiontests/incubating/src/test/java/org/apache/isis/testdomain/publishing/LifecycleEventPublishingTestAbstract.java b/regressiontests/incubating/src/test/java/org/apache/isis/testdomain/publishing/LifecycleEventPublishingTestAbstract.java
index 3a2db5a..0f08964 100644
--- a/regressiontests/incubating/src/test/java/org/apache/isis/testdomain/publishing/LifecycleEventPublishingTestAbstract.java
+++ b/regressiontests/incubating/src/test/java/org/apache/isis/testdomain/publishing/LifecycleEventPublishingTestAbstract.java
@@ -46,7 +46,7 @@ extends PublishingTestAbstract {
 
     @Override
     protected final boolean supportsProgrammaticTesting(final ChangeScenario changeScenario) {
-        return false;
+        return changeScenario.isSupportsProgrammatic();
     }
 
     @Override
@@ -58,34 +58,126 @@ extends PublishingTestAbstract {
     protected void verify(
             final ChangeScenario changeScenario,
             final VerificationStage verificationStage) {
+
+        val bookSample1 = Can.of( // initial
+                BookDto
+                .sample());
+        val bookSample2 = Can.of( // after property update
+                BookDto
+                .sampleBuilder()
+                .name("Book #2")
+                .build());
+        val bookSample3 = Can.of( // after action invocation
+                BookDto
+                .sampleBuilder()
+                .price(198.0)
+                .build());
+
         switch(verificationStage) {
 
         case FAILURE_CASE:
 
-            assertHasLifecycleEvents(
-                    JdoBook.PersistingLifecycleEvent.class,
-                    JpaBook.PersistingLifecycleEvent.class,
-                    Can.empty());
+            assertHasLoadedLifecycleEvents(Can.empty());
+            assertHasPersistingLifecycleEvents(Can.empty());
+            assertHasPersistedLifecycleEvents(Can.empty());
+            assertHasUpdatingLifecycleEvents(Can.empty());
+            assertHasUpdatedLifecycleEvents(Can.empty());
+            assertHasRemovingLifecycleEvents(Can.empty());
+            return;
 
-            break;
         case PRE_COMMIT:
+
+            switch(changeScenario) {
+            case ENTITY_CREATION:
+
+                assertHasPersistingLifecycleEvents(bookSample1);
+                //assertHasPersistedLifecycleEvents(Can.empty()); //TODO what is expected empty or not?
+                return;
+
+            case ENTITY_LOADING:
+                //TODO what is there to verify?
+                return;
+
+            case PROPERTY_UPDATE: // update the book's name -> "Book #2"
+
+                //assertHasUpdatingLifecycleEvents(bookSample2); //FIXME PRE_COMMIT triggered too early?
+                //assertHasUpdatedLifecycleEvents(Can.empty()); //TODO what is expected empty or not?
+                return;
+
+            case ACTION_INVOCATION: // double the book's price action -> 198.0
+
+                //assertHasUpdatingLifecycleEvents(bookSample3);//FIXME PRE_COMMIT triggered too early?
+                //assertHasUpdatedLifecycleEvents(Can.empty()); //TODO what is expected empty or not?
+                return;
+
+            case ENTITY_REMOVAL:
+
+                assertHasRemovingLifecycleEvents(bookSample1);
+                return;
+
+            default:
+                throw _Exceptions.unmatchedCase(changeScenario);
+            }
+
         case POST_INTERACTION:
-            break;
         case POST_COMMIT:
 
             switch(changeScenario) {
-            case PROPERTY_UPDATE:
-                //TODO add assertions
-                break;
-            case ACTION_INVOCATION:
-                //TODO add assertions
-                break;
+            case ENTITY_CREATION:
+
+                assertHasLoadedLifecycleEvents(Can.empty());
+                assertHasPersistingLifecycleEvents(bookSample1);
+                assertHasPersistedLifecycleEvents(bookSample1);
+                assertHasUpdatingLifecycleEvents(Can.empty());
+                assertHasUpdatedLifecycleEvents(Can.empty());
+                assertHasRemovingLifecycleEvents(Can.empty());
+                return;
+
+            case ENTITY_LOADING:
+                assertHasLoadedLifecycleEvents(bookSample1);
+                assertHasPersistingLifecycleEvents(Can.empty());
+                assertHasPersistedLifecycleEvents(Can.empty());
+                assertHasUpdatingLifecycleEvents(Can.empty());
+                assertHasUpdatedLifecycleEvents(Can.empty());
+                assertHasRemovingLifecycleEvents(Can.empty());
+                return;
+
+
+            case PROPERTY_UPDATE: // update the book's name -> "Book #2"
+
+                assertHasLoadedLifecycleEvents(Can.empty());
+                assertHasPersistingLifecycleEvents(Can.empty());
+                assertHasPersistedLifecycleEvents(Can.empty());
+                assertHasUpdatingLifecycleEvents(bookSample2);
+                assertHasUpdatedLifecycleEvents(bookSample2);
+                assertHasRemovingLifecycleEvents(Can.empty());
+                return;
+
+            case ACTION_INVOCATION: // double the book's price action -> 198.0
+
+                assertHasLoadedLifecycleEvents(Can.empty());
+                assertHasPersistingLifecycleEvents(Can.empty());
+                assertHasPersistedLifecycleEvents(Can.empty());
+                assertHasUpdatingLifecycleEvents(bookSample3);
+                assertHasUpdatedLifecycleEvents(bookSample3);
+                assertHasRemovingLifecycleEvents(Can.empty());
+                return;
+
+            case ENTITY_REMOVAL:
+
+                assertHasLoadedLifecycleEvents(Can.empty());
+                assertHasPersistingLifecycleEvents(Can.empty());
+                assertHasPersistedLifecycleEvents(Can.empty());
+                assertHasUpdatingLifecycleEvents(Can.empty());
+                assertHasUpdatedLifecycleEvents(Can.empty());
+                assertHasRemovingLifecycleEvents(bookSample1);
+                return;
+
             default:
-                //TODO add assertions ...
                 throw _Exceptions.unmatchedCase(changeScenario);
             }
 
-            break;
+
         default:
             // if hitting this, the caller is requesting a verification stage, we are providing no case for
             fail(String.format("internal error, stage not verified: %s", verificationStage));
@@ -94,6 +186,57 @@ extends PublishingTestAbstract {
 
     // -- HELPER
 
+    //TODO also verify these
+    private void assertHasCreatedLifecycleEvents(final Can<BookDto> expectedBooks) {
+        assertHasLifecycleEvents(
+                JdoBook.CreatedLifecycleEvent.class,
+                JpaBook.CreatedLifecycleEvent.class,
+                expectedBooks);
+    }
+
+    //TODO also verify these
+    private void assertHasLoadedLifecycleEvents(final Can<BookDto> expectedBooks) {
+        assertHasLifecycleEvents(
+                JdoBook.LoadedLifecycleEvent.class,
+                JpaBook.LoadedLifecycleEvent.class,
+                expectedBooks);
+    }
+
+    private void assertHasPersistingLifecycleEvents(final Can<BookDto> expectedBooks) {
+        assertHasLifecycleEvents(
+                JdoBook.PersistingLifecycleEvent.class,
+                JpaBook.PersistingLifecycleEvent.class,
+                expectedBooks);
+    }
+
+    private void assertHasPersistedLifecycleEvents(final Can<BookDto> expectedBooks) {
+        assertHasLifecycleEvents(
+                JdoBook.PersistedLifecycleEvent.class,
+                JpaBook.PersistedLifecycleEvent.class,
+                expectedBooks);
+    }
+
+    private void assertHasUpdatingLifecycleEvents(final Can<BookDto> expectedBooks) {
+        assertHasLifecycleEvents(
+                JdoBook.UpdatingLifecycleEvent.class,
+                JpaBook.UpdatingLifecycleEvent.class,
+                expectedBooks);
+    }
+
+    private void assertHasUpdatedLifecycleEvents(final Can<BookDto> expectedBooks) {
+        assertHasLifecycleEvents(
+                JdoBook.UpdatedLifecycleEvent.class,
+                JpaBook.UpdatedLifecycleEvent.class,
+                expectedBooks);
+    }
+
+    private void assertHasRemovingLifecycleEvents(final Can<BookDto> expectedBooks) {
+        assertHasLifecycleEvents(
+                JdoBook.RemovingLifecycleEvent.class,
+                JpaBook.RemovingLifecycleEvent.class,
+                expectedBooks);
+    }
+
     private void assertHasLifecycleEvents(
             final Class<? extends AbstractLifecycleEvent<JdoBook>> eventClassWhenJdo,
             final Class<? extends AbstractLifecycleEvent<JpaBook>> eventClassWhenJpa,
diff --git a/regressiontests/incubating/src/test/java/org/apache/isis/testdomain/publishing/PublishingTestAbstract.java b/regressiontests/incubating/src/test/java/org/apache/isis/testdomain/publishing/PublishingTestAbstract.java
index 255a73b..0e0c82f 100644
--- a/regressiontests/incubating/src/test/java/org/apache/isis/testdomain/publishing/PublishingTestAbstract.java
+++ b/regressiontests/incubating/src/test/java/org/apache/isis/testdomain/publishing/PublishingTestAbstract.java
@@ -24,6 +24,11 @@ implements HasPersistenceStandard {
         return generateTests(ChangeScenario.ENTITY_CREATION);
     }
 
+    @TestFactory @DisplayName("Entity Loading")
+    final List<DynamicTest> generateTestsForLoading() {
+        return generateTests(ChangeScenario.ENTITY_LOADING);
+    }
+
     @TestFactory @DisplayName("Entity Removal")
     final List<DynamicTest> generateTestsForRemoval() {
         return generateTests(ChangeScenario.ENTITY_REMOVAL);
diff --git a/regressiontests/incubating/src/test/java/org/apache/isis/testdomain/publishing/jdo/JdoLifecycleEventPublishingTest.java b/regressiontests/incubating/src/test/java/org/apache/isis/testdomain/publishing/jdo/JdoLifecycleEventPublishingTest.java
index bb64720..150c946 100644
--- a/regressiontests/incubating/src/test/java/org/apache/isis/testdomain/publishing/jdo/JdoLifecycleEventPublishingTest.java
+++ b/regressiontests/incubating/src/test/java/org/apache/isis/testdomain/publishing/jdo/JdoLifecycleEventPublishingTest.java
@@ -28,18 +28,19 @@ import org.apache.isis.testdomain.conf.Configuration_usingJdo;
 import org.apache.isis.testdomain.publishing.LifecycleEventPublishingTestAbstract;
 import org.apache.isis.testdomain.publishing.PublishingTestFactoryAbstract;
 import org.apache.isis.testdomain.publishing.PublishingTestFactoryJdo;
-import org.apache.isis.testdomain.publishing.conf.Configuration_usingEntityPropertyChangePublishing;
+import org.apache.isis.testdomain.publishing.conf.Configuration_usingLifecycleEventPublishing;
 
 @SpringBootTest(
         classes = {
                 Configuration_usingJdo.class,
-                Configuration_usingEntityPropertyChangePublishing.class,
+                Configuration_usingLifecycleEventPublishing.class,
                 PublishingTestFactoryJdo.class,
                 //XrayEnable.class
         },
         properties = {
+                "logging.level.org.apache.isis.testdomain.util.event.LifecycleEventSubscriberForTesting=DEBUG",
                 "logging.level.org.springframework.orm.jpa.*=DEBUG",
-                "logging.level.org.apache.isis.testdomain.util.rest.KVStoreForTesting=DEBUG",
+                "logging.level.org.apache.isis.testdomain.util.kv.KVStoreForTesting=DEBUG",
         })
 @TestPropertySource({
     IsisPresets.UseLog4j2Test
diff --git a/regressiontests/incubating/src/test/java/org/apache/isis/testdomain/publishing/jpa/JpaLifecycleEventPublishingTest.java b/regressiontests/incubating/src/test/java/org/apache/isis/testdomain/publishing/jpa/JpaLifecycleEventPublishingTest.java
index a6eb993..bd58a60 100644
--- a/regressiontests/incubating/src/test/java/org/apache/isis/testdomain/publishing/jpa/JpaLifecycleEventPublishingTest.java
+++ b/regressiontests/incubating/src/test/java/org/apache/isis/testdomain/publishing/jpa/JpaLifecycleEventPublishingTest.java
@@ -28,18 +28,19 @@ import org.apache.isis.testdomain.conf.Configuration_usingJpa;
 import org.apache.isis.testdomain.publishing.LifecycleEventPublishingTestAbstract;
 import org.apache.isis.testdomain.publishing.PublishingTestFactoryAbstract;
 import org.apache.isis.testdomain.publishing.PublishingTestFactoryJpa;
-import org.apache.isis.testdomain.publishing.conf.Configuration_usingEntityPropertyChangePublishing;
+import org.apache.isis.testdomain.publishing.conf.Configuration_usingLifecycleEventPublishing;
 
 @SpringBootTest(
         classes = {
                 Configuration_usingJpa.class,
-                Configuration_usingEntityPropertyChangePublishing.class,
+                Configuration_usingLifecycleEventPublishing.class,
                 PublishingTestFactoryJpa.class,
                 //XrayEnable.class
         },
         properties = {
-                "logging.level.org.springframework.orm.jpa.*=DEBUG",
-                "logging.level.org.apache.isis.testdomain.util.rest.KVStoreForTesting=DEBUG",
+                "logging.level.org.apache.isis.testdomain.util.event.LifecycleEventSubscriberForTesting=DEBUG",
+                //"logging.level.org.springframework.orm.jpa.*=DEBUG",
+                "logging.level.org.apache.isis.testdomain.util.kv.KVStoreForTesting=DEBUG",
         })
 @TestPropertySource({
     IsisPresets.UseLog4j2Test
diff --git a/regressiontests/stable/src/main/java/org/apache/isis/testdomain/publishing/PublishingTestFactoryAbstract.java b/regressiontests/stable/src/main/java/org/apache/isis/testdomain/publishing/PublishingTestFactoryAbstract.java
index 6a6195b..cf33dd0 100644
--- a/regressiontests/stable/src/main/java/org/apache/isis/testdomain/publishing/PublishingTestFactoryAbstract.java
+++ b/regressiontests/stable/src/main/java/org/apache/isis/testdomain/publishing/PublishingTestFactoryAbstract.java
@@ -66,6 +66,7 @@ public abstract class PublishingTestFactoryAbstract {
     @RequiredArgsConstructor @Getter
     public static enum ChangeScenario {
         ENTITY_CREATION("creation", true),
+        ENTITY_LOADING("loading", true),
         PROPERTY_UPDATE("property update", false),
         ACTION_INVOCATION("action invocation", false),
         ENTITY_REMOVAL("removal", true);
diff --git a/regressiontests/stable/src/main/java/org/apache/isis/testdomain/publishing/PublishingTestFactoryJdo.java b/regressiontests/stable/src/main/java/org/apache/isis/testdomain/publishing/PublishingTestFactoryJdo.java
index cc9abaf..dcb540c 100644
--- a/regressiontests/stable/src/main/java/org/apache/isis/testdomain/publishing/PublishingTestFactoryJdo.java
+++ b/regressiontests/stable/src/main/java/org/apache/isis/testdomain/publishing/PublishingTestFactoryJdo.java
@@ -31,6 +31,7 @@ import javax.jdo.PersistenceManagerFactory;
 import org.springframework.context.annotation.Import;
 import org.springframework.stereotype.Component;
 
+import static org.junit.jupiter.api.Assertions.assertNotNull;
 import static org.junit.jupiter.api.Assertions.assertThrows;
 import static org.junit.jupiter.api.Assertions.fail;
 
@@ -54,6 +55,7 @@ import org.apache.isis.testdomain.jdo.entities.JdoBook;
 import org.apache.isis.testdomain.jdo.entities.JdoInventory;
 import org.apache.isis.testdomain.jdo.entities.JdoProduct;
 import org.apache.isis.testdomain.publishing.PublishingTestFactoryAbstract.CommitListener;
+import org.apache.isis.testdomain.util.dto.BookDto;
 import org.apache.isis.testing.fixtures.applib.fixturescripts.FixtureScripts;
 
 import static org.apache.isis.applib.services.wrapper.control.AsyncControl.returningVoid;
@@ -98,6 +100,7 @@ extends PublishingTestFactoryAbstract {
             fixtureScripts.runPersona(JdoTestDomainPersona.PurgeAll);
             break;
 
+        case ENTITY_LOADING:
         case PROPERTY_UPDATE:
         case ACTION_INVOCATION:
         case ENTITY_REMOVAL:
@@ -127,6 +130,15 @@ extends PublishingTestFactoryAbstract {
             setupBookForJdo();
             break;
 
+        case ENTITY_LOADING:
+
+            context.runGiven();
+            withBookDo(book->{
+                assertNotNull(book);
+            });
+            break;
+
+
         case PROPERTY_UPDATE:
 
             withBookDo(book->{
@@ -394,9 +406,7 @@ extends PublishingTestFactoryAbstract {
 
         val products = new HashSet<JdoProduct>();
 
-        products.add(JdoBook.of(
-                "Sample Book", "A sample book for testing.", 99.,
-                "Sample Author", "Sample ISBN", "Sample Publisher"));
+        products.add(BookDto.sample().toJdoBook());
 
         val inventory = JdoInventory.of("Sample Inventory", products);
         pm.makePersistent(inventory);
diff --git a/regressiontests/stable/src/main/java/org/apache/isis/testdomain/publishing/PublishingTestFactoryJpa.java b/regressiontests/stable/src/main/java/org/apache/isis/testdomain/publishing/PublishingTestFactoryJpa.java
index db5148f..b233d79 100644
--- a/regressiontests/stable/src/main/java/org/apache/isis/testdomain/publishing/PublishingTestFactoryJpa.java
+++ b/regressiontests/stable/src/main/java/org/apache/isis/testdomain/publishing/PublishingTestFactoryJpa.java
@@ -28,6 +28,7 @@ import javax.inject.Inject;
 import org.springframework.context.annotation.Import;
 import org.springframework.stereotype.Component;
 
+import static org.junit.jupiter.api.Assertions.assertNotNull;
 import static org.junit.jupiter.api.Assertions.assertThrows;
 import static org.junit.jupiter.api.Assertions.fail;
 
@@ -52,6 +53,7 @@ import org.apache.isis.testdomain.jpa.entities.JpaBook;
 import org.apache.isis.testdomain.jpa.entities.JpaInventory;
 import org.apache.isis.testdomain.jpa.entities.JpaProduct;
 import org.apache.isis.testdomain.publishing.PublishingTestFactoryAbstract.CommitListener;
+import org.apache.isis.testdomain.util.dto.BookDto;
 import org.apache.isis.testing.fixtures.applib.fixturescripts.FixtureScripts;
 
 import static org.apache.isis.applib.services.wrapper.control.AsyncControl.returningVoid;
@@ -94,6 +96,7 @@ extends PublishingTestFactoryAbstract {
             fixtureScripts.runPersona(JpaTestDomainPersona.PurgeAll);
             break;
 
+        case ENTITY_LOADING:
         case PROPERTY_UPDATE:
         case ACTION_INVOCATION:
         case ENTITY_REMOVAL:
@@ -122,6 +125,14 @@ extends PublishingTestFactoryAbstract {
             setupBookForJpa();
             break;
 
+        case ENTITY_LOADING:
+
+            context.runGiven();
+            withBookDo(book->{
+                assertNotNull(book);
+            });
+            break;
+
         case PROPERTY_UPDATE:
 
             withBookDo(book->{
@@ -387,9 +398,7 @@ extends PublishingTestFactoryAbstract {
 
         val products = new HashSet<JpaProduct>();
 
-        products.add(JpaBook.of(
-                "Sample Book", "A sample book for testing.", 99.,
-                "Sample Author", "Sample ISBN", "Sample Publisher"));
+        products.add(BookDto.sample().toJpaBook());
 
         val inventory = JpaInventory.of("Sample Inventory", products);
         em.persist(inventory);
diff --git a/api/applib/src/main/java/org/apache/isis/applib/events/lifecycle/AbstractLifecycleEvent.java b/regressiontests/stable/src/main/java/org/apache/isis/testdomain/publishing/conf/Configuration_usingLifecycleEventPublishing.java
similarity index 60%
copy from api/applib/src/main/java/org/apache/isis/applib/events/lifecycle/AbstractLifecycleEvent.java
copy to regressiontests/stable/src/main/java/org/apache/isis/testdomain/publishing/conf/Configuration_usingLifecycleEventPublishing.java
index 33b9a45..62d044e 100644
--- a/api/applib/src/main/java/org/apache/isis/applib/events/lifecycle/AbstractLifecycleEvent.java
+++ b/regressiontests/stable/src/main/java/org/apache/isis/testdomain/publishing/conf/Configuration_usingLifecycleEventPublishing.java
@@ -16,26 +16,17 @@
  *  specific language governing permissions and limitations
  *  under the License.
  */
-package org.apache.isis.applib.events.lifecycle;
+package org.apache.isis.testdomain.publishing.conf;
 
-import javax.annotation.Nullable;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.context.annotation.Import;
 
-import org.apache.isis.applib.events.EventObjectBase;
+import org.apache.isis.testdomain.util.event.LifecycleEventSubscriberForTesting;
 
-/**
- * Superclass for all lifecycle events that are raised by the framework when
- * loading, saving, updating or deleting objects from the database.
- *
- * @since 1.x {@index}
- */
-public abstract class AbstractLifecycleEvent<S> extends EventObjectBase<S> {
-
-    public AbstractLifecycleEvent() {
-        this(null);
-    }
-
-    public AbstractLifecycleEvent(S source) {
-        super(source);
-    }
+@Configuration
+@Import({
+    LifecycleEventSubscriberForTesting.class
+})
+public class Configuration_usingLifecycleEventPublishing {
 
 }
diff --git a/regressiontests/stable/src/main/java/org/apache/isis/testdomain/util/dto/BookDto.java b/regressiontests/stable/src/main/java/org/apache/isis/testdomain/util/dto/BookDto.java
index b15e8f5..9fa5bed 100644
--- a/regressiontests/stable/src/main/java/org/apache/isis/testdomain/util/dto/BookDto.java
+++ b/regressiontests/stable/src/main/java/org/apache/isis/testdomain/util/dto/BookDto.java
@@ -76,6 +76,21 @@ public class BookDto {
         .build();
     }
 
+    public static BookDto sample() {
+        return BookDto.sampleBuilder()
+                .build();
+    }
+
+    public static BookDtoBuilder sampleBuilder() {
+        return BookDto.builder()
+                .author("Sample Author")
+                .description("A sample book for testing.")
+                .isbn("Sample ISBN")
+                .name("Sample Book")
+                .price(99.)
+                .publisher("Sample Publisher");
+    }
+
     @Programmatic
     public JdoBook toJdoBook() {
        return JdoBook.of(this.getName(), this.getDescription(), this.getPrice(),
@@ -113,4 +128,6 @@ public class BookDto {
         return bookDto;
     }
 
+
+
 }
diff --git a/regressiontests/stable/src/main/java/org/apache/isis/testdomain/util/event/LifecycleEventSubscriberForTesting.java b/regressiontests/stable/src/main/java/org/apache/isis/testdomain/util/event/LifecycleEventSubscriberForTesting.java
index 79317cd..0338fa0 100644
--- a/regressiontests/stable/src/main/java/org/apache/isis/testdomain/util/event/LifecycleEventSubscriberForTesting.java
+++ b/regressiontests/stable/src/main/java/org/apache/isis/testdomain/util/event/LifecycleEventSubscriberForTesting.java
@@ -140,21 +140,19 @@ public class LifecycleEventSubscriberForTesting {
     private void storeJdoEvent(final AbstractLifecycleEvent<JdoBook> ev) {
         val eventType = ev.getClass().getName();
 
-        log.info("on {}", eventType);
+        log.debug("on {}", eventType);
 
         val bookDto = BookDto.from(ev.getSource());
-        kvStore.append(LifecycleEventSubscriberForTesting.class, eventType, bookDto);
-        kvStore.incrementCounter(LifecycleEventSubscriberForTesting.class, eventType);
+        kvStore.append(this, eventType, bookDto);
     }
 
     private void storeJpaEvent(final AbstractLifecycleEvent<JpaBook> ev) {
         val eventType = ev.getClass().getName();
 
-        log.info("on {}", eventType);
+        log.debug("on {}", eventType);
 
         val bookDto = BookDto.from(ev.getSource());
-        kvStore.append(LifecycleEventSubscriberForTesting.class, eventType, bookDto);
-        kvStore.incrementCounter(LifecycleEventSubscriberForTesting.class, eventType);
+        kvStore.append(this, eventType, bookDto);
     }
 
 
diff --git a/regressiontests/stable/src/main/java/org/apache/isis/testdomain/util/kv/KVStoreForTesting.java b/regressiontests/stable/src/main/java/org/apache/isis/testdomain/util/kv/KVStoreForTesting.java
index 5277f18..20a00d0 100644
--- a/regressiontests/stable/src/main/java/org/apache/isis/testdomain/util/kv/KVStoreForTesting.java
+++ b/regressiontests/stable/src/main/java/org/apache/isis/testdomain/util/kv/KVStoreForTesting.java
@@ -82,11 +82,13 @@ public class KVStoreForTesting {
         if(canRef.isEmpty()) {
             put(caller, keyStr, Can.ofSingleton(value));
         } else {
+            @SuppressWarnings("unchecked")
             val newCan = ((Can<Object>)canRef.get()).add(value);
             put(caller, keyStr, newCan);
         }
     }
 
+    @SuppressWarnings("unchecked")
     public Can<Object> getAll(final Class<?> callerType, final String keyStr) {
         val canRef = get(callerType, keyStr);
         return canRef.isEmpty()