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/31 13:41:44 UTC

[isis] branch master updated: ISIS-2794: fixes object creation callbacks/events not triggered

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 c9a18fc  ISIS-2794: fixes object creation callbacks/events not triggered
c9a18fc is described below

commit c9a18fc0ed5d74c2506c74d74340dcc70efa7ebe
Author: Andi Huber <ah...@apache.org>
AuthorDate: Sat Jul 31 15:41:36 2021 +0200

    ISIS-2794: fixes object creation callbacks/events not triggered
---
 .../objectmanager/create/ObjectCreator.java        |   2 +-
 .../create/ObjectCreator_builtinHandlers.java      | 106 ++++++---------------
 .../HasEnlistedEntityPropertyChanges.java          |   2 +-
 .../objectlifecycle/ObjectLifecyclePublisher.java} |  22 +++--
 .../services/objectlifecycle}/PreAndPostValue.java |  12 +--
 .../objectlifecycle}/PropertyChangeRecord.java     |   5 +-
 .../objectlifecycle/PropertyValuePlaceholder.java} |   4 +-
 .../IsisModuleCoreRuntimeServices.java             |   2 +
 .../factory/FactoryServiceDefault.java             |  29 +++---
 .../EntityPropertyChangePublisherDefault.java      |   3 +-
 .../publish/ObjectLifecyclePublisherDefault.java   |  45 ++++-----
 .../transaction/IsisModuleCoreTransaction.java     |   1 -
 .../EntityPropertyChangePublisher.java             |   1 +
 .../PreAndPostValues_shouldAudit_Test.java         |  13 +--
 .../changetracking/EntityChangeTrackerJdo.java     |   8 +-
 .../_EntityPropertyChangeFactory.java              |   2 +-
 .../jpa/applib/integration/IsisEntityListener.java |  23 +++--
 .../jpa/integration/IsisModuleJpaIntegration.java  |  20 +---
 .../PersistenceMetricsServiceJpa.java              |  36 +++++++
 .../LifecycleEventPublishingTestAbstract.java      |   2 +-
 .../util/interaction/InteractionTestAbstract.java  |  42 ++++----
 21 files changed, 174 insertions(+), 206 deletions(-)

diff --git a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/objectmanager/create/ObjectCreator.java b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/objectmanager/create/ObjectCreator.java
index b1306c5..3cfca99 100644
--- a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/objectmanager/create/ObjectCreator.java
+++ b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/objectmanager/create/ObjectCreator.java
@@ -58,7 +58,7 @@ public interface ObjectCreator {
     public static ObjectCreator createDefault(final MetaModelContext mmc) {
 
         val chainOfHandlers = _Lists.of(
-                new ObjectCreator_builtinHandlers.LegacyCreationHandler(mmc)
+                new ObjectCreator_builtinHandlers.DefaultCreationHandler(mmc)
 
 //                new ObjectCreator_builtinHandlers.GuardAgainstNull(),
 //                new ObjectCreator_builtinHandlers.LoadService(),
diff --git a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/objectmanager/create/ObjectCreator_builtinHandlers.java b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/objectmanager/create/ObjectCreator_builtinHandlers.java
index cae3d69..f1fd59f 100644
--- a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/objectmanager/create/ObjectCreator_builtinHandlers.java
+++ b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/objectmanager/create/ObjectCreator_builtinHandlers.java
@@ -19,23 +19,18 @@
 package org.apache.isis.core.metamodel.objectmanager.create;
 
 import java.lang.reflect.Array;
+import java.lang.reflect.InvocationTargetException;
 import java.lang.reflect.Modifier;
 
-import org.apache.isis.applib.events.lifecycle.AbstractLifecycleEvent;
-import org.apache.isis.applib.services.command.Command;
-import org.apache.isis.applib.services.eventbus.EventBusService;
-import org.apache.isis.commons.internal.base._Casts;
 import org.apache.isis.commons.internal.exceptions._Exceptions;
-import org.apache.isis.commons.internal.factory._InstanceUtil;
 import org.apache.isis.core.metamodel.context.MetaModelContext;
-import org.apache.isis.core.metamodel.facets.object.callbacks.CallbackFacet;
-import org.apache.isis.core.metamodel.facets.object.callbacks.CreatedCallbackFacet;
-import org.apache.isis.core.metamodel.facets.object.callbacks.CreatedLifecycleEventFacet;
-import org.apache.isis.core.metamodel.facets.object.callbacks.LifecycleEventFacet;
+import org.apache.isis.core.metamodel.services.objectlifecycle.ObjectLifecyclePublisher;
 import org.apache.isis.core.metamodel.spec.ManagedObject;
 import org.apache.isis.core.metamodel.spec.ObjectSpecification;
 import org.apache.isis.core.metamodel.spec.feature.MixedIn;
 
+import lombok.AccessLevel;
+import lombok.Getter;
 import lombok.NonNull;
 import lombok.Value;
 import lombok.val;
@@ -49,10 +44,16 @@ import lombok.extern.log4j.Log4j2;
 final class ObjectCreator_builtinHandlers {
 
     @Value @Log4j2
-    public static class LegacyCreationHandler implements ObjectCreator.Handler {
+    public static class DefaultCreationHandler implements ObjectCreator.Handler {
 
+        @Getter
         private final @NonNull MetaModelContext metaModelContext;
 
+        @Getter(lazy = true, value = AccessLevel.PRIVATE)
+        private final ObjectLifecyclePublisher persistenceLifecyclePublisher =
+            getMetaModelContext().getServiceRegistry()
+                    .lookupServiceElseFail(ObjectLifecyclePublisher.class);
+
         @Override
         public boolean isHandling(final ObjectCreator.Request objectCreateRequest) {
             return true;
@@ -67,14 +68,23 @@ final class ObjectCreator_builtinHandlers {
                 log.debug("creating instance of {}", spec);
             }
 
-            val pojo = instantiateAndInjectServices(spec);
-            val adapter = ManagedObject.of(spec, pojo);
-            return initializePropertiesAndDoCallback(adapter);
+            val pojo = metaModelContext.getServiceInjector()
+                    .injectServicesInto(instantiate(spec));
+            val domainObject = ManagedObject.of(spec, pojo);
+
+            // initialize new object
+            domainObject.getSpecification().streamAssociations(MixedIn.EXCLUDED)
+            .forEach(field->field.toDefault(domainObject));
+
+            getPersistenceLifecyclePublisher().onPostCreate(domainObject);
+
+            return domainObject;
+
         }
 
         //  -- HELPER
 
-        private Object instantiateAndInjectServices(final ObjectSpecification spec) {
+        private Object instantiate(final ObjectSpecification spec) {
 
             val type = spec.getCorrespondingClass();
             if (type.isArray()) {
@@ -87,79 +97,17 @@ final class ObjectCreator_builtinHandlers {
 
             try {
 
-                val newInstance = type.newInstance();
-                metaModelContext.getServiceInjector().injectServicesInto(newInstance);
+                val newInstance = type.getDeclaredConstructor().newInstance();
                 return newInstance;
 
-            } catch (IllegalAccessException | InstantiationException e) {
+            } catch (IllegalAccessException | InstantiationException | IllegalArgumentException
+                    | InvocationTargetException | NoSuchMethodException | SecurityException e) {
                 throw _Exceptions.unrecoverable(
                         "Failed to create instance of type " + spec.getFullIdentifier(), e);
             }
 
         }
 
-        private ManagedObject initializePropertiesAndDoCallback(final ManagedObject adapter) {
-
-            // initialize new object
-            adapter.getSpecification().streamAssociations(MixedIn.EXCLUDED)
-            .forEach(field->field.toDefault(adapter));
-
-             val pojo = adapter.getPojo();
-
-            CallbackFacet.callCallback(adapter, CreatedCallbackFacet.class);
-
-            if (Command.class.isAssignableFrom(pojo.getClass())) {
-
-                // special case... the command object is created while the transaction is being started and before
-                // the event bus service is initialized (nb: we initialize services *within* a transaction).  To resolve
-                // this catch-22 situation, we simply suppress the posting of this event for this domain class.
-
-                // this seems the least unpleasant of the various options available:
-                // * we could have put a check in the EventBusService to ignore the post if not yet initialized;
-                //   however this might hide other genuine errors
-                // * we could have used the thread-local in JdoStateManagerForIsis and the "skip(...)" hook in EventBusServiceJdo
-                //   to have this event be skipped; but that seems like co-opting some other design
-                // * we could have the transaction initialize the EventBusService as a "special case" before creating the Command;
-                //   but then do we worry about it being re-init'd later by the ServicesInitializer?
-
-                // so, doing it this way is this simplest, least obscure.
-
-                if(log.isDebugEnabled()) {
-                    log.debug("Skipping postEvent for creation of Command pojo");
-                }
-
-            } else {
-                postLifecycleEventIfRequired(adapter, CreatedLifecycleEventFacet.class);
-            }
-
-            return adapter;
-        }
-
-        private void postLifecycleEventIfRequired(
-                final ManagedObject adapter,
-                final Class<? extends LifecycleEventFacet> lifecycleEventFacetClass) {
-
-            val lifecycleEventFacet = adapter.getSpecification().getFacet(lifecycleEventFacetClass);
-            if(lifecycleEventFacet == null) {
-                return;
-            }
-
-            val eventType = lifecycleEventFacet.getEventType();
-            val instance = _InstanceUtil.createInstance(eventType);
-            val pojo = adapter.getPojo();
-            postEvent(_Casts.uncheckedCast(instance), pojo);
-
-        }
-
-        private <T> void postEvent(final AbstractLifecycleEvent<T> event, final T pojo) {
-
-            metaModelContext.getServiceRegistry()
-                .lookupService(EventBusService.class)
-                .ifPresent(eventBusService->{
-                    event.initSource(pojo);
-                    eventBusService.post(event);
-                });
-        }
 
 
     }
diff --git a/core/transaction/src/main/java/org/apache/isis/core/transaction/changetracking/HasEnlistedEntityPropertyChanges.java b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/services/objectlifecycle/HasEnlistedEntityPropertyChanges.java
similarity index 94%
rename from core/transaction/src/main/java/org/apache/isis/core/transaction/changetracking/HasEnlistedEntityPropertyChanges.java
rename to core/metamodel/src/main/java/org/apache/isis/core/metamodel/services/objectlifecycle/HasEnlistedEntityPropertyChanges.java
index f249b3f..6be1c6e 100644
--- a/core/transaction/src/main/java/org/apache/isis/core/transaction/changetracking/HasEnlistedEntityPropertyChanges.java
+++ b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/services/objectlifecycle/HasEnlistedEntityPropertyChanges.java
@@ -16,7 +16,7 @@
  *  specific language governing permissions and limitations
  *  under the License.
  */
-package org.apache.isis.core.transaction.changetracking;
+package org.apache.isis.core.metamodel.services.objectlifecycle;
 
 import java.sql.Timestamp;
 
diff --git a/core/transaction/src/main/java/org/apache/isis/core/transaction/changetracking/PersistenceLifecycleTracker.java b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/services/objectlifecycle/ObjectLifecyclePublisher.java
similarity index 88%
rename from core/transaction/src/main/java/org/apache/isis/core/transaction/changetracking/PersistenceLifecycleTracker.java
rename to core/metamodel/src/main/java/org/apache/isis/core/metamodel/services/objectlifecycle/ObjectLifecyclePublisher.java
index f571890..d43bdb7 100644
--- a/core/transaction/src/main/java/org/apache/isis/core/transaction/changetracking/PersistenceLifecycleTracker.java
+++ b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/services/objectlifecycle/ObjectLifecyclePublisher.java
@@ -16,30 +16,36 @@
  *  specific language governing permissions and limitations
  *  under the License.
  */
-package org.apache.isis.core.transaction.changetracking;
+package org.apache.isis.core.metamodel.services.objectlifecycle;
 
 import java.sql.Timestamp;
 
+import org.apache.isis.applib.services.factory.FactoryService;
 import org.apache.isis.applib.services.publishing.spi.EntityPropertyChange;
 import org.apache.isis.applib.services.xactn.TransactionId;
 import org.apache.isis.commons.collections.Can;
 import org.apache.isis.core.metamodel.facets.properties.property.entitychangepublishing.EntityPropertyChangePublishingPolicyFacet;
+import org.apache.isis.core.metamodel.objectmanager.ObjectManager;
 import org.apache.isis.core.metamodel.spec.ManagedObject;
 import org.apache.isis.core.metamodel.spec.feature.MixedIn;
-import org.apache.isis.core.transaction.changetracking.events.IsisTransactionPlaceholder;
 
 import lombok.NonNull;
 
 /**
  * Responsible for collecting, then immediately publishing changes to domain objects,
- * that occur within a transaction, that is,
+ * that is,
  * notify publishing subscribers and call the various persistence call-back facets.
  *
  * @since 2.0 {index}
- * @apiNote Introduced for JPA (EclipseLink implementation). More lightweight than
- * {@link EntityChangeTracker}
  */
-public interface PersistenceLifecycleTracker {
+public interface ObjectLifecyclePublisher {
+
+    /**
+     * Independent of the persistence stack, only triggered by {@link FactoryService}
+     * and internal {@link ObjectManager}.
+     * @param domainObject - an entity or view-model
+     */
+    void onPostCreate(ManagedObject domainObject);
 
     void onPostLoad(ManagedObject entity);
 
@@ -78,7 +84,7 @@ public interface PersistenceLifecycleTracker {
                             entity,
                             property,
                             PreAndPostValue
-                                .pre(IsisTransactionPlaceholder.NEW)
+                                .pre(PropertyValuePlaceholder.NEW)
                                 .withPost(property.get(entity).getPojo()))
                     .toEntityPropertyChange(
                             timestamp,
@@ -115,7 +121,7 @@ public interface PersistenceLifecycleTracker {
                             property,
                             PreAndPostValue
                                 .pre(property.get(entity).getPojo())
-                                .withPost(IsisTransactionPlaceholder.DELETED))
+                                .withPost(PropertyValuePlaceholder.DELETED))
                     .toEntityPropertyChange(
                             timestamp,
                             user,
diff --git a/core/transaction/src/main/java/org/apache/isis/core/transaction/changetracking/PreAndPostValue.java b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/services/objectlifecycle/PreAndPostValue.java
similarity index 87%
rename from core/transaction/src/main/java/org/apache/isis/core/transaction/changetracking/PreAndPostValue.java
rename to core/metamodel/src/main/java/org/apache/isis/core/metamodel/services/objectlifecycle/PreAndPostValue.java
index 5a284d5..8d1a053 100644
--- a/core/transaction/src/main/java/org/apache/isis/core/transaction/changetracking/PreAndPostValue.java
+++ b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/services/objectlifecycle/PreAndPostValue.java
@@ -16,12 +16,10 @@
  *  specific language governing permissions and limitations
  *  under the License.
  */
-package org.apache.isis.core.transaction.changetracking;
+package org.apache.isis.core.metamodel.services.objectlifecycle;
 
 import java.util.Objects;
 
-import org.apache.isis.core.transaction.changetracking.events.IsisTransactionPlaceholder;
-
 import lombok.Getter;
 
 public final class PreAndPostValue {
@@ -73,13 +71,13 @@ public final class PreAndPostValue {
 
     public boolean shouldPublish() {
         // don't audit objects that were created and then immediately deleted within the same xactn
-        if (getPre() == IsisTransactionPlaceholder.NEW
-                && getPost() == IsisTransactionPlaceholder.DELETED) {
+        if (getPre() == PropertyValuePlaceholder.NEW
+                && getPost() == PropertyValuePlaceholder.DELETED) {
             return false;
         }
         // but do always audit objects that have just been created or deleted
-        if (getPre() == IsisTransactionPlaceholder.NEW
-                || getPost() == IsisTransactionPlaceholder.DELETED) {
+        if (getPre() == PropertyValuePlaceholder.NEW
+                || getPost() == PropertyValuePlaceholder.DELETED) {
             return true;
         }
         // else - for updated objects - audit only if the property value has changed
diff --git a/core/transaction/src/main/java/org/apache/isis/core/transaction/changetracking/PropertyChangeRecord.java b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/services/objectlifecycle/PropertyChangeRecord.java
similarity index 95%
rename from core/transaction/src/main/java/org/apache/isis/core/transaction/changetracking/PropertyChangeRecord.java
rename to core/metamodel/src/main/java/org/apache/isis/core/metamodel/services/objectlifecycle/PropertyChangeRecord.java
index 1cd9b92..5383cc3 100644
--- a/core/transaction/src/main/java/org/apache/isis/core/transaction/changetracking/PropertyChangeRecord.java
+++ b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/services/objectlifecycle/PropertyChangeRecord.java
@@ -16,7 +16,7 @@
  *  specific language governing permissions and limitations
  *  under the License.
  */
-package org.apache.isis.core.transaction.changetracking;
+package org.apache.isis.core.metamodel.services.objectlifecycle;
 
 import java.sql.Timestamp;
 import java.util.UUID;
@@ -30,7 +30,6 @@ import org.apache.isis.core.metamodel.spec.ManagedObject;
 import org.apache.isis.core.metamodel.spec.ManagedObjects;
 import org.apache.isis.core.metamodel.spec.ManagedObjects.EntityUtil;
 import org.apache.isis.core.metamodel.spec.feature.ObjectAssociation;
-import org.apache.isis.core.transaction.changetracking.events.IsisTransactionPlaceholder;
 
 import lombok.EqualsAndHashCode;
 import lombok.Getter;
@@ -92,7 +91,7 @@ public final class PropertyChangeRecord {
     @Deprecated // unreliable logic, instead the caller should know if this originates from a delete event
     public void updatePostValue() {
         preAndPostValue = EntityUtil.isDetachedOrRemoved(entity) //TODO[ISIS-2573] when detached, logic is wrong
-                ? preAndPostValue.withPost(IsisTransactionPlaceholder.DELETED)
+                ? preAndPostValue.withPost(PropertyValuePlaceholder.DELETED)
                 : preAndPostValue.withPost(getPropertyValue());
     }
 
diff --git a/core/transaction/src/main/java/org/apache/isis/core/transaction/changetracking/events/IsisTransactionPlaceholder.java b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/services/objectlifecycle/PropertyValuePlaceholder.java
similarity index 90%
rename from core/transaction/src/main/java/org/apache/isis/core/transaction/changetracking/events/IsisTransactionPlaceholder.java
rename to core/metamodel/src/main/java/org/apache/isis/core/metamodel/services/objectlifecycle/PropertyValuePlaceholder.java
index dfb091b..7aebc66 100644
--- a/core/transaction/src/main/java/org/apache/isis/core/transaction/changetracking/events/IsisTransactionPlaceholder.java
+++ b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/services/objectlifecycle/PropertyValuePlaceholder.java
@@ -16,14 +16,14 @@
  *  specific language governing permissions and limitations
  *  under the License.
  */
-package org.apache.isis.core.transaction.changetracking.events;
+package org.apache.isis.core.metamodel.services.objectlifecycle;
 
 /**
  *
  * @since 2.0
  *
  */
-public enum IsisTransactionPlaceholder {
+public enum PropertyValuePlaceholder {
 
     NEW,
     DELETED
diff --git a/core/runtimeservices/src/main/java/org/apache/isis/core/runtimeservices/IsisModuleCoreRuntimeServices.java b/core/runtimeservices/src/main/java/org/apache/isis/core/runtimeservices/IsisModuleCoreRuntimeServices.java
index 8fec5b6..30ff866 100644
--- a/core/runtimeservices/src/main/java/org/apache/isis/core/runtimeservices/IsisModuleCoreRuntimeServices.java
+++ b/core/runtimeservices/src/main/java/org/apache/isis/core/runtimeservices/IsisModuleCoreRuntimeServices.java
@@ -48,6 +48,7 @@ import org.apache.isis.core.runtimeservices.publish.CommandPublisherDefault;
 import org.apache.isis.core.runtimeservices.publish.EntityChangesPublisherDefault;
 import org.apache.isis.core.runtimeservices.publish.EntityPropertyChangePublisherDefault;
 import org.apache.isis.core.runtimeservices.publish.ExecutionPublisherDefault;
+import org.apache.isis.core.runtimeservices.publish.ObjectLifecyclePublisherDefault;
 import org.apache.isis.core.runtimeservices.recognizer.ExceptionRecognizerServiceDefault;
 import org.apache.isis.core.runtimeservices.recognizer.dae.ExceptionRecognizerForDataAccessException;
 import org.apache.isis.core.runtimeservices.repository.RepositoryServiceDefault;
@@ -100,6 +101,7 @@ import org.apache.isis.core.runtimeservices.xmlsnapshot.XmlSnapshotServiceDefaul
         WrapperFactoryDefault.class,
         XmlServiceDefault.class,
         XmlSnapshotServiceDefault.class,
+        ObjectLifecyclePublisherDefault.class,
 
         // @Controller
         RoutingServiceDefault.class,
diff --git a/core/runtimeservices/src/main/java/org/apache/isis/core/runtimeservices/factory/FactoryServiceDefault.java b/core/runtimeservices/src/main/java/org/apache/isis/core/runtimeservices/factory/FactoryServiceDefault.java
index ca2d02b..9257e9a 100644
--- a/core/runtimeservices/src/main/java/org/apache/isis/core/runtimeservices/factory/FactoryServiceDefault.java
+++ b/core/runtimeservices/src/main/java/org/apache/isis/core/runtimeservices/factory/FactoryServiceDefault.java
@@ -39,6 +39,8 @@ import org.apache.isis.commons.internal.reflection._Reflect;
 import org.apache.isis.core.config.environment.IsisSystemEnvironment;
 import org.apache.isis.core.metamodel.facets.object.mixin.MixinFacet;
 import org.apache.isis.core.metamodel.facets.object.viewmodel.ViewModelFacet;
+import org.apache.isis.core.metamodel.services.objectlifecycle.ObjectLifecyclePublisher;
+import org.apache.isis.core.metamodel.spec.ManagedObject;
 import org.apache.isis.core.metamodel.spec.ObjectSpecification;
 import org.apache.isis.core.metamodel.specloader.SpecificationLoader;
 
@@ -58,6 +60,7 @@ public class FactoryServiceDefault implements FactoryService {
     @Inject private SpecificationLoader specificationLoader;
     @Inject private ServiceInjector serviceInjector;
     @Inject private IsisSystemEnvironment isisSystemEnvironment;
+    @Inject private ObjectLifecyclePublisher persistenceLifecyclePublisher;
 
     @Override
     public <T> T getOrCreate(final @NonNull Class<T> requiredType) {
@@ -85,16 +88,16 @@ public class FactoryServiceDefault implements FactoryService {
     }
 
     @Override
-    public <T> T detachedEntity(final @NonNull T entity) {
-        val entityClass = entity.getClass();
+    public <T> T detachedEntity(final @NonNull T entityPojo) {
+        val entityClass = entityPojo.getClass();
         val spec = loadSpec(entityClass);
         if(!spec.isEntity()) {
             throw _Exceptions.illegalArgument("Type '%s' is not recogniced as an entity type by the framework.",
                     entityClass);
         }
-
-        serviceInjector.injectServicesInto(entity);
-        return entity;
+        serviceInjector.injectServicesInto(entityPojo);
+        persistenceLifecyclePublisher.onPostCreate(ManagedObject.of(spec, entityPojo));
+        return entityPojo;
     }
 
     @Override
@@ -130,16 +133,16 @@ public class FactoryServiceDefault implements FactoryService {
     }
 
     @Override
-    public <T> T viewModel(final @NonNull T viewModel) {
-        val viewModelClass = viewModel.getClass();
+    public <T> T viewModel(final @NonNull T viewModelPojo) {
+        val viewModelClass = viewModelPojo.getClass();
         val spec = loadSpec(viewModelClass);
         if(!spec.isViewModel()) {
             throw _Exceptions.illegalArgument("Type '%s' is not recogniced as a ViewModel by the framework.",
                     viewModelClass);
         }
-
-        serviceInjector.injectServicesInto(viewModel);
-        return viewModel;
+        serviceInjector.injectServicesInto(viewModelPojo);
+        persistenceLifecyclePublisher.onPostCreate(ManagedObject.of(spec, viewModelPojo));
+        return viewModelPojo;
     }
 
     @Override
@@ -178,8 +181,10 @@ public class FactoryServiceDefault implements FactoryService {
         return viewModelFacet;
     }
 
-    private Object createObject(ObjectSpecification spec) {
-        return spec.createObject().getPojo();
+    private Object createObject(final ObjectSpecification spec) {
+        val domainObject = spec.createObject();
+        persistenceLifecyclePublisher.onPostCreate(domainObject);
+        return domainObject.getPojo();
     }
 
 
diff --git a/core/runtimeservices/src/main/java/org/apache/isis/core/runtimeservices/publish/EntityPropertyChangePublisherDefault.java b/core/runtimeservices/src/main/java/org/apache/isis/core/runtimeservices/publish/EntityPropertyChangePublisherDefault.java
index ea70c90..c561e7c 100644
--- a/core/runtimeservices/src/main/java/org/apache/isis/core/runtimeservices/publish/EntityPropertyChangePublisherDefault.java
+++ b/core/runtimeservices/src/main/java/org/apache/isis/core/runtimeservices/publish/EntityPropertyChangePublisherDefault.java
@@ -31,8 +31,9 @@ import org.apache.isis.applib.services.xactn.TransactionId;
 import org.apache.isis.applib.services.xactn.TransactionService;
 import org.apache.isis.commons.collections.Can;
 import org.apache.isis.commons.having.HasEnabling;
+import org.apache.isis.core.metamodel.services.objectlifecycle.HasEnlistedEntityPropertyChanges;
 import org.apache.isis.core.transaction.changetracking.EntityPropertyChangePublisher;
-import org.apache.isis.core.transaction.changetracking.HasEnlistedEntityPropertyChanges;
+
 import org.springframework.beans.factory.annotation.Qualifier;
 import org.springframework.stereotype.Service;
 
diff --git a/persistence/jpa/integration/src/main/java/org/apache/isis/persistence/jpa/integration/changetracking/PersistenceLifecycleEventPublisherJpa.java b/core/runtimeservices/src/main/java/org/apache/isis/core/runtimeservices/publish/ObjectLifecyclePublisherDefault.java
similarity index 80%
rename from persistence/jpa/integration/src/main/java/org/apache/isis/persistence/jpa/integration/changetracking/PersistenceLifecycleEventPublisherJpa.java
rename to core/runtimeservices/src/main/java/org/apache/isis/core/runtimeservices/publish/ObjectLifecyclePublisherDefault.java
index c7b377d..5589e45 100644
--- a/persistence/jpa/integration/src/main/java/org/apache/isis/persistence/jpa/integration/changetracking/PersistenceLifecycleEventPublisherJpa.java
+++ b/core/runtimeservices/src/main/java/org/apache/isis/core/runtimeservices/publish/ObjectLifecyclePublisherDefault.java
@@ -1,4 +1,4 @@
-package org.apache.isis.persistence.jpa.integration.changetracking;
+package org.apache.isis.core.runtimeservices.publish;
 
 import javax.annotation.Priority;
 import javax.inject.Inject;
@@ -9,9 +9,10 @@ import org.springframework.stereotype.Service;
 
 import org.apache.isis.applib.annotation.PriorityPrecedence;
 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.CreatedCallbackFacet;
+import org.apache.isis.core.metamodel.facets.object.callbacks.CreatedLifecycleEventFacet;
 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;
@@ -24,30 +25,30 @@ import org.apache.isis.core.metamodel.facets.object.callbacks.UpdatedCallbackFac
 import org.apache.isis.core.metamodel.facets.object.callbacks.UpdatedLifecycleEventFacet;
 import org.apache.isis.core.metamodel.facets.object.callbacks.UpdatingCallbackFacet;
 import org.apache.isis.core.metamodel.facets.object.callbacks.UpdatingLifecycleEventFacet;
+import org.apache.isis.core.metamodel.services.objectlifecycle.ObjectLifecyclePublisher;
+import org.apache.isis.core.metamodel.services.objectlifecycle.PropertyChangeRecord;
 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.PersistenceLifecycleTracker;
 
 /**
+ * @see ObjectLifecyclePublisher
  * @since 2.0 {@index}
  */
 @Service
-@Named("isis.transaction.PersistenceLifecycleEventPublisherJpa")
+@Named("isis.runtimeservices.ObjectLifecyclePublisherDefault")
 @Priority(PriorityPrecedence.EARLY)
-@Qualifier("jpa")
+@Qualifier("Default")
 //@Log4j2
-public class PersistenceLifecycleEventPublisherJpa
+public class ObjectLifecyclePublisherDefault
 extends PersistenceCallbackHandlerAbstract
 implements
-    MetricsService,
-    PersistenceLifecycleTracker {
+    ObjectLifecyclePublisher {
 
     private final EntityPropertyChangePublisher entityPropertyChangePublisher;
 
     @Inject
-    public PersistenceLifecycleEventPublisherJpa(
+    public ObjectLifecyclePublisherDefault(
             final EventBusService eventBusService,
             final EntityPropertyChangePublisher entityPropertyChangePublisher) {
         super(eventBusService);
@@ -55,6 +56,12 @@ implements
     }
 
     @Override
+    public void onPostCreate(final ManagedObject domainObject) {
+        CallbackFacet.callCallback(domainObject, CreatedCallbackFacet.class);
+        postLifecycleEventIfRequired(domainObject, CreatedLifecycleEventFacet.class);
+    }
+
+    @Override
     public void onPrePersist(final ManagedObject entity) {
         CallbackFacet.callCallback(entity, PersistingCallbackFacet.class);
         postLifecycleEventIfRequired(entity, PersistingLifecycleEventFacet.class);
@@ -73,7 +80,7 @@ implements
         postLifecycleEventIfRequired(entity, UpdatingLifecycleEventFacet.class);
 
         entityPropertyChangePublisher.publishChangedProperties(
-                PersistenceLifecycleTracker
+                ObjectLifecyclePublisher
                 .publishingPayloadForUpdate(entity, changeRecords));
 
     }
@@ -84,7 +91,7 @@ implements
         postLifecycleEventIfRequired(entity, RemovingLifecycleEventFacet.class);
 
         entityPropertyChangePublisher.publishChangedProperties(
-                PersistenceLifecycleTracker
+                ObjectLifecyclePublisher
                 .publishingPayloadForDeletion(entity));
     }
 
@@ -94,7 +101,7 @@ implements
         postLifecycleEventIfRequired(entity, PersistedLifecycleEventFacet.class);
 
         entityPropertyChangePublisher.publishChangedProperties(
-                PersistenceLifecycleTracker
+                ObjectLifecyclePublisher
                 .publishingPayloadForCreation(entity));
     }
 
@@ -110,16 +117,4 @@ implements
         postLifecycleEventIfRequired(entity, LoadedLifecycleEventFacet.class);
     }
 
-    // -- METRICS
-
-    @Override
-    public int numberEntitiesLoaded() {
-        return -1; // n/a
-    }
-
-    @Override
-    public int numberEntitiesDirtied() {
-        return -1; // n/a
-    }
-
 }
diff --git a/core/transaction/src/main/java/org/apache/isis/core/transaction/IsisModuleCoreTransaction.java b/core/transaction/src/main/java/org/apache/isis/core/transaction/IsisModuleCoreTransaction.java
index dd25c15..90b1a0f 100644
--- a/core/transaction/src/main/java/org/apache/isis/core/transaction/IsisModuleCoreTransaction.java
+++ b/core/transaction/src/main/java/org/apache/isis/core/transaction/IsisModuleCoreTransaction.java
@@ -31,5 +31,4 @@ import org.apache.isis.core.transaction.changetracking.events.TimestampService;
 })
 public class IsisModuleCoreTransaction {
 
-
 }
diff --git a/core/transaction/src/main/java/org/apache/isis/core/transaction/changetracking/EntityPropertyChangePublisher.java b/core/transaction/src/main/java/org/apache/isis/core/transaction/changetracking/EntityPropertyChangePublisher.java
index fa4216c..58ff0cf 100644
--- a/core/transaction/src/main/java/org/apache/isis/core/transaction/changetracking/EntityPropertyChangePublisher.java
+++ b/core/transaction/src/main/java/org/apache/isis/core/transaction/changetracking/EntityPropertyChangePublisher.java
@@ -19,6 +19,7 @@
 package org.apache.isis.core.transaction.changetracking;
 
 import org.apache.isis.applib.services.publishing.spi.EntityPropertyChange;
+import org.apache.isis.core.metamodel.services.objectlifecycle.HasEnlistedEntityPropertyChanges;
 
 /**
  * Notifies {@link org.apache.isis.applib.services.publishing.spi.EntityPropertyChangeSubscriber}s.
diff --git a/core/transaction/src/test/java/org/apache/isis/core/transaction/changetracking/PreAndPostValues_shouldAudit_Test.java b/core/transaction/src/test/java/org/apache/isis/core/transaction/changetracking/PreAndPostValues_shouldAudit_Test.java
index 554ab1b..8198bb2 100644
--- a/core/transaction/src/test/java/org/apache/isis/core/transaction/changetracking/PreAndPostValues_shouldAudit_Test.java
+++ b/core/transaction/src/test/java/org/apache/isis/core/transaction/changetracking/PreAndPostValues_shouldAudit_Test.java
@@ -21,9 +21,10 @@ package org.apache.isis.core.transaction.changetracking;
 import static org.junit.jupiter.api.Assertions.assertFalse;
 import static org.junit.jupiter.api.Assertions.assertTrue;
 
-import org.junit.jupiter.api.Test;
+import org.apache.isis.core.metamodel.services.objectlifecycle.PropertyValuePlaceholder;
+import org.apache.isis.core.metamodel.services.objectlifecycle.PreAndPostValue;
 
-import org.apache.isis.core.transaction.changetracking.events.IsisTransactionPlaceholder;
+import org.junit.jupiter.api.Test;
 
 import lombok.val;
 
@@ -32,7 +33,7 @@ public class PreAndPostValues_shouldAudit_Test {
 
     @Test
     public void just_created() {
-        val preAndPostValue = PreAndPostValue.pre(IsisTransactionPlaceholder.NEW)
+        val preAndPostValue = PreAndPostValue.pre(PropertyValuePlaceholder.NEW)
                 .withPost("Foo");
 
         assertTrue(preAndPostValue.shouldPublish());
@@ -40,7 +41,7 @@ public class PreAndPostValues_shouldAudit_Test {
     @Test
     public void just_deleted() {
         val preAndPostValue = PreAndPostValue.pre("Foo")
-                .withPost(IsisTransactionPlaceholder.DELETED);
+                .withPost(PropertyValuePlaceholder.DELETED);
 
         assertTrue(preAndPostValue.shouldPublish());
     }
@@ -60,8 +61,8 @@ public class PreAndPostValues_shouldAudit_Test {
     }
     @Test
     public void created_and_then_deleted() {
-        val preAndPostValue = PreAndPostValue.pre(IsisTransactionPlaceholder.NEW)
-                .withPost(IsisTransactionPlaceholder.DELETED);
+        val preAndPostValue = PreAndPostValue.pre(PropertyValuePlaceholder.NEW)
+                .withPost(PropertyValuePlaceholder.DELETED);
 
         assertFalse(preAndPostValue.shouldPublish());
     }
diff --git a/persistence/jdo/integration/src/main/java/org/apache/isis/persistence/jdo/integration/changetracking/EntityChangeTrackerJdo.java b/persistence/jdo/integration/src/main/java/org/apache/isis/persistence/jdo/integration/changetracking/EntityChangeTrackerJdo.java
index faa0dfd..bc1f50a 100644
--- a/persistence/jdo/integration/src/main/java/org/apache/isis/persistence/jdo/integration/changetracking/EntityChangeTrackerJdo.java
+++ b/persistence/jdo/integration/src/main/java/org/apache/isis/persistence/jdo/integration/changetracking/EntityChangeTrackerJdo.java
@@ -66,6 +66,9 @@ import org.apache.isis.core.metamodel.facets.object.callbacks.UpdatingCallbackFa
 import org.apache.isis.core.metamodel.facets.object.callbacks.UpdatingLifecycleEventFacet;
 import org.apache.isis.core.metamodel.facets.object.publish.entitychange.EntityChangePublishingFacet;
 import org.apache.isis.core.metamodel.facets.properties.property.entitychangepublishing.EntityPropertyChangePublishingPolicyFacet;
+import org.apache.isis.core.metamodel.services.objectlifecycle.HasEnlistedEntityPropertyChanges;
+import org.apache.isis.core.metamodel.services.objectlifecycle.PropertyValuePlaceholder;
+import org.apache.isis.core.metamodel.services.objectlifecycle.PropertyChangeRecord;
 import org.apache.isis.core.metamodel.spec.ManagedObject;
 import org.apache.isis.core.metamodel.spec.ManagedObjects;
 import org.apache.isis.core.metamodel.spec.feature.MixedIn;
@@ -73,10 +76,7 @@ import org.apache.isis.core.transaction.changetracking.EntityChangeTracker;
 import org.apache.isis.core.transaction.changetracking.EntityChangesPublisher;
 import org.apache.isis.core.transaction.changetracking.EntityPropertyChangePublisher;
 import org.apache.isis.core.transaction.changetracking.HasEnlistedEntityChanges;
-import org.apache.isis.core.transaction.changetracking.HasEnlistedEntityPropertyChanges;
 import org.apache.isis.core.transaction.changetracking.PersistenceCallbackHandlerAbstract;
-import org.apache.isis.core.transaction.changetracking.PropertyChangeRecord;
-import org.apache.isis.core.transaction.changetracking.events.IsisTransactionPlaceholder;
 import org.apache.isis.core.transaction.events.TransactionBeforeCompletionEvent;
 
 import lombok.AccessLevel;
@@ -140,7 +140,7 @@ implements
             return;
         }
         enlistForChangeKindPublishing(adapter, EntityChangeKind.CREATE);
-        enlistForPreAndPostValuePublishing(adapter, record->record.setPreValue(IsisTransactionPlaceholder.NEW));
+        enlistForPreAndPostValuePublishing(adapter, record->record.setPreValue(PropertyValuePlaceholder.NEW));
     }
 
     private void enlistUpdatingInternal(
diff --git a/persistence/jdo/integration/src/main/java/org/apache/isis/persistence/jdo/integration/changetracking/_EntityPropertyChangeFactory.java b/persistence/jdo/integration/src/main/java/org/apache/isis/persistence/jdo/integration/changetracking/_EntityPropertyChangeFactory.java
index bb4023c..728d1e5 100644
--- a/persistence/jdo/integration/src/main/java/org/apache/isis/persistence/jdo/integration/changetracking/_EntityPropertyChangeFactory.java
+++ b/persistence/jdo/integration/src/main/java/org/apache/isis/persistence/jdo/integration/changetracking/_EntityPropertyChangeFactory.java
@@ -24,7 +24,7 @@ import org.apache.isis.applib.services.bookmark.Bookmark;
 import org.apache.isis.applib.services.publishing.spi.EntityPropertyChange;
 import org.apache.isis.applib.services.xactn.TransactionId;
 import org.apache.isis.core.metamodel.facets.actions.action.invocation.CommandUtil;
-import org.apache.isis.core.transaction.changetracking.PropertyChangeRecord;
+import org.apache.isis.core.metamodel.services.objectlifecycle.PropertyChangeRecord;
 
 import lombok.val;
 
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 d2a8770..a3035dc 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
@@ -35,10 +35,9 @@ import org.apache.isis.applib.services.inject.ServiceInjector;
 import org.apache.isis.commons.collections.Can;
 import org.apache.isis.core.metamodel.facets.properties.property.entitychangepublishing.EntityPropertyChangePublishingPolicyFacet;
 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.PersistenceLifecycleTracker;
+import org.apache.isis.core.metamodel.services.objectlifecycle.ObjectLifecyclePublisher;
+import org.apache.isis.core.metamodel.services.objectlifecycle.PreAndPostValue;
+import org.apache.isis.core.metamodel.services.objectlifecycle.PropertyChangeRecord;
 import org.apache.isis.persistence.jpa.applib.services.JpaSupportService;
 
 import lombok.val;
@@ -46,7 +45,7 @@ import lombok.extern.log4j.Log4j2;
 
 /**
  * EntityListener class for listing with the {@link javax.persistence.EntityListeners} annotation, to
- * support injection point resolving for entities, and to notify {@link EntityChangeTracker} of changes.
+ * support injection point resolving for entities, and to notify {@link ObjectLifecyclePublisher} of changes.
  *
  * <p>
  * Instances of this class are not managed by Spring, but by the persistence layer.
@@ -64,7 +63,7 @@ public class IsisEntityListener {
 
     // not managed by Spring (directly)
     @Inject private ServiceInjector serviceInjector;
-    @Inject private PersistenceLifecycleTracker persistenceLifecycleTracker;
+    @Inject private ObjectLifecyclePublisher objectLifecyclePublisher;
     @Inject private Provider<JpaSupportService> jpaSupportServiceProvider;
     @Inject private ObjectManager objectManager;
 
@@ -72,7 +71,7 @@ public class IsisEntityListener {
         log.debug("onPrePersist: {}", entityPojo);
         serviceInjector.injectServicesInto(entityPojo);
         val entity = objectManager.adapt(entityPojo);
-        persistenceLifecycleTracker.onPrePersist(entity);
+        objectLifecyclePublisher.onPrePersist(entity);
     }
 
     @PreUpdate void onPreUpdate(final Object entityPojo) {
@@ -111,7 +110,7 @@ public class IsisEntityListener {
             })
             .collect(Can.toCan()); // a Can<T> only collects non-null elements
 
-            persistenceLifecycleTracker.onPreUpdate(entity, propertyChangeRecords);
+            objectLifecyclePublisher.onPreUpdate(entity, propertyChangeRecords);
 
         });
     }
@@ -120,19 +119,19 @@ public class IsisEntityListener {
         log.debug("onAnyRemove: {}", entityPojo);
         serviceInjector.injectServicesInto(entityPojo);
         val entity = objectManager.adapt(entityPojo);
-        persistenceLifecycleTracker.onPreRemove(entity);
+        objectLifecyclePublisher.onPreRemove(entity);
     }
 
     @PostPersist void onPostPersist(final Object entityPojo) {
         log.debug("onPostPersist: {}", entityPojo);
         val entity = objectManager.adapt(entityPojo);
-        persistenceLifecycleTracker.onPostPersist(entity);
+        objectLifecyclePublisher.onPostPersist(entity);
     }
 
     @PostUpdate void onPostUpdate(final Object entityPojo) {
         log.debug("onPostUpdate: {}", entityPojo);
         val entity = objectManager.adapt(entityPojo);
-        persistenceLifecycleTracker.onPostUpdate(entity);
+        objectLifecyclePublisher.onPostUpdate(entity);
     }
 
     @PostRemove void onPostRemove(final Object entityPojo) {
@@ -143,7 +142,7 @@ public class IsisEntityListener {
         log.debug("onPostLoad: {}", entityPojo);
         serviceInjector.injectServicesInto(entityPojo);
         val entity = objectManager.adapt(entityPojo);
-        persistenceLifecycleTracker.onPostLoad(entity);
+        objectLifecyclePublisher.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 b4454cd..84c418a 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.PersistenceLifecycleEventPublisherJpa;
+import org.apache.isis.persistence.jpa.integration.changetracking.PersistenceMetricsServiceJpa;
 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,24 +39,8 @@ import org.apache.isis.persistence.jpa.integration.typeconverters.JavaAwtBuffere
 
         // @Service's
         JpaSupportServiceUsingSpring.class,
-        PersistenceLifecycleEventPublisherJpa.class,
+        PersistenceMetricsServiceJpa.class,
 
-//        DataNucleusSettings.class,
-//        ExceptionRecognizerForSQLIntegrityConstraintViolationUniqueOrIndexException.class,
-//        ExceptionRecognizerForJDODataStoreExceptionIntegrityConstraintViolationForeignKeyNoActionException.class,
-//        ExceptionRecognizerForJDOObjectNotFoundException.class,
-//        ExceptionRecognizerForJDODataStoreException.class,
-//
-//        IsisJdoSupportDN5.class,
-//        IsisPlatformTransactionManagerForJdo.class,
-//        JdoPersistenceLifecycleService.class,
-//        PersistenceSessionFactory5.class,
-//        JdoMetamodelMenu.class,
-//
-//        // @Mixin's
-//        Persistable_datanucleusVersionLong.class,
-//        Persistable_datanucleusVersionTimestamp.class,
-//        Persistable_downloadJdoMetadata.class,
 })
 @EntityScan(basePackageClasses = {
 
diff --git a/persistence/jpa/integration/src/main/java/org/apache/isis/persistence/jpa/integration/changetracking/PersistenceMetricsServiceJpa.java b/persistence/jpa/integration/src/main/java/org/apache/isis/persistence/jpa/integration/changetracking/PersistenceMetricsServiceJpa.java
new file mode 100644
index 0000000..64401e8
--- /dev/null
+++ b/persistence/jpa/integration/src/main/java/org/apache/isis/persistence/jpa/integration/changetracking/PersistenceMetricsServiceJpa.java
@@ -0,0 +1,36 @@
+package org.apache.isis.persistence.jpa.integration.changetracking;
+
+import javax.annotation.Priority;
+import javax.inject.Named;
+
+import org.springframework.beans.factory.annotation.Qualifier;
+import org.springframework.stereotype.Service;
+
+import org.apache.isis.applib.annotation.PriorityPrecedence;
+import org.apache.isis.applib.services.metrics.MetricsService;
+
+/**
+ * @since 2.0 {@index}
+ */
+@Service
+@Named("isis.transaction.PersistenceMetricsServiceJpa")
+@Priority(PriorityPrecedence.EARLY)
+@Qualifier("jpa")
+//@Log4j2
+public class PersistenceMetricsServiceJpa
+implements
+    MetricsService {
+
+    // -- METRICS
+
+    @Override
+    public int numberEntitiesLoaded() {
+        return -1; // n/a
+    }
+
+    @Override
+    public int numberEntitiesDirtied() {
+        return -1; // n/a
+    }
+
+}
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 50aa07f..e567076 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
@@ -129,7 +129,7 @@ extends PublishingTestAbstract {
             switch(changeScenario) {
             case ENTITY_CREATION:
 
-                //assertHasCreatedLifecycleEvents(bookSample1); //FIXME creation events not triggered
+                assertHasCreatedLifecycleEvents(bookSample1);
                 assertHasLoadedLifecycleEvents(Can.empty());
                 assertHasPersistingLifecycleEvents(Can.empty());
                 assertHasPersistedLifecycleEvents(Can.empty());
diff --git a/regressiontests/stable/src/main/java/org/apache/isis/testdomain/util/interaction/InteractionTestAbstract.java b/regressiontests/stable/src/main/java/org/apache/isis/testdomain/util/interaction/InteractionTestAbstract.java
index 1d5f387..5d77425 100644
--- a/regressiontests/stable/src/main/java/org/apache/isis/testdomain/util/interaction/InteractionTestAbstract.java
+++ b/regressiontests/stable/src/main/java/org/apache/isis/testdomain/util/interaction/InteractionTestAbstract.java
@@ -18,10 +18,6 @@
  */
 package org.apache.isis.testdomain.util.interaction;
 
-import static org.junit.jupiter.api.Assertions.assertEquals;
-import static org.junit.jupiter.api.Assertions.assertFalse;
-import static org.junit.jupiter.api.Assertions.assertTrue;
-
 import java.util.Collections;
 import java.util.List;
 import java.util.TreeSet;
@@ -32,17 +28,20 @@ import java.util.stream.Collectors;
 import javax.annotation.Nullable;
 import javax.inject.Inject;
 
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
 import org.apache.isis.applib.annotation.Where;
+import org.apache.isis.applib.services.iactnlayer.InteractionService;
 import org.apache.isis.applib.services.wrapper.WrapperFactory;
 import org.apache.isis.commons.internal.base._NullSafe;
 import org.apache.isis.commons.internal.base._Strings;
 import org.apache.isis.commons.internal.collections._Sets;
-import org.apache.isis.applib.services.iactnlayer.InteractionService;
 import org.apache.isis.core.metamodel.interactions.managed.ActionInteraction;
 import org.apache.isis.core.metamodel.interactions.managed.CollectionInteraction;
 import org.apache.isis.core.metamodel.interactions.managed.PropertyInteraction;
 import org.apache.isis.core.metamodel.objectmanager.ObjectManager;
-import org.apache.isis.core.transaction.changetracking.EntityChangeTracker;
 import org.apache.isis.testdomain.publishing.subscriber.EntityPropertyChangeSubscriberForTesting;
 import org.apache.isis.testdomain.util.CollectionAssertions;
 import org.apache.isis.testdomain.util.kv.KVStoreForTesting;
@@ -56,27 +55,22 @@ public abstract class InteractionTestAbstract extends IsisIntegrationTestAbstrac
     @Inject protected InteractionService interactionService;
     @Inject protected WrapperFactory wrapper;
     @Inject protected KVStoreForTesting kvStoreForTesting;
-    @Inject private javax.inject.Provider<EntityChangeTracker> entityChangeTrackerProvider;
-
-    protected EntityChangeTracker getEntityChangeTracker() {
-        return entityChangeTrackerProvider.get();
-    }
 
     // -- INTERACTION STARTERS
 
-    protected ActionInteraction startActionInteractionOn(Class<?> type, String actionId, Where where) {
+    protected ActionInteraction startActionInteractionOn(final Class<?> type, final String actionId, final Where where) {
         val viewModel = factoryService.viewModel(type);
         val managedObject = objectManager.adapt(viewModel);
         return ActionInteraction.start(managedObject, actionId, where);
     }
 
-    protected PropertyInteraction startPropertyInteractionOn(Class<?> type, String propertyId, Where where) {
+    protected PropertyInteraction startPropertyInteractionOn(final Class<?> type, final String propertyId, final Where where) {
         val viewModel = factoryService.viewModel(type);
         val managedObject = objectManager.adapt(viewModel);
         return PropertyInteraction.start(managedObject, propertyId, where);
     }
 
-    protected CollectionInteraction startCollectionInteractionOn(Class<?> type, String collectionId, Where where) {
+    protected CollectionInteraction startCollectionInteractionOn(final Class<?> type, final String collectionId, final Where where) {
         val viewModel = factoryService.viewModel(type);
         val managedObject = objectManager.adapt(viewModel);
         return CollectionInteraction.start(managedObject, collectionId, where);
@@ -84,7 +78,7 @@ public abstract class InteractionTestAbstract extends IsisIntegrationTestAbstrac
 
     // -- SHORTCUTS
 
-    protected Object invokeAction(Class<?> type, String actionId, @Nullable List<Object> pojoArgList) {
+    protected Object invokeAction(final Class<?> type, final String actionId, @Nullable final List<Object> pojoArgList) {
         val managedAction = startActionInteractionOn(type, actionId, Where.OBJECT_FORMS)
                 .getManagedAction().get(); // should not throw
 
@@ -111,15 +105,15 @@ public abstract class InteractionTestAbstract extends IsisIntegrationTestAbstrac
         assertEquals(Collections.<String>emptyList(), specLoader.getOrAssessValidationResult().getMessages());
     }
 
-    protected void assertComponentWiseEquals(Object a, Object b) {
+    protected void assertComponentWiseEquals(final Object a, final Object b) {
         CollectionAssertions.assertComponentWiseEquals(a, b);
     }
 
-    protected void assertComponentWiseUnwrappedEquals(Object a, Object b) {
+    protected void assertComponentWiseUnwrappedEquals(final Object a, final Object b) {
         CollectionAssertions.assertComponentWiseUnwrappedEquals(a, b);
     }
 
-    protected void assertEmpty(Object x) {
+    protected void assertEmpty(final Object x) {
         if(x instanceof CharSequence) {
             assertTrue(_Strings.isEmpty((CharSequence)x));
             return;
@@ -127,14 +121,14 @@ public abstract class InteractionTestAbstract extends IsisIntegrationTestAbstrac
         assertEquals(0L, _NullSafe.streamAutodetect(x).count());
     }
 
-    protected void assertDoesIncrement(Supplier<LongAdder> adder, Runnable runnable) {
+    protected void assertDoesIncrement(final Supplier<LongAdder> adder, final Runnable runnable) {
         final int eventCount0 = adder.get().intValue();
         runnable.run();
         final int eventCount1 = adder.get().intValue();
         assertEquals(eventCount0 + 1, eventCount1);
     }
 
-    protected void assertDoesNotIncrement(Supplier<LongAdder> adder, Runnable runnable) {
+    protected void assertDoesNotIncrement(final Supplier<LongAdder> adder, final Runnable runnable) {
         final int eventCount0 = adder.get().intValue();
         runnable.run();
         final int eventCount1 = adder.get().intValue();
@@ -143,21 +137,21 @@ public abstract class InteractionTestAbstract extends IsisIntegrationTestAbstrac
 
     // -- ASSERTIONS (INTERACTIONAL)
 
-    protected void assertInteractional(Runnable runnable) {
+    protected void assertInteractional(final Runnable runnable) {
         InteractionBoundaryProbe.assertInteractional(kvStoreForTesting, runnable);
     }
 
-    protected <T> T assertInteractional(Supplier<T> supplier) {
+    protected <T> T assertInteractional(final Supplier<T> supplier) {
         return InteractionBoundaryProbe.assertInteractional(kvStoreForTesting, supplier);
     }
 
     // -- ASSERTIONS (TRANSACTIONAL)
 
-    protected void assertTransactional(Runnable runnable) {
+    protected void assertTransactional(final Runnable runnable) {
         InteractionBoundaryProbe.assertTransactional(kvStoreForTesting, runnable);
     }
 
-    protected <T> T assertTransactional(Supplier<T> supplier) {
+    protected <T> T assertTransactional(final Supplier<T> supplier) {
         return InteractionBoundaryProbe.assertTransactional(kvStoreForTesting, supplier);
     }