You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@isis.apache.org by da...@apache.org on 2022/08/04 09:31:00 UTC

[isis] branch ISIS-3110 updated (697c95938b -> 8464d7a2bc)

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

danhaywood pushed a change to branch ISIS-3110
in repository https://gitbox.apache.org/repos/asf/isis.git


 discard 697c95938b ISIS-3110: removes circular dependency
 discard 6c12b16990 ISIS-3110: removes circular dependency
 discard d3ce224d00 ISIS-3110: moves the subscriber for EntityTrackerChangeDefault...
 discard dfc8df1719 ISIS-3110: fixes up integ test issues
 discard 6e5e265f78 ISIS-3110: fixes up compile issues resulting from deletion of unneeded superclass
 discard d4b2a23e3f ISIS-3110: cleans up import
 discard 9fca7a37cb ISIS-3110: fixes integ test
 discard e178e6a927 ISIS-3110: fixes JDO aud trail integ test
 discard 9fd67f45e1 ISIS-3110: reworks JPA and JDO auditing (EntityPropertyChange)
 discard 96d1c4f854 ISIS-3110: factors out persistence-commons
 discard f427fbea15 ISIS-3110: introduces EntityChangeTrackerJpa, mirroring JDO impl, but leveraging the PropertyChangeRecords already provided to us
 discard 55e8afb76d ISIS-2965: small improvements to build.sh
     add 3a908d64c7 ISIS-3064: schema rename fixes ("isisExtSecman")
     add 339e3dbd1c ISIS-3109: adds ZonedDateTime demo showcasing @ValueSemantics(timeZoneTranslation = TimeZoneTranslation.NONE)
     add cfe91482d1 ISIS-3109: some visual improvements with time-zone badge rendering
     add 640d44e4b7 ISIS-3109: adds Offset(Date)Time demo showcasing
     add f317ab9a15 ISIS-3109: introduces general purpose KeyValueSessionStore
     add b9f689441e ISIS-3109: store user's ZoneId from Login to session
     add 825fda9b59 ISIS-3109: surface the user's current time-zone in the UI via ApplicationUser_timeZone (new mixin)
     add 6235c4b272 Bump error_prone_annotations from 2.14.0 to 2.15.0
     add f07ba7159d Merge pull request #1043 from apache/dependabot/maven/master/com.google.errorprone-error_prone_annotations-2.15.0
     add e14d9be750 Bump javaparser-core from 3.24.2 to 3.24.4
     add c964df41e6 Merge pull request #1041 from apache/dependabot/maven/master/com.github.javaparser-javaparser-core-3.24.4
     add d8492335d6 Bump checker-qual from 3.23.0 to 3.24.0
     add dcfb61fb8e Merge pull request #1042 from apache/dependabot/maven/master/org.checkerframework-checker-qual-3.24.0
     add 590d59d4fb ISIS-3109: prepare InteractionContext to be amended with time-zone info
     add 1275167f00 ISIS-3109: properly amend authentication with zone-it post sign-in
     new a0c90c4761 ISIS-2965: small improvements to build.sh
     new 1071e5ef49 ISIS-3110: introduces EntityChangeTrackerJpa, mirroring JDO impl, but leveraging the PropertyChangeRecords already provided to us
     new 15d4901117 ISIS-3110: factors out persistence-commons
     new 9d51d073c8 ISIS-3110: reworks JPA and JDO auditing (EntityPropertyChange)
     new 8a48540269 ISIS-3110: fixes JDO aud trail integ test
     new 776c7465b1 ISIS-3110: fixes integ test
     new 8aaca881e3 ISIS-3110: cleans up import
     new 62aebee91b ISIS-3110: fixes up compile issues resulting from deletion of unneeded superclass
     new be76012fba ISIS-3110: fixes up integ test issues
     new 5ed3321d04 ISIS-3110: moves the subscriber for EntityTrackerChangeDefault...
     new 872adf79da ISIS-3110: removes circular dependency
     new 8464d7a2bc ISIS-3110: removes circular dependency

This update added new revisions after undoing existing revisions.
That is to say, some revisions that were in the old version of the
branch are not in the new version.  This situation occurs
when a user --force pushes a change and generates a repository
containing something like this:

 * -- * -- B -- O -- O -- O   (697c95938b)
            \
             N -- N -- N   refs/heads/ISIS-3110 (8464d7a2bc)

You should already have received notification emails for all of the O
revisions, and so the following emails describe only the N revisions
from the common base, B.

Any revisions marked "omit" are not gone; other references still
refer to them.  Any revisions marked "discard" are gone forever.

The 12 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails.  The revisions
listed as "add" were already present in the repository and have only
been added to this reference.


Summary of changes:
 .../services/iactnlayer/InteractionContext.java    |  9 ++-
 .../keyvaluestore/KeyValueSessionStore.java        | 82 ++++++++++++++++++++++
 .../services/user/ImpersonatedUserHolder.java      |  2 +-
 ....java => UserCurrentSessionTimeZoneHolder.java} | 44 ++++++------
 bom/pom.xml                                        |  2 +-
 .../isis/commons/internal/base/_Temporals.java     |  8 +++
 .../internal/debug/xray/_CallStackMerger.java      |  2 -
 .../core/interaction/session/IsisInteraction.java  | 13 ++--
 .../valuesemantics/temporal/BadgeRenderer.java     | 11 +--
 .../temporal/TemporalValueSemanticsProvider.java   |  6 +-
 .../IsisModuleCoreRuntimeServices.java             |  2 +
 .../session/InteractionServiceDefault.java         |  2 +-
 .../user/ImpersonatedUserHolderDefault.java        | 51 +++++---------
 .../UserCurrentSessionTimeZoneHolderDefault.java   | 65 +++++++++++++++++
 .../AuthenticationRequestAbstract.java             |  7 +-
 .../manager/AuthenticationManager.java             | 13 +++-
 .../isis/security/AuthenticatorsForTesting.java    | 10 +--
 .../AuthenticationManager_authenticators_Test.java |  3 +
 ...rdAuthenticationManager_AuthenticationTest.java |  2 +
 .../isis/core/webapp/IsisModuleCoreWebapp.java     |  6 +-
 .../KeyValueStoreUsingHttpSession.java}            | 47 +++++++------
 ...er3.java => JavaTimeOffsetDateTimeHolder4.java} | 37 ++++------
 .../jdo/JavaTimeOffsetDateTimeJdo.java             |  5 ++
 .../jpa/JavaTimeOffsetDateTimeJpa.java             |  5 ++
 .../persistence/JavaTimeOffsetDateTimeEntity.java  |  4 +-
 .../JavaTimeOffsetDateTimeEntity.layout.xml        |  1 +
 .../vm/JavaTimeOffsetDateTimeVm.java               |  8 ++-
 .../vm/JavaTimeOffsetDateTimeVm.layout.xml         |  1 +
 ...Holder3.java => JavaTimeOffsetTimeHolder4.java} | 37 ++++------
 .../jdo/JavaTimeOffsetTimeJdo.java                 |  5 ++
 .../jpa/JavaTimeOffsetTimeJpa.java                 |  5 ++
 .../persistence/JavaTimeOffsetTimeEntity.java      |  4 +-
 .../JavaTimeOffsetTimeEntity.layout.xml            |  1 +
 .../vm/JavaTimeOffsetTimeVm.java                   | 10 ++-
 .../vm/JavaTimeOffsetTimeVm.layout.xml             |  1 +
 ...der3.java => JavaTimeZonedDateTimeHolder4.java} | 37 ++++------
 .../jdo/JavaTimeZonedDateTimeJdo.java              |  7 ++
 .../jpa/JavaTimeZonedDateTimeJpa.java              |  7 ++
 .../persistence/JavaTimeZonedDateTimeEntity.java   |  4 +-
 .../JavaTimeZonedDateTimeEntity.layout.xml         |  1 +
 .../vm/JavaTimeZonedDateTimeVm.java                | 12 +++-
 .../vm/JavaTimeZonedDateTimeVm.layout.xml          |  1 +
 .../demo/domain/src/main/resources/application.yml |  2 +-
 .../adoc/modules/secman/pages/setting-up.adoc      |  2 +-
 .../secman/applib/IsisModuleExtSecmanApplib.java   |  2 +
 .../secman/applib/user/dom/ApplicationUser.java    | 19 ++---
 .../user/dom/mixins/ApplicationUser_timeZone.java  | 58 +++++++--------
 .../authenticator/AuthenticatorSecman.java         |  2 -
 .../AuthenticatorSecmanAutoConfiguration.java      |  3 +-
 .../main/resources/META-INF/orm-secman.template    | 10 +--
 .../spiimpl/SessionSubscriberForSessionLog.java    |  3 +-
 .../jdo/adoc/modules/ROOT/pages/db-schemas.adoc    |  4 +-
 .../jpa/adoc/modules/ROOT/pages/db-schemas.adoc    |  4 +-
 .../shiro/authentication/AuthenticatorShiro.java   | 13 ++--
 .../spring/webmodule/SpringSecurityFilter.java     |  5 +-
 tooling/javamodel/pom.xml                          |  2 +-
 tooling/pom.xml                                    |  2 +-
 .../model/isis/HasAmendableInteractionContext.java | 19 ++---
 .../wicket/ui/pages/login/IsisSignInPanel.java     | 73 ++++++++++++++-----
 .../wicket/ui/pages/login/SignInPanelAbstract.java | 22 +++++-
 .../AuthenticatedWebSessionForIsis.java            | 28 +++++---
 ...uthenticatedWebSessionForIsis_Authenticate.java |  1 +
 .../AuthenticatedWebSessionForIsis_SignIn.java     |  2 +
 63 files changed, 557 insertions(+), 299 deletions(-)
 create mode 100644 api/applib/src/main/java/org/apache/isis/applib/services/keyvaluestore/KeyValueSessionStore.java
 copy api/applib/src/main/java/org/apache/isis/applib/services/user/{ImpersonatedUserHolder.java => UserCurrentSessionTimeZoneHolder.java} (51%)
 create mode 100644 core/runtimeservices/src/main/java/org/apache/isis/core/runtimeservices/user/UserCurrentSessionTimeZoneHolderDefault.java
 rename core/webapp/src/main/java/org/apache/isis/core/webapp/{impersonation/ImpersonatedUserHolderUsingHttpSession.java => keyvaluestore/KeyValueStoreUsingHttpSession.java} (61%)
 copy examples/demo/domain/src/main/java/demoapp/dom/types/javatime/javatimeoffsetdatetime/holder/{JavaTimeOffsetDateTimeHolder3.java => JavaTimeOffsetDateTimeHolder4.java} (55%)
 copy examples/demo/domain/src/main/java/demoapp/dom/types/javatime/javatimeoffsettime/holder/{JavaTimeOffsetTimeHolder3.java => JavaTimeOffsetTimeHolder4.java} (56%)
 copy examples/demo/domain/src/main/java/demoapp/dom/types/javatime/javatimezoneddatetime/holder/{JavaTimeZonedDateTimeHolder3.java => JavaTimeZonedDateTimeHolder4.java} (55%)
 copy regressiontests/stable/src/main/java/org/apache/isis/testdomain/model/good/ProperMemberSupport_property1.java => extensions/security/secman/applib/src/main/java/org/apache/isis/extensions/secman/applib/user/dom/mixins/ApplicationUser_timeZone.java (51%)
 copy api/applib/src/main/java/org/apache/isis/applib/services/sudo/SudoServiceListener.java => viewers/wicket/model/src/main/java/org/apache/isis/viewer/wicket/model/isis/HasAmendableInteractionContext.java (63%)


[isis] 08/12: ISIS-3110: fixes up compile issues resulting from deletion of unneeded superclass

Posted by da...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

danhaywood pushed a commit to branch ISIS-3110
in repository https://gitbox.apache.org/repos/asf/isis.git

commit 62aebee91b34e10fd47acd2fef674882b881d219
Author: Dan Haywood <da...@haywood-associates.co.uk>
AuthorDate: Thu Aug 4 07:04:37 2022 +0100

    ISIS-3110: fixes up compile issues resulting from deletion of unneeded superclass
---
 .../publish/LifecycleCallbackNotifier.java         | 120 +++++++++------------
 .../PersistenceCallbackHandlerAbstract.java        |  61 -----------
 .../changetracking/EntityChangeTrackerDefault.java |  22 +---
 .../DomainModelTest_usingBadDomain.java            |   8 +-
 4 files changed, 63 insertions(+), 148 deletions(-)

diff --git a/core/runtimeservices/src/main/java/org/apache/isis/core/runtimeservices/publish/LifecycleCallbackNotifier.java b/core/runtimeservices/src/main/java/org/apache/isis/core/runtimeservices/publish/LifecycleCallbackNotifier.java
index d6a941c67e..fbd6d448a8 100644
--- a/core/runtimeservices/src/main/java/org/apache/isis/core/runtimeservices/publish/LifecycleCallbackNotifier.java
+++ b/core/runtimeservices/src/main/java/org/apache/isis/core/runtimeservices/publish/LifecycleCallbackNotifier.java
@@ -32,11 +32,15 @@ import org.springframework.stereotype.Component;
 
 import org.apache.isis.applib.annotation.InteractionScope;
 import org.apache.isis.applib.annotation.PriorityPrecedence;
+import org.apache.isis.applib.events.lifecycle.AbstractLifecycleEvent;
 import org.apache.isis.applib.services.bookmark.Bookmark;
 import org.apache.isis.applib.services.eventbus.EventBusService;
+import org.apache.isis.applib.services.iactnlayer.InteractionService;
+import org.apache.isis.commons.internal.factory._InstanceUtil;
 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.facets.object.callbacks.LoadedCallbackFacet;
 import org.apache.isis.core.metamodel.facets.object.callbacks.LoadedLifecycleEventFacet;
 import org.apache.isis.core.metamodel.facets.object.callbacks.PersistedCallbackFacet;
@@ -51,10 +55,12 @@ 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.spec.ManagedObject;
 import org.apache.isis.core.runtimeservices.IsisModuleCoreRuntimeServices;
-import org.apache.isis.core.transaction.changetracking.PersistenceCallbackHandlerAbstract;
 import org.apache.isis.core.transaction.changetracking.events.PostStoreEvent;
 import org.apache.isis.core.transaction.changetracking.events.PreStoreEvent;
 
+import lombok.RequiredArgsConstructor;
+import lombok.val;
+
 /**
  * Calls lifecycle callbacks for entities, ensuring that any given entity is only ever called once.
  * @since 2.0 {@index}
@@ -62,97 +68,75 @@ import org.apache.isis.core.transaction.changetracking.events.PreStoreEvent;
 @Component
 @Named(IsisModuleCoreRuntimeServices.NAMESPACE + ".LifecycleCallbackNotifier")
 @Priority(PriorityPrecedence.EARLY)
+@RequiredArgsConstructor(onConstructor_ = {@Inject})
 @Qualifier("Default")
-@InteractionScope
 //@Log4j2
-public class LifecycleCallbackNotifier extends PersistenceCallbackHandlerAbstract {
-
-    private final Set<ManagedObject> postCreated = new LinkedHashSet<>();
-    private final Set<ManagedObject> postLoaded = new LinkedHashSet<>();
-    private final Set<ManagedObject> prePersisted = new LinkedHashSet<>();
-    private final Set<ManagedObject> postPersisted = new LinkedHashSet<>();
-    private final Set<ManagedObject> preUpdated = new LinkedHashSet<>();
-    private final Set<ManagedObject> postUpdated = new LinkedHashSet<>();
-    private final Set<ManagedObject> preRemoved = new LinkedHashSet<>();
-
-    @Inject
-    public LifecycleCallbackNotifier(EventBusService eventBusService) {
-        super(eventBusService);
-    }
+public class LifecycleCallbackNotifier {
+
+    final EventBusService eventBusService;
 
     public void postCreate(ManagedObject entity) {
-        notify(entity,
-                postCreated,
-                e -> {
-                    CallbackFacet.callCallback(entity, CreatedCallbackFacet.class);
-                    postLifecycleEventIfRequired(entity, CreatedLifecycleEventFacet.class);
-                });
+        CallbackFacet.callCallback(entity, CreatedCallbackFacet.class);
+        postLifecycleEventIfRequired(entity, CreatedLifecycleEventFacet.class);
     }
 
     public void postLoad(ManagedObject entity) {
-        notify(entity,
-                postLoaded,
-                e -> {
-                    CallbackFacet.callCallback(entity, LoadedCallbackFacet.class);
-                    postLifecycleEventIfRequired(entity, LoadedLifecycleEventFacet.class);
-                });
+        CallbackFacet.callCallback(entity, LoadedCallbackFacet.class);
+        postLifecycleEventIfRequired(entity, LoadedLifecycleEventFacet.class);
     }
 
     public void prePersist(ManagedObject entity) {
-        notify(entity,
-                prePersisted,
-                e -> {
-                    eventBusService.post(PreStoreEvent.of(entity.getPojo()));
-                    CallbackFacet.callCallback(entity, PersistingCallbackFacet.class);
-                    postLifecycleEventIfRequired(entity, PersistingLifecycleEventFacet.class);
-                });
+        eventBusService.post(PreStoreEvent.of(entity.getPojo()));
+        CallbackFacet.callCallback(entity, PersistingCallbackFacet.class);
+        postLifecycleEventIfRequired(entity, PersistingLifecycleEventFacet.class);
     }
 
     public void postPersist(ManagedObject entity) {
-        notify(entity,
-                postPersisted,
-                e -> {
-                    eventBusService.post(PostStoreEvent.of(entity.getPojo()));
-                    CallbackFacet.callCallback(entity, PersistedCallbackFacet.class);
-                    postLifecycleEventIfRequired(entity, PersistedLifecycleEventFacet.class);
-                });
+        eventBusService.post(PostStoreEvent.of(entity.getPojo()));
+        CallbackFacet.callCallback(entity, PersistedCallbackFacet.class);
+        postLifecycleEventIfRequired(entity, PersistedLifecycleEventFacet.class);
     }
 
     public void preUpdate(ManagedObject entity) {
-        notify(entity,
-                preUpdated,
-                e -> {
-                    eventBusService.post(PreStoreEvent.of(entity.getPojo()));
-                    CallbackFacet.callCallback(entity, UpdatingCallbackFacet.class);
-                    postLifecycleEventIfRequired(entity, UpdatingLifecycleEventFacet.class);
-                });
+        eventBusService.post(PreStoreEvent.of(entity.getPojo()));
+        CallbackFacet.callCallback(entity, UpdatingCallbackFacet.class);
+        postLifecycleEventIfRequired(entity, UpdatingLifecycleEventFacet.class);
     }
 
     public void postUpdate(ManagedObject entity) {
-        notify(entity,
-                postUpdated,
-                e -> {
-                    CallbackFacet.callCallback(entity, UpdatedCallbackFacet.class);
-                    postLifecycleEventIfRequired(entity, UpdatedLifecycleEventFacet.class);
-                });
+        CallbackFacet.callCallback(entity, UpdatedCallbackFacet.class);
+        postLifecycleEventIfRequired(entity, UpdatedLifecycleEventFacet.class);
     }
 
     public void preRemove(ManagedObject entity) {
-        notify(entity,
-                preRemoved,
-                e -> {
-                    CallbackFacet.callCallback(entity, RemovingCallbackFacet.class);
-                    postLifecycleEventIfRequired(entity, RemovingLifecycleEventFacet.class);
-                });
+        CallbackFacet.callCallback(entity, RemovingCallbackFacet.class);
+        postLifecycleEventIfRequired(entity, RemovingLifecycleEventFacet.class);
+    }
+
+
+    //  -- HELPER
+
+    @SuppressWarnings({ "unchecked", "rawtypes" })
+    protected void postLifecycleEventIfRequired(
+            final ManagedObject adapter,
+            final Class<? extends LifecycleEventFacet> lifecycleEventFacetClass) {
+
+        val lifecycleEventFacet = adapter.getSpecification().getFacet(lifecycleEventFacetClass);
+        if(lifecycleEventFacet == null) {
+            return;
+        }
+        val eventInstance = (AbstractLifecycleEvent) _InstanceUtil
+                .createInstance(lifecycleEventFacet.getEventType());
+        val pojo = adapter.getPojo();
+        postEvent(eventInstance, pojo);
+
     }
 
-    private static void notify(ManagedObject entity, Set<ManagedObject> notified, Consumer<ManagedObject> notify) {
-        Optional.of(entity)
-                .filter(x -> !notified.contains(x))
-                .ifPresent(x -> {
-                    notify.accept(entity);
-                    notified.add(x);
-                });
+    protected void postEvent(final AbstractLifecycleEvent<Object> event, final Object pojo) {
+        if(eventBusService!=null) {
+            event.initSource(pojo);
+            eventBusService.post(event);
+        }
     }
 
 }
diff --git a/core/transaction/src/main/java/org/apache/isis/core/transaction/changetracking/PersistenceCallbackHandlerAbstract.java b/core/transaction/src/main/java/org/apache/isis/core/transaction/changetracking/PersistenceCallbackHandlerAbstract.java
deleted file mode 100644
index 88f17fcaa5..0000000000
--- a/core/transaction/src/main/java/org/apache/isis/core/transaction/changetracking/PersistenceCallbackHandlerAbstract.java
+++ /dev/null
@@ -1,61 +0,0 @@
-/*
- *  Licensed to the Apache Software Foundation (ASF) under one
- *  or more contributor license agreements.  See the NOTICE file
- *  distributed with this work for additional information
- *  regarding copyright ownership.  The ASF licenses this file
- *  to you under the Apache License, Version 2.0 (the
- *  "License"); you may not use this file except in compliance
- *  with the License.  You may obtain a copy of the License at
- *
- *        http://www.apache.org/licenses/LICENSE-2.0
- *
- *  Unless required by applicable law or agreed to in writing,
- *  software distributed under the License is distributed on an
- *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- *  KIND, either express or implied.  See the License for the
- *  specific language governing permissions and limitations
- *  under the License.
- */
-package org.apache.isis.core.transaction.changetracking;
-
-import org.apache.isis.applib.events.lifecycle.AbstractLifecycleEvent;
-import org.apache.isis.applib.services.eventbus.EventBusService;
-import org.apache.isis.commons.internal.factory._InstanceUtil;
-import org.apache.isis.core.metamodel.facets.object.callbacks.LifecycleEventFacet;
-import org.apache.isis.core.metamodel.spec.ManagedObject;
-
-import lombok.AccessLevel;
-import lombok.RequiredArgsConstructor;
-import lombok.val;
-
-@RequiredArgsConstructor(access = AccessLevel.PROTECTED)
-public abstract class PersistenceCallbackHandlerAbstract {
-
-    protected final EventBusService eventBusService;
-
-    //  -- HELPER
-
-    @SuppressWarnings({ "unchecked", "rawtypes" })
-    protected void postLifecycleEventIfRequired(
-            final ManagedObject adapter,
-            final Class<? extends LifecycleEventFacet> lifecycleEventFacetClass) {
-
-        val lifecycleEventFacet = adapter.getSpecification().getFacet(lifecycleEventFacetClass);
-        if(lifecycleEventFacet == null) {
-            return;
-        }
-        val eventInstance = (AbstractLifecycleEvent) _InstanceUtil
-                .createInstance(lifecycleEventFacet.getEventType());
-        val pojo = adapter.getPojo();
-        postEvent(eventInstance, pojo);
-
-    }
-
-    protected void postEvent(final AbstractLifecycleEvent<Object> event, final Object pojo) {
-        if(eventBusService!=null) {
-            event.initSource(pojo);
-            eventBusService.post(event);
-        }
-    }
-
-}
diff --git a/persistence/commons/src/main/java/org/apache/isis/persistence/jpa/integration/changetracking/EntityChangeTrackerDefault.java b/persistence/commons/src/main/java/org/apache/isis/persistence/jpa/integration/changetracking/EntityChangeTrackerDefault.java
index dbf8a49d21..8c9450a0c8 100644
--- a/persistence/commons/src/main/java/org/apache/isis/persistence/jpa/integration/changetracking/EntityChangeTrackerDefault.java
+++ b/persistence/commons/src/main/java/org/apache/isis/persistence/jpa/integration/changetracking/EntityChangeTrackerDefault.java
@@ -65,12 +65,12 @@ 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.PersistenceCallbackHandlerAbstract;
 import org.apache.isis.core.transaction.events.TransactionBeforeCompletionEvent;
 
 import lombok.AccessLevel;
 import lombok.Getter;
 import lombok.NonNull;
+import lombok.RequiredArgsConstructor;
 import lombok.val;
 import lombok.extern.log4j.Log4j2;
 
@@ -82,9 +82,9 @@ import lombok.extern.log4j.Log4j2;
 @Priority(PriorityPrecedence.EARLY)
 @Qualifier("default")
 @InteractionScope
+@RequiredArgsConstructor(onConstructor_ = {@Inject})
 @Log4j2
 public class EntityChangeTrackerDefault
-extends PersistenceCallbackHandlerAbstract
 implements
     MetricsService,
     EntityChangeTracker,
@@ -108,25 +108,13 @@ implements
     @Getter(AccessLevel.PACKAGE)
     private final Map<Bookmark, EntityChangeKind> changeKindByEnlistedAdapter = _Maps.newLinkedHashMap();
 
-    private final EntityPropertyChangePublisher entityPropertyChangePublisher;
-    private final EntityChangesPublisher entityChangesPublisher;
-    private final Provider<InteractionProvider> interactionProviderProvider;
-
     private final LongAdder numberEntitiesLoaded = new LongAdder();
     private final LongAdder entityChangeEventCount = new LongAdder();
     private final AtomicBoolean persistentChangesEncountered = new AtomicBoolean();
 
-    @Inject
-    public EntityChangeTrackerDefault(
-            final EntityPropertyChangePublisher entityPropertyChangePublisher,
-            final EntityChangesPublisher entityChangesPublisher,
-            final EventBusService eventBusService,
-            final Provider<InteractionProvider> interactionProviderProvider) {
-        super(eventBusService);
-        this.entityPropertyChangePublisher = entityPropertyChangePublisher;
-        this.entityChangesPublisher = entityChangesPublisher;
-        this.interactionProviderProvider = interactionProviderProvider;
-    }
+    private final EntityPropertyChangePublisher entityPropertyChangePublisher;
+    private final EntityChangesPublisher entityChangesPublisher;
+    private final Provider<InteractionProvider> interactionProviderProvider;
 
     Set<PropertyChangeRecord> snapshotPropertyChangeRecords() {
         // this code path has side-effects, it locks the result for this transaction,
diff --git a/regressiontests/stable-domainmodel/src/test/java/org/apache/isis/testdomain/domainmodel/DomainModelTest_usingBadDomain.java b/regressiontests/stable-domainmodel/src/test/java/org/apache/isis/testdomain/domainmodel/DomainModelTest_usingBadDomain.java
index 5486678a75..09e698a575 100644
--- a/regressiontests/stable-domainmodel/src/test/java/org/apache/isis/testdomain/domainmodel/DomainModelTest_usingBadDomain.java
+++ b/regressiontests/stable-domainmodel/src/test/java/org/apache/isis/testdomain/domainmodel/DomainModelTest_usingBadDomain.java
@@ -46,6 +46,7 @@ import org.apache.isis.applib.annotation.Property;
 import org.apache.isis.applib.annotation.PropertyLayout;
 import org.apache.isis.applib.exceptions.unrecoverable.DomainModelException;
 import org.apache.isis.applib.id.LogicalType;
+import org.apache.isis.applib.services.iactnlayer.InteractionService;
 import org.apache.isis.commons.collections.Can;
 import org.apache.isis.core.config.IsisConfiguration;
 import org.apache.isis.core.config.environment.IsisSystemEnvironment;
@@ -91,6 +92,7 @@ import lombok.val;
 class DomainModelTest_usingBadDomain {
 
     @Inject private IsisConfiguration configuration;
+    @Inject private InteractionService interactionService;
     @Inject private IsisSystemEnvironment isisSystemEnvironment;
     @Inject private SpecificationLoader specificationLoader;
     @Inject private DomainObjectTesterFactory testerFactory;
@@ -99,8 +101,10 @@ class DomainModelTest_usingBadDomain {
 
     @BeforeEach
     void setup() {
-        validator = new DomainModelValidator(specificationLoader, configuration, isisSystemEnvironment);
-        assertThrows(DomainModelException.class, validator::throwIfInvalid);
+        interactionService.runAnonymous(() -> {
+            validator = new DomainModelValidator(specificationLoader, configuration, isisSystemEnvironment);
+            assertThrows(DomainModelException.class, validator::throwIfInvalid);
+        });
     }
 
 


[isis] 01/12: ISIS-2965: small improvements to build.sh

Posted by da...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

danhaywood pushed a commit to branch ISIS-3110
in repository https://gitbox.apache.org/repos/asf/isis.git

commit a0c90c47610701394554a0264d24be9ea8d6c364
Author: Dan Haywood <da...@haywood-associates.co.uk>
AuthorDate: Wed Aug 3 09:34:39 2022 +0100

    ISIS-2965: small improvements to build.sh
---
 build.sh | 10 +++++++++-
 1 file changed, 9 insertions(+), 1 deletion(-)

diff --git a/build.sh b/build.sh
index e9d9ad3cea..dbdd67feea 100644
--- a/build.sh
+++ b/build.sh
@@ -50,6 +50,7 @@ usage() {
  echo "  -k use 'package' rather than 'install'.  Does not run integ tests.  Cannot combine with '-y'" >&2
  echo "  -y use 'verify' rather than 'install'.  Cannot combine with '-k'"                             >&2
  echo "  -O do NOT add '-o' (offline) flag, ie bring down any new dependencies"                        >&2
+ echo "  -I append '-Dmodule-all-except-incubator"                                                     >&2
  echo "  -F do NOT search for Failures and Errors at the end"                                          >&2
  echo "  -S do NOT print summary or last 50 lines at the end"                                          >&2
  echo "  -w whatif - don't run the command but do print it out.  Implies -v (verbose)"                 >&2
@@ -68,12 +69,13 @@ WHATIF=false
 SINGLE_THREADED=false
 SKIP_SEARCH_FOR_FAILURES=false
 SKIP_SUMMARY=false
+ALL_EXCEPT_INCUBATOR=false
 EDIT=false
 VERBOSE=false
 
 MVN_LOG=/tmp/$BASENAME_0.$$.log
 
-while getopts 'prctlkyOFSwveh' opt
+while getopts 'prctlkyIOFSwveh' opt
 do
   case $opt in
     p) export GIT_PULL=true ;;
@@ -83,6 +85,7 @@ do
     l) export SINGLE_THREADED=true ;;
     k) export PACKAGE_ONLY=true ;;
     y) export VERIFY_ONLY=true ;;
+    I) export ALL_EXCEPT_INCUBATOR=true ;;
     F) export SKIP_SEARCH_FOR_FAILURES=true ;;
     S) export SKIP_SUMMARY=true ;;
     w) export WHATIF=true ;;
@@ -110,6 +113,7 @@ if [ "$VERBOSE" = "true" ]; then
   echo "-k PACKAGE_ONLY             : $PACKAGE_ONLY"
   echo "-y VERIFY_ONLY              : $VERIFY_ONLY"
   echo "-O SKIP_OFFLINE             : $SKIP_OFFLINE"
+  echo "-I ALL_EXCEPT_INCUBATOR     : $ALL_EXCEPT_INCUBATOR"
   echo "-F SKIP_SEARCH_FOR_FAILURES : $SKIP_SEARCH_FOR_FAILURES"
   echo "-S SKIP_SUMMARY             : $SKIP_SUMMARY"
   echo "-w WHATIF                   : $WHATIF"
@@ -140,6 +144,10 @@ if [ "$TIMELINE" = "true" ]; then
   OPTS="$OPTS -Dmaven-timeline.version=1.8-SNAPSHOT"
 fi
 
+if [ "$ALL_EXCEPT_INCUBATOR" = "true" ]; then
+  OPTS="$OPTS -Dmodule-all-except-incubator"
+fi
+
 if [ "$SKIP_OFFLINE" = "false" ]; then
   OPTS="$OPTS -o"
 fi


[isis] 02/12: ISIS-3110: introduces EntityChangeTrackerJpa, mirroring JDO impl, but leveraging the PropertyChangeRecords already provided to us

Posted by da...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

danhaywood pushed a commit to branch ISIS-3110
in repository https://gitbox.apache.org/repos/asf/isis.git

commit 1071e5ef49548ce0e850019af35230d32e51a3c0
Author: Dan Haywood <da...@haywood-associates.co.uk>
AuthorDate: Wed Aug 3 13:15:20 2022 +0100

    ISIS-3110: introduces EntityChangeTrackerJpa, mirroring JDO impl, but leveraging the PropertyChangeRecords already provided to us
---
 .../objectlifecycle/ObjectLifecyclePublisher.java  | 118 +++++++----------
 .../publish/ObjectLifecyclePublisherDefault.java   |  27 ++--
 .../changetracking/EntityChangeTracker.java        |  11 +-
 .../integtests/AuditTrail_IntegTestAbstract.java   |   4 +-
 .../jpa/integtests/AuditTrail_IntegTest.java       |   5 -
 .../audittrail/jpa/integtests/model/Counter.java   |   6 +-
 .../changetracking/EntityChangeTrackerJdo.java     |  12 ++
 .../IsisModulePersistenceJpaIntegration.java       |   4 +-
 .../changetracking/EntityChangeTrackerJpa.java}    | 145 ++++++++++++++++-----
 .../PersistenceMetricsServiceJpa.java              |  54 --------
 .../changetracking/_ChangingEntitiesFactory.java   | 143 ++++++++++++++++++++
 .../changetracking/_SimpleChangingEntities.java    | 121 +++++++++++++++++
 .../jpa/integration/changetracking/_Xray.java      | 145 +++++++++++++++++++++
 .../bootstrap/css/bootstrap-overrides-all-v2.css   |   5 +
 14 files changed, 618 insertions(+), 182 deletions(-)

diff --git a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/services/objectlifecycle/ObjectLifecyclePublisher.java b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/services/objectlifecycle/ObjectLifecyclePublisher.java
index 2ef38b932f..8d7adce84f 100644
--- a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/services/objectlifecycle/ObjectLifecyclePublisher.java
+++ b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/services/objectlifecycle/ObjectLifecyclePublisher.java
@@ -69,100 +69,74 @@ public interface ObjectLifecyclePublisher {
     static HasEnlistedEntityPropertyChanges publishingPayloadForCreation(
             final @NonNull ManagedObject entity) {
 
-        return new HasEnlistedEntityPropertyChanges() {
+        return (timestamp, user, txId) -> entityPropertyChangesForCreation(timestamp, user, txId, entity);
+    }
 
-            @Override
-            public Can<EntityPropertyChange> getPropertyChanges(
-                    final Timestamp timestamp,
-                    final String user,
-                    final TransactionId txId) {
+    private static Can<EntityPropertyChange> entityPropertyChangesForCreation(Timestamp timestamp, String user, TransactionId txId, ManagedObject entity) {
+        return propertyChangeRecordsForCreation(entity).stream()
+                .map(pcr -> pcr.toEntityPropertyChange(timestamp, user, txId))
+                .collect(Can.toCan());
+    }
 
-                return entity
+    static Can<PropertyChangeRecord> propertyChangeRecordsForCreation(ManagedObject entity) {
+        return entity
                 .getSpecification()
                 .streamProperties(MixedIn.EXCLUDED)
-                .filter(property->EntityChangePublishingFacet.isPublishingEnabled(entity.getSpecification()))
-                .filter(property->!EntityPropertyChangePublishingPolicyFacet.isExcludedFromPublishing(property))
-                .map(property->
+                .filter(property -> EntityChangePublishingFacet.isPublishingEnabled(entity.getSpecification()))
+                .filter(property -> !EntityPropertyChangePublishingPolicyFacet.isExcludedFromPublishing(property))
+                .map(property ->
                         PropertyChangeRecord
-                        .of(
-                                entity,
-                                property,
-                                PreAndPostValue
-                                    .pre(PropertyValuePlaceholder.NEW)
-                                    .withPost(ManagedObjects.UnwrapUtil.single(property.get(entity, InteractionInitiatedBy.FRAMEWORK))))
-                        .toEntityPropertyChange(
-                                timestamp,
-                                user,
-                                txId)
-                )
+                                .of(
+                                        entity,
+                                        property,
+                                        PreAndPostValue
+                                                .pre(PropertyValuePlaceholder.NEW)
+                                                .withPost(ManagedObjects.UnwrapUtil.single(property.get(entity, InteractionInitiatedBy.FRAMEWORK)))))
                 .collect(Can.toCan());
-
-            }
-
-        };
-
     }
 
     static HasEnlistedEntityPropertyChanges publishingPayloadForDeletion(
             final @NonNull ManagedObject entity) {
 
-        return new HasEnlistedEntityPropertyChanges() {
+        return (timestamp, user, txId) -> entityPropertyChangesForDeletion(timestamp, user, txId, entity);
 
-            @Override
-            public Can<EntityPropertyChange> getPropertyChanges(
-                    final Timestamp timestamp,
-                    final String user,
-                    final TransactionId txId) {
+    }
 
-                return entity
+    private static Can<EntityPropertyChange> entityPropertyChangesForDeletion(Timestamp timestamp, String user, TransactionId txId, ManagedObject entity) {
+        return propertyChangeRecordsForDeletion(entity).stream()
+                .map(pcr -> pcr.toEntityPropertyChange(timestamp, user, txId))
+                .collect(Can.toCan());
+    }
+
+    static Can<PropertyChangeRecord> propertyChangeRecordsForDeletion(ManagedObject entity) {
+        return entity
                 .getSpecification()
                 .streamProperties(MixedIn.EXCLUDED)
-                .filter(property->EntityChangePublishingFacet.isPublishingEnabled(entity.getSpecification()))
-                .filter(property->!EntityPropertyChangePublishingPolicyFacet.isExcludedFromPublishing(property))
-                .map(property->
+                .filter(property -> EntityChangePublishingFacet.isPublishingEnabled(entity.getSpecification()))
+                .filter(property -> !EntityPropertyChangePublishingPolicyFacet.isExcludedFromPublishing(property))
+                .map(property ->
                         PropertyChangeRecord
-                        .of(
-                                entity,
-                                property,
-                                PreAndPostValue
-                                    .pre(ManagedObjects.UnwrapUtil.single(property.get(entity, InteractionInitiatedBy.FRAMEWORK)))
-                                    .withPost(PropertyValuePlaceholder.DELETED))
-                        .toEntityPropertyChange(
-                                timestamp,
-                                user,
-                                txId)
+                                .of(
+                                        entity,
+                                        property,
+                                        PreAndPostValue
+                                                .pre(ManagedObjects.UnwrapUtil.single(property.get(entity, InteractionInitiatedBy.FRAMEWORK)))
+                                                .withPost(PropertyValuePlaceholder.DELETED))
                 )
                 .collect(Can.toCan());
-
-            }
-
-        };
-
     }
 
-    static HasEnlistedEntityPropertyChanges publishingPayloadForUpdate(
-            final ManagedObject entity,
-            final Can<PropertyChangeRecord> changeRecords) {
-
-        return new HasEnlistedEntityPropertyChanges() {
-
-            @Override
-            public Can<EntityPropertyChange> getPropertyChanges(
-                    final Timestamp timestamp,
-                    final String user,
-                    final TransactionId txId) {
-
-                return changeRecords
-                .map(changeRecord->
-                    changeRecord
-                    .toEntityPropertyChange(
-                            timestamp,
-                            user,
-                            txId));
-            }
+    static HasEnlistedEntityPropertyChanges publishingPayloadForUpdate(final Can<PropertyChangeRecord> changeRecords) {
+        return (timestamp, user, txId) -> entityPropertyChangesForUpdate(timestamp, user, txId, changeRecords);
+    }
 
-        };
+    private static Can<EntityPropertyChange> entityPropertyChangesForUpdate(Timestamp timestamp, String user, TransactionId txId, Can<PropertyChangeRecord> changeRecords) {
+        return propertyChangeRecordsForUpdate(changeRecords)
+                .map(pcr -> pcr.toEntityPropertyChange(timestamp, user, txId));
+    }
 
+    static Can<PropertyChangeRecord> propertyChangeRecordsForUpdate(Can<PropertyChangeRecord> changeRecords) {
+        return changeRecords;
     }
 
 
diff --git a/core/runtimeservices/src/main/java/org/apache/isis/core/runtimeservices/publish/ObjectLifecyclePublisherDefault.java b/core/runtimeservices/src/main/java/org/apache/isis/core/runtimeservices/publish/ObjectLifecyclePublisherDefault.java
index f1ff4e6a07..cf3aa6f0f6 100644
--- a/core/runtimeservices/src/main/java/org/apache/isis/core/runtimeservices/publish/ObjectLifecyclePublisherDefault.java
+++ b/core/runtimeservices/src/main/java/org/apache/isis/core/runtimeservices/publish/ObjectLifecyclePublisherDefault.java
@@ -21,6 +21,7 @@ package org.apache.isis.core.runtimeservices.publish;
 import javax.annotation.Priority;
 import javax.inject.Inject;
 import javax.inject.Named;
+import javax.inject.Provider;
 
 import org.springframework.beans.factory.annotation.Qualifier;
 import org.springframework.stereotype.Service;
@@ -48,7 +49,7 @@ import org.apache.isis.core.metamodel.services.objectlifecycle.ObjectLifecyclePu
 import org.apache.isis.core.metamodel.services.objectlifecycle.PropertyChangeRecord;
 import org.apache.isis.core.metamodel.spec.ManagedObject;
 import org.apache.isis.core.runtimeservices.IsisModuleCoreRuntimeServices;
-import org.apache.isis.core.transaction.changetracking.EntityPropertyChangePublisher;
+import org.apache.isis.core.transaction.changetracking.EntityChangeTracker;
 import org.apache.isis.core.transaction.changetracking.PersistenceCallbackHandlerAbstract;
 
 /**
@@ -65,14 +66,18 @@ extends PersistenceCallbackHandlerAbstract
 implements
     ObjectLifecyclePublisher {
 
-    private final EntityPropertyChangePublisher entityPropertyChangePublisher;
+    private final Provider<EntityChangeTracker> entityChangeTrackerProvider;
 
     @Inject
     public ObjectLifecyclePublisherDefault(
             final EventBusService eventBusService,
-            final EntityPropertyChangePublisher entityPropertyChangePublisher) {
+            final Provider<EntityChangeTracker> entityChangeTrackerProvider) {
         super(eventBusService);
-        this.entityPropertyChangePublisher = entityPropertyChangePublisher;
+        this.entityChangeTrackerProvider = entityChangeTrackerProvider;
+    }
+
+    EntityChangeTracker entityChangeTracker() {
+        return entityChangeTrackerProvider.get();
     }
 
     @Override
@@ -100,9 +105,7 @@ implements
         postLifecycleEventIfRequired(entity, UpdatingLifecycleEventFacet.class);
 
         if(EntityChangePublishingFacet.isPublishingEnabled(entity.getSpecification())) {
-            entityPropertyChangePublisher.publishChangedProperties(
-                    ObjectLifecyclePublisher
-                    .publishingPayloadForUpdate(entity, changeRecords));
+            entityChangeTracker().enlistUpdating(entity, changeRecords);
         }
 
     }
@@ -113,9 +116,8 @@ implements
         postLifecycleEventIfRequired(entity, RemovingLifecycleEventFacet.class);
 
         if(EntityChangePublishingFacet.isPublishingEnabled(entity.getSpecification())) {
-            entityPropertyChangePublisher.publishChangedProperties(
-                    ObjectLifecyclePublisher
-                    .publishingPayloadForDeletion(entity));
+            entityChangeTracker().enlistDeleting(entity, ObjectLifecyclePublisher
+                    .propertyChangeRecordsForDeletion(entity));
         }
     }
 
@@ -125,9 +127,8 @@ implements
         postLifecycleEventIfRequired(entity, PersistedLifecycleEventFacet.class);
 
         if(EntityChangePublishingFacet.isPublishingEnabled(entity.getSpecification())) {
-            entityPropertyChangePublisher.publishChangedProperties(
-                    ObjectLifecyclePublisher
-                    .publishingPayloadForCreation(entity));
+            entityChangeTracker().enlistCreated(entity, ObjectLifecyclePublisher
+                    .propertyChangeRecordsForCreation(entity));
         }
     }
 
diff --git a/core/transaction/src/main/java/org/apache/isis/core/transaction/changetracking/EntityChangeTracker.java b/core/transaction/src/main/java/org/apache/isis/core/transaction/changetracking/EntityChangeTracker.java
index ffe1bf8da5..45acfa6121 100644
--- a/core/transaction/src/main/java/org/apache/isis/core/transaction/changetracking/EntityChangeTracker.java
+++ b/core/transaction/src/main/java/org/apache/isis/core/transaction/changetracking/EntityChangeTracker.java
@@ -18,6 +18,8 @@
  */
 package org.apache.isis.core.transaction.changetracking;
 
+import org.apache.isis.commons.collections.Can;
+import org.apache.isis.core.metamodel.services.objectlifecycle.PropertyChangeRecord;
 import org.apache.isis.core.metamodel.spec.ManagedObject;
 
 /**
@@ -38,6 +40,8 @@ public interface EntityChangeTracker {
      */
     void enlistCreated(ManagedObject entity);
 
+    void enlistCreated(ManagedObject entity, Can<PropertyChangeRecord> propertyChangeRecords);
+
     /**
      * Publishing support: for object stores to enlist an object that is about to be deleted,
      * capturing the pre-deletion value of the properties of the {@link ManagedObject}.
@@ -47,7 +51,9 @@ public interface EntityChangeTracker {
      * The post-modification values are captured  when the transaction commits.  In the case of deleted objects, a
      * dummy value <tt>'[DELETED]'</tt> is used as the post-modification value.
      */
-    void enlistDeleting(ManagedObject entity);
+    void enlistDeleting(ManagedObject entity) ;
+
+    void enlistDeleting(ManagedObject entity, Can<PropertyChangeRecord> propertyChangeRecords);
 
     /**
      * Publishing support: for object stores to enlist an object that is about to be updated,
@@ -59,6 +65,8 @@ public interface EntityChangeTracker {
      */
     void enlistUpdating(ManagedObject entity);
 
+    void enlistUpdating(ManagedObject entity, Can<PropertyChangeRecord> propertyChangeRecords);
+
     /**
      * Fires the appropriate event and lifecycle callback: {@literal LOADED}
      */
@@ -74,5 +82,6 @@ public interface EntityChangeTracker {
      */
     void recognizeUpdating(ManagedObject entity);
 
+
 }
 
diff --git a/extensions/security/audittrail/applib/src/test/java/org/apache/isis/extensions/audittrail/applib/integtests/AuditTrail_IntegTestAbstract.java b/extensions/security/audittrail/applib/src/test/java/org/apache/isis/extensions/audittrail/applib/integtests/AuditTrail_IntegTestAbstract.java
index 3673c987a7..e4c4910bec 100644
--- a/extensions/security/audittrail/applib/src/test/java/org/apache/isis/extensions/audittrail/applib/integtests/AuditTrail_IntegTestAbstract.java
+++ b/extensions/security/audittrail/applib/src/test/java/org/apache/isis/extensions/audittrail/applib/integtests/AuditTrail_IntegTestAbstract.java
@@ -74,7 +74,7 @@ public abstract class AuditTrail_IntegTestAbstract extends IsisIntegrationTestAb
         // then
         var entries = auditTrailEntryRepository.findAll();
         val propertyIds = entries.stream().map(AuditTrailEntry::getPropertyId).collect(Collectors.toList());
-        assertThat(propertyIds).containsExactlyInAnyOrder("name", "num", "num2");
+        assertThat(propertyIds).contains("name", "num", "num2");
 
         val entriesById = entries.stream().collect(Collectors.toMap(AuditTrailEntry::getPropertyId, x -> x));
         assertThat(entriesById.get("name"))
@@ -175,7 +175,7 @@ public abstract class AuditTrail_IntegTestAbstract extends IsisIntegrationTestAb
         // then
         var entries = auditTrailEntryRepository.findAll();
         val propertyIds = entries.stream().map(AuditTrailEntry::getPropertyId).collect(Collectors.toList());
-        assertThat(propertyIds).containsExactlyInAnyOrder("name", "num", "num2");
+        assertThat(propertyIds).contains("name", "num", "num2");
 
         val entriesById = entries.stream().collect(Collectors.toMap(AuditTrailEntry::getPropertyId, x -> x));
         assertThat(entriesById.get("name"))
diff --git a/extensions/security/audittrail/persistence-jpa/src/test/java/org/apache/isis/extensions/audittrail/jpa/integtests/AuditTrail_IntegTest.java b/extensions/security/audittrail/persistence-jpa/src/test/java/org/apache/isis/extensions/audittrail/jpa/integtests/AuditTrail_IntegTest.java
index 2c768d2f72..837457fcb5 100644
--- a/extensions/security/audittrail/persistence-jpa/src/test/java/org/apache/isis/extensions/audittrail/jpa/integtests/AuditTrail_IntegTest.java
+++ b/extensions/security/audittrail/persistence-jpa/src/test/java/org/apache/isis/extensions/audittrail/jpa/integtests/AuditTrail_IntegTest.java
@@ -73,11 +73,6 @@ public class AuditTrail_IntegTest extends AuditTrail_IntegTestAbstract {
         return Counter.builder().name(name).build();
     }
 
-    @BeforeEach()
-    void checkPersistenceStack() {
-        // currently disabled for JPA, since EntityPropertyChangePublisher still to be implemented.
-        Assumptions.assumeThat(isisBeanTypeRegistry.determineCurrentPersistenceStack().isJpa()).isFalse();
-    }
 
     @Inject IsisBeanTypeRegistry isisBeanTypeRegistry;
 }
diff --git a/extensions/security/audittrail/persistence-jpa/src/test/java/org/apache/isis/extensions/audittrail/jpa/integtests/model/Counter.java b/extensions/security/audittrail/persistence-jpa/src/test/java/org/apache/isis/extensions/audittrail/jpa/integtests/model/Counter.java
index f477634eb2..351fec8052 100644
--- a/extensions/security/audittrail/persistence-jpa/src/test/java/org/apache/isis/extensions/audittrail/jpa/integtests/model/Counter.java
+++ b/extensions/security/audittrail/persistence-jpa/src/test/java/org/apache/isis/extensions/audittrail/jpa/integtests/model/Counter.java
@@ -23,12 +23,15 @@ package org.apache.isis.extensions.audittrail.jpa.integtests.model;
 import javax.inject.Named;
 import javax.persistence.Column;
 import javax.persistence.Entity;
+import javax.persistence.EntityListeners;
 import javax.persistence.GeneratedValue;
 import javax.persistence.Id;
 import javax.persistence.Table;
 
 import org.apache.isis.applib.annotation.DomainObject;
 import org.apache.isis.applib.annotation.Nature;
+import org.apache.isis.applib.annotation.Publishing;
+import org.apache.isis.persistence.jpa.applib.integration.IsisEntityListener;
 
 import lombok.AccessLevel;
 import lombok.AllArgsConstructor;
@@ -43,7 +46,8 @@ import lombok.Setter;
         name = "Counter"
 )
 @Named("audittrail.test.Counter")
-@DomainObject(nature = Nature.ENTITY)
+@EntityListeners(IsisEntityListener.class)
+@DomainObject(nature = Nature.ENTITY, entityChangePublishing = Publishing.ENABLED)
 @NoArgsConstructor
 @Builder
 @AllArgsConstructor(access = AccessLevel.PRIVATE)
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 cb2427003b..fd39f99375 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
@@ -366,6 +366,10 @@ implements
         }
     }
 
+    @Override
+    public void enlistCreated(ManagedObject entity, Can<PropertyChangeRecord> propertyChangeRecords) {
+    }
+
     @Override
     public void enlistDeleting(final ManagedObject entity) {
         _Xray.enlistDeleting(entity, interactionProviderProvider);
@@ -374,6 +378,10 @@ implements
         postLifecycleEventIfRequired(entity, RemovingLifecycleEventFacet.class);
     }
 
+    @Override
+    public void enlistDeleting(ManagedObject entity, Can<PropertyChangeRecord> propertyChangeRecords) {
+    }
+
     @Override
     public void enlistUpdating(final ManagedObject entity) {
         _Xray.enlistUpdating(entity, interactionProviderProvider);
@@ -389,6 +397,10 @@ implements
         }
     }
 
+    @Override
+    public void enlistUpdating(ManagedObject entity, Can<PropertyChangeRecord> propertyChangeRecords) {
+    }
+
     @Override
     public void recognizeLoaded(final ManagedObject entity) {
         _Xray.recognizeLoaded(entity, interactionProviderProvider);
diff --git a/persistence/jpa/integration/src/main/java/org/apache/isis/persistence/jpa/integration/IsisModulePersistenceJpaIntegration.java b/persistence/jpa/integration/src/main/java/org/apache/isis/persistence/jpa/integration/IsisModulePersistenceJpaIntegration.java
index 682b625059..6989843f23 100644
--- a/persistence/jpa/integration/src/main/java/org/apache/isis/persistence/jpa/integration/IsisModulePersistenceJpaIntegration.java
+++ b/persistence/jpa/integration/src/main/java/org/apache/isis/persistence/jpa/integration/IsisModulePersistenceJpaIntegration.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.PersistenceMetricsServiceJpa;
+import org.apache.isis.persistence.jpa.integration.changetracking.EntityChangeTrackerJpa;
 import org.apache.isis.persistence.jpa.integration.entity.JpaEntityIntegration;
 import org.apache.isis.persistence.jpa.integration.services.JpaSupportServiceUsingSpring;
 import org.apache.isis.persistence.jpa.integration.typeconverters.applib.IsisBookmarkConverter;
@@ -52,7 +52,7 @@ import org.apache.isis.persistence.jpa.metamodel.IsisModulePersistenceJpaMetamod
 
         // @Service's
         JpaSupportServiceUsingSpring.class,
-        PersistenceMetricsServiceJpa.class,
+        EntityChangeTrackerJpa.class,
 
 })
 @EntityScan(basePackageClasses = {
diff --git a/persistence/jdo/integration/src/main/java/org/apache/isis/persistence/jdo/integration/changetracking/EntityChangeTrackerJdo.java b/persistence/jpa/integration/src/main/java/org/apache/isis/persistence/jpa/integration/changetracking/EntityChangeTrackerJpa.java
similarity index 75%
copy from persistence/jdo/integration/src/main/java/org/apache/isis/persistence/jdo/integration/changetracking/EntityChangeTrackerJdo.java
copy to persistence/jpa/integration/src/main/java/org/apache/isis/persistence/jpa/integration/changetracking/EntityChangeTrackerJpa.java
index cb2427003b..2cee271ff1 100644
--- a/persistence/jdo/integration/src/main/java/org/apache/isis/persistence/jdo/integration/changetracking/EntityChangeTrackerJdo.java
+++ b/persistence/jpa/integration/src/main/java/org/apache/isis/persistence/jpa/integration/changetracking/EntityChangeTrackerJpa.java
@@ -16,8 +16,9 @@
  *  specific language governing permissions and limitations
  *  under the License.
  */
-package org.apache.isis.persistence.jdo.integration.changetracking;
+package org.apache.isis.persistence.jpa.integration.changetracking;
 
+import java.util.List;
 import java.util.Map;
 import java.util.Optional;
 import java.util.Set;
@@ -33,6 +34,7 @@ import javax.inject.Provider;
 import org.springframework.beans.factory.annotation.Qualifier;
 import org.springframework.context.event.EventListener;
 import org.springframework.core.annotation.Order;
+import org.springframework.lang.Nullable;
 import org.springframework.stereotype.Service;
 
 import org.apache.isis.applib.annotation.EntityChangeKind;
@@ -48,6 +50,7 @@ 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.commons.internal.base._Lazy;
+import org.apache.isis.commons.internal.collections._Lists;
 import org.apache.isis.commons.internal.collections._Maps;
 import org.apache.isis.commons.internal.collections._Sets;
 import org.apache.isis.commons.internal.exceptions._Exceptions;
@@ -71,7 +74,6 @@ import org.apache.isis.core.metamodel.services.objectlifecycle.PropertyChangeRec
 import org.apache.isis.core.metamodel.services.objectlifecycle.PropertyValuePlaceholder;
 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.MixedIn;
 import org.apache.isis.core.transaction.changetracking.EntityChangeTracker;
 import org.apache.isis.core.transaction.changetracking.EntityChangesPublisher;
@@ -90,12 +92,12 @@ import lombok.extern.log4j.Log4j2;
  * @since 2.0 {@index}
  */
 @Service
-@Named("isis.transaction.EntityChangeTrackerJdo")
+@Named("isis.transaction.EntityChangeTrackerJpa")
 @Priority(PriorityPrecedence.EARLY)
 @Qualifier("jdo")
 @InteractionScope
 @Log4j2
-public class EntityChangeTrackerJdo
+public class EntityChangeTrackerJpa
 extends PersistenceCallbackHandlerAbstract
 implements
     MetricsService,
@@ -103,27 +105,50 @@ implements
     HasEnlistedEntityPropertyChanges,
     HasEnlistedEntityChanges {
 
+    /**
+     * If provided by the ORM.
+     */
+    private final List<PropertyChangeRecord> enlistedPropertyChangesOfCreated = _Lists.newArrayList();
+    /**
+     * If provided by the ORM.
+     */
+    private final List<PropertyChangeRecord> enlistedPropertyChangesOfUpdated = _Lists.newArrayList();
+    /**
+     * If provided by the ORM.
+     */
+    private final List<PropertyChangeRecord> enlistedPropertyChangesOfDeleted = _Lists.newArrayList();
+
     /**
      * Contains initial change records having set the pre-values of every property of every object that was enlisted.
+     *
+     * <p>
+     *     ONLY USED IF THE ENLISTED PROPERTY CHANGES ({@link #enlistedPropertyChangesOfCreated}, {@link #enlistedPropertyChangesOfUpdated}, {@link #enlistedPropertyChangesOfDeleted}) were not provided already.
+     * </p>
      */
     private final Map<String, PropertyChangeRecord> propertyChangeRecordsById = _Maps.newLinkedHashMap();
 
     /**
      * Contains pre- and post- values of every property of every object that actually changed. A lazy snapshot,
      * triggered by internal call to {@link #snapshotPropertyChangeRecords()}.
+     *
+     * <p>
+     *     ONLY USED IF THE ENLISTED PROPERTY CHANGES ({@link #enlistedPropertyChangesOfCreated}, {@link #enlistedPropertyChangesOfUpdated}, {@link #enlistedPropertyChangesOfDeleted}) were not provided already.
+     * </p>
      */
     private final _Lazy<Set<PropertyChangeRecord>> entityPropertyChangeRecordsForPublishing
         = _Lazy.threadSafe(this::capturePostValuesAndDrain);
 
+
     @Getter(AccessLevel.PACKAGE)
     private final Map<Bookmark, EntityChangeKind> changeKindByEnlistedAdapter = _Maps.newLinkedHashMap();
 
+
     private final EntityPropertyChangePublisher entityPropertyChangePublisher;
     private final EntityChangesPublisher entityChangesPublisher;
     private final Provider<InteractionProvider> interactionProviderProvider;
 
     @Inject
-    public EntityChangeTrackerJdo(
+    public EntityChangeTrackerJpa(
             final EntityPropertyChangePublisher entityPropertyChangePublisher,
             final EntityChangesPublisher entityChangesPublisher,
             final EventBusService eventBusService,
@@ -140,30 +165,47 @@ implements
         .orElse(false);
     }
 
-    private void enlistCreatedInternal(final @NonNull ManagedObject adapter) {
+    private void enlistCreatedInternal(final @NonNull ManagedObject adapter, @Nullable Can<PropertyChangeRecord> propertyChangeRecords) {
         if(!isEntityEnabledForChangePublishing(adapter)) {
             return;
         }
         enlistForChangeKindPublishing(adapter, EntityChangeKind.CREATE);
-        enlistForPreAndPostValuePublishing(adapter, record->record.setPreValue(PropertyValuePlaceholder.NEW));
+        if (propertyChangeRecords != null) {
+            // provided by ORM
+            propertyChangeRecords.forEach(this.enlistedPropertyChangesOfCreated::add);
+        } else {
+            // home-grown approach
+            enlistForPreAndPostValuePublishing(adapter, record->record.setPreValue(PropertyValuePlaceholder.NEW));
+        }
     }
 
-    private void enlistUpdatingInternal(
-            final @NonNull ManagedObject entity) {
+    private void enlistUpdatingInternal(final @NonNull ManagedObject entity, Can<PropertyChangeRecord> propertyChangeRecords) {
         if(!isEntityEnabledForChangePublishing(entity)) {
             return;
         }
         enlistForChangeKindPublishing(entity, EntityChangeKind.UPDATE);
-        enlistForPreAndPostValuePublishing(entity, PropertyChangeRecord::updatePreValue);
+        if(propertyChangeRecords != null) {
+            // provided by ORM
+            propertyChangeRecords.forEach(this.enlistedPropertyChangesOfUpdated::add);
+        } else {
+            // home-grown approach
+            enlistForPreAndPostValuePublishing(entity, PropertyChangeRecord::updatePreValue);
+        }
     }
 
-    private void enlistDeletingInternal(final @NonNull ManagedObject adapter) {
+    private void enlistDeletingInternal(final @NonNull ManagedObject adapter, Can<PropertyChangeRecord> propertyChangeRecords) {
         if(!isEntityEnabledForChangePublishing(adapter)) {
             return;
         }
         final boolean enlisted = enlistForChangeKindPublishing(adapter, EntityChangeKind.DELETE);
         if(enlisted) {
-            enlistForPreAndPostValuePublishing(adapter, PropertyChangeRecord::updatePreValue);
+            if (propertyChangeRecords != null) {
+                // provided by ORM
+                propertyChangeRecords.forEach(this.enlistedPropertyChangesOfDeleted::add);
+            } else {
+                // home-grown approach
+                enlistForPreAndPostValuePublishing(adapter, PropertyChangeRecord::updatePreValue);
+            }
         }
     }
 
@@ -173,12 +215,17 @@ implements
         return entityPropertyChangeRecordsForPublishing.get();
     }
 
+    private boolean isOrmSuppliedChangeRecords() {
+        return !(enlistedPropertyChangesOfCreated.isEmpty() && enlistedPropertyChangesOfUpdated.isEmpty() && enlistedPropertyChangesOfDeleted.isEmpty());
+    }
+
     private boolean isEntityEnabledForChangePublishing(final @NonNull ManagedObject adapter) {
 
         if(!EntityChangePublishingFacet.isPublishingEnabled(adapter.getSpecification())) {
             return false; // ignore entities that are not enabled for entity change publishing
         }
 
+        // if home-grown
         if(entityPropertyChangeRecordsForPublishing.isMemoized()) {
             throw _Exceptions.illegalState("Cannot enlist additional changes for auditing, "
                     + "since changedObjectPropertiesRef was already prepared (memoized) for auditing.");
@@ -213,9 +260,14 @@ implements
 
     private void postPublishing() {
         log.debug("purging entity change records");
-        propertyChangeRecordsById.clear();
+        this.enlistedPropertyChangesOfCreated.clear();
+        this.enlistedPropertyChangesOfUpdated.clear();
+        this.enlistedPropertyChangesOfDeleted.clear();
+
+//        propertyChangeRecordsById.clear();
         changeKindByEnlistedAdapter.clear();
-        entityPropertyChangeRecordsForPublishing.clear();
+//        entityPropertyChangeRecordsForPublishing.clear();
+
         entityChangeEventCount.reset();
         numberEntitiesLoaded.reset();
     }
@@ -240,7 +292,6 @@ implements
             final java.sql.Timestamp timestamp,
             final String userName,
             final TransactionId txId) {
-
         return snapshotPropertyChangeRecords().stream()
                 .map(propertyChangeRecord -> propertyChangeRecord.toEntityPropertyChange(timestamp, userName, txId))
                 .collect(Can.toCan());
@@ -316,20 +367,34 @@ implements
      */
     private Set<PropertyChangeRecord> capturePostValuesAndDrain() {
 
-        val records = propertyChangeRecordsById.values().stream()
-                // set post values, which have been left empty up to now
-                .peek(rec->{
-                    // assuming this check correctly detects deleted entities (JDO)
-                    if(EntityUtil.isDetachedOrRemoved(rec.getEntity())) {
-                        rec.updatePostValueAsDeleted();
-                    } else {
-                        rec.updatePostValueAsNonDeleted();
-                    }
-                })
-                .filter(managedProperty->managedProperty.getPreAndPostValue().shouldPublish())
-                .collect(_Sets.toUnmodifiable());
-
-        propertyChangeRecordsById.clear();
+        Set<PropertyChangeRecord> records;
+
+        if (isOrmSuppliedChangeRecords()) {
+            records = _Sets.newLinkedHashSet();
+            // TODO: might need to make this more sophisticated ?
+            records.addAll(enlistedPropertyChangesOfCreated);
+            records.addAll(enlistedPropertyChangesOfUpdated);
+            records.addAll(enlistedPropertyChangesOfDeleted);
+
+            enlistedPropertyChangesOfCreated.clear();
+            enlistedPropertyChangesOfUpdated.clear();
+            enlistedPropertyChangesOfDeleted.clear();
+        } else {
+            records = propertyChangeRecordsById.values().stream()
+                    // set post values, which have been left empty up to now
+                    .peek(rec->{
+                        // assuming this check correctly detects deleted entities (JDO)
+                        if(ManagedObjects.EntityUtil.isDetachedOrRemoved(rec.getEntity())) {
+                            rec.updatePostValueAsDeleted();
+                        } else {
+                            rec.updatePostValueAsNonDeleted();
+                        }
+                    })
+                    .filter(managedProperty->managedProperty.getPreAndPostValue().shouldPublish())
+                    .collect(_Sets.toUnmodifiable());
+
+            propertyChangeRecordsById.clear();
+        }
 
         return records;
 
@@ -356,9 +421,15 @@ implements
 
     @Override
     public void enlistCreated(final ManagedObject entity) {
+        enlistCreated(entity, null);
+    }
+
+
+    @Override
+    public void enlistCreated(ManagedObject entity,  @Nullable final Can<PropertyChangeRecord> propertyChangeRecords) {
         _Xray.enlistCreated(entity, interactionProviderProvider);
         val hasAlreadyBeenEnlisted = isEnlisted(entity);
-        enlistCreatedInternal(entity);
+        enlistCreatedInternal(entity, propertyChangeRecords);
 
         if(!hasAlreadyBeenEnlisted) {
             CallbackFacet.callCallback(entity, PersistedCallbackFacet.class);
@@ -368,19 +439,29 @@ implements
 
     @Override
     public void enlistDeleting(final ManagedObject entity) {
+        enlistDeleting(entity, null);
+    }
+
+    @Override
+    public void enlistDeleting(ManagedObject entity, final Can<PropertyChangeRecord> propertyChangeRecords) {
         _Xray.enlistDeleting(entity, interactionProviderProvider);
-        enlistDeletingInternal(entity);
+        enlistDeletingInternal(entity, propertyChangeRecords);
         CallbackFacet.callCallback(entity, RemovingCallbackFacet.class);
         postLifecycleEventIfRequired(entity, RemovingLifecycleEventFacet.class);
     }
 
     @Override
     public void enlistUpdating(final ManagedObject entity) {
+        enlistUpdating(entity, null);
+    }
+
+    @Override
+    public void enlistUpdating(ManagedObject entity, final Can<PropertyChangeRecord> propertyChangeRecords) {
         _Xray.enlistUpdating(entity, interactionProviderProvider);
         val hasAlreadyBeenEnlisted = isEnlisted(entity);
         // we call this come what may;
         // additional properties may now have been changed, and the changeKind for publishing might also be modified
-        enlistUpdatingInternal(entity);
+        enlistUpdatingInternal(entity, propertyChangeRecords);
 
         if(!hasAlreadyBeenEnlisted) {
             // prevent an infinite loop... don't call the 'updating()' callback on this object if we have already done so
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
deleted file mode 100644
index 407ce89282..0000000000
--- a/persistence/jpa/integration/src/main/java/org/apache/isis/persistence/jpa/integration/changetracking/PersistenceMetricsServiceJpa.java
+++ /dev/null
@@ -1,54 +0,0 @@
-/*
- *  Licensed to the Apache Software Foundation (ASF) under one
- *  or more contributor license agreements.  See the NOTICE file
- *  distributed with this work for additional information
- *  regarding copyright ownership.  The ASF licenses this file
- *  to you under the Apache License, Version 2.0 (the
- *  "License"); you may not use this file except in compliance
- *  with the License.  You may obtain a copy of the License at
- *
- *        http://www.apache.org/licenses/LICENSE-2.0
- *
- *  Unless required by applicable law or agreed to in writing,
- *  software distributed under the License is distributed on an
- *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- *  KIND, either express or implied.  See the License for the
- *  specific language governing permissions and limitations
- *  under the License.
- */
-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/persistence/jpa/integration/src/main/java/org/apache/isis/persistence/jpa/integration/changetracking/_ChangingEntitiesFactory.java b/persistence/jpa/integration/src/main/java/org/apache/isis/persistence/jpa/integration/changetracking/_ChangingEntitiesFactory.java
new file mode 100644
index 0000000000..c3e142433f
--- /dev/null
+++ b/persistence/jpa/integration/src/main/java/org/apache/isis/persistence/jpa/integration/changetracking/_ChangingEntitiesFactory.java
@@ -0,0 +1,143 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you under the Apache License, Version 2.0 (the
+ *  "License"); you may not use this file except in compliance
+ *  with the License.  You may obtain a copy of the License at
+ *
+ *        http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing,
+ *  software distributed under the License is distributed on an
+ *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ *  KIND, either express or implied.  See the License for the
+ *  specific language governing permissions and limitations
+ *  under the License.
+ */
+package org.apache.isis.persistence.jpa.integration.changetracking;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Optional;
+import java.util.UUID;
+
+import org.apache.isis.applib.annotation.EntityChangeKind;
+import org.apache.isis.applib.jaxb.JavaSqlXMLGregorianCalendarMarshalling;
+import org.apache.isis.applib.services.bookmark.Bookmark;
+import org.apache.isis.applib.services.iactn.Interaction;
+import org.apache.isis.applib.services.publishing.spi.EntityChanges;
+import org.apache.isis.core.metamodel.execution.InteractionInternal;
+import org.apache.isis.schema.chg.v2.ChangesDto;
+import org.apache.isis.schema.chg.v2.ObjectsDto;
+import org.apache.isis.schema.common.v2.OidsDto;
+
+import lombok.val;
+
+final class _ChangingEntitiesFactory {
+
+    public static Optional<EntityChanges> createChangingEntities(
+            final java.sql.Timestamp completedAt,
+            final String userName,
+            final EntityChangeTrackerJpa entityChangeTracker) {
+
+        if(entityChangeTracker.getChangeKindByEnlistedAdapter().isEmpty()) {
+            return Optional.empty();
+        }
+
+        // take a copy of enlisted adapters ... the JDO implementation of the PublishingService
+        // creates further entities which would be enlisted;
+        // taking copy of the map avoids ConcurrentModificationException
+        val changeKindByEnlistedAdapter = new HashMap<>(
+                entityChangeTracker.getChangeKindByEnlistedAdapter());
+
+        val changingEntities = newChangingEntities(
+                completedAt,
+                userName,
+                entityChangeTracker.currentInteraction(),
+                entityChangeTracker.numberEntitiesLoaded(),
+                // side-effect: it locks the result for this transaction,
+                // such that cannot enlist on top of it
+                entityChangeTracker.snapshotPropertyChangeRecords().size(),
+                changeKindByEnlistedAdapter);
+
+        return Optional.of(changingEntities);
+    }
+
+    // -- HELPER
+
+    private static EntityChanges newChangingEntities(
+            final java.sql.Timestamp completedAt,
+            final String userName,
+            final Interaction interaction,
+            final int numberEntitiesLoaded,
+            final int numberEntityPropertiesModified,
+            final Map<Bookmark, EntityChangeKind> changeKindByEnlistedAdapter) {
+
+        val interactionId = interaction.getInteractionId();
+        final int nextEventSequence = ((InteractionInternal) interaction).getThenIncrementTransactionSequence();
+
+        return new _SimpleChangingEntities(
+                    interactionId, nextEventSequence,
+                    userName, completedAt,
+                    numberEntitiesLoaded,
+                    numberEntityPropertiesModified,
+                    ()->newDto(
+                            interactionId, nextEventSequence,
+                            userName, completedAt,
+                            numberEntitiesLoaded,
+                            numberEntityPropertiesModified,
+                            changeKindByEnlistedAdapter));
+    }
+
+    private static ChangesDto newDto(
+            final UUID interactionId, final int transactionSequenceNum,
+            final String userName, final java.sql.Timestamp completedAt,
+            final int numberEntitiesLoaded,
+            final int numberEntityPropertiesModified,
+            final Map<Bookmark, EntityChangeKind> changeKindByEnlistedEntity) {
+
+        val objectsDto = new ObjectsDto();
+        objectsDto.setCreated(new OidsDto());
+        objectsDto.setUpdated(new OidsDto());
+        objectsDto.setDeleted(new OidsDto());
+
+        changeKindByEnlistedEntity.forEach((bookmark, kind)->{
+            val oidDto = bookmark.toOidDto();
+            if(oidDto==null) {
+                return;
+            }
+            switch(kind) {
+            case CREATE:
+                objectsDto.getCreated().getOid().add(oidDto);
+                return;
+            case UPDATE:
+                objectsDto.getUpdated().getOid().add(oidDto);
+                return;
+            case DELETE:
+                objectsDto.getDeleted().getOid().add(oidDto);
+                return;
+            }
+        });
+
+        objectsDto.setLoaded(numberEntitiesLoaded);
+        objectsDto.setPropertiesModified(numberEntityPropertiesModified);
+
+        val changesDto = new ChangesDto();
+
+        changesDto.setMajorVersion("2");
+        changesDto.setMinorVersion("0");
+
+        changesDto.setInteractionId(interactionId.toString());
+        changesDto.setSequence(transactionSequenceNum);
+
+        changesDto.setUsername(userName);
+        changesDto.setCompletedAt(JavaSqlXMLGregorianCalendarMarshalling.toXMLGregorianCalendar(completedAt));
+
+        changesDto.setObjects(objectsDto);
+        return changesDto;
+    }
+
+
+}
diff --git a/persistence/jpa/integration/src/main/java/org/apache/isis/persistence/jpa/integration/changetracking/_SimpleChangingEntities.java b/persistence/jpa/integration/src/main/java/org/apache/isis/persistence/jpa/integration/changetracking/_SimpleChangingEntities.java
new file mode 100644
index 0000000000..addff06fb0
--- /dev/null
+++ b/persistence/jpa/integration/src/main/java/org/apache/isis/persistence/jpa/integration/changetracking/_SimpleChangingEntities.java
@@ -0,0 +1,121 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you under the Apache License, Version 2.0 (the
+ *  "License"); you may not use this file except in compliance
+ *  with the License.  You may obtain a copy of the License at
+ *
+ *        http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing,
+ *  software distributed under the License is distributed on an
+ *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ *  KIND, either express or implied.  See the License for the
+ *  specific language governing permissions and limitations
+ *  under the License.
+ */
+package org.apache.isis.persistence.jpa.integration.changetracking;
+
+import java.sql.Timestamp;
+import java.util.UUID;
+import java.util.function.Supplier;
+
+import org.apache.isis.applib.services.publishing.spi.EntityChanges;
+import org.apache.isis.schema.chg.v2.ChangesDto;
+
+import lombok.NonNull;
+import lombok.ToString;
+
+/**
+ * Captures which objects were created, updated or deleted in the course of a transaction.
+ */
+@ToString
+final class _SimpleChangingEntities implements EntityChanges {
+
+    private UUID transactionUuid;
+    private final int sequence;
+    private final String userName;
+    private final Timestamp completedAt;
+    private final int numberEntitiesLoaded;
+    private final int numberEntityPropertiesModified;
+
+    @ToString.Exclude
+    private final Supplier<ChangesDto> changesDtoSupplier;
+
+    public _SimpleChangingEntities(
+            final @NonNull UUID transactionUuid,
+            final int sequence,
+            final @NonNull String userName,
+            final @NonNull Timestamp completedAt,
+            final int numberEntitiesLoaded,
+            final int numberEntityPropertiesModified,
+            final @NonNull Supplier<ChangesDto> changesDtoSupplier) {
+
+        this.transactionUuid = transactionUuid;
+        this.sequence = sequence;
+        this.userName = userName;
+        this.completedAt = completedAt;
+        this.numberEntitiesLoaded = numberEntitiesLoaded;
+        this.numberEntityPropertiesModified = numberEntityPropertiesModified;
+        this.changesDtoSupplier = changesDtoSupplier;
+    }
+
+    @Override
+    public UUID getInteractionId() {
+        return transactionUuid;
+    }
+
+    @Override
+    public int getSequence() {
+        return sequence;
+    }
+
+    /**
+     * The date/time at which this set of enlisted objects was created
+     * (approx the completion time of the transaction).
+     */
+    @Override
+    public Timestamp getCompletedAt() {
+        return completedAt;
+    }
+
+    @Override
+    public String getUsername() {
+        return userName;
+    }
+
+    private ChangesDto dto;
+
+    @Override
+    public ChangesDto getDto() {
+        return dto != null ? dto : (dto = changesDtoSupplier.get());
+    }
+
+    @Override
+    public int getNumberLoaded() {
+        return numberEntitiesLoaded;
+    }
+
+    @Override
+    public int getNumberCreated() {
+        return getDto().getObjects().getCreated().getOid().size();
+    }
+
+    @Override
+    public int getNumberUpdated() {
+        return getDto().getObjects().getUpdated().getOid().size();
+    }
+
+    @Override
+    public int getNumberDeleted() {
+        return getDto().getObjects().getDeleted().getOid().size();
+    }
+
+    @Override
+    public int getNumberPropertiesModified() {
+        return numberEntityPropertiesModified;
+    }
+
+}
diff --git a/persistence/jpa/integration/src/main/java/org/apache/isis/persistence/jpa/integration/changetracking/_Xray.java b/persistence/jpa/integration/src/main/java/org/apache/isis/persistence/jpa/integration/changetracking/_Xray.java
new file mode 100644
index 0000000000..50955f25fb
--- /dev/null
+++ b/persistence/jpa/integration/src/main/java/org/apache/isis/persistence/jpa/integration/changetracking/_Xray.java
@@ -0,0 +1,145 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you under the Apache License, Version 2.0 (the
+ *  "License"); you may not use this file except in compliance
+ *  with the License.  You may obtain a copy of the License at
+ *
+ *        http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing,
+ *  software distributed under the License is distributed on an
+ *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ *  KIND, either express or implied.  See the License for the
+ *  specific language governing permissions and limitations
+ *  under the License.
+ */
+package org.apache.isis.persistence.jpa.integration.changetracking;
+
+import java.awt.Color;
+
+import javax.inject.Provider;
+
+import org.apache.isis.applib.services.iactn.InteractionProvider;
+import org.apache.isis.commons.internal.debug._XrayEvent;
+import org.apache.isis.commons.internal.debug.xray.XrayUi;
+import org.apache.isis.core.metamodel.spec.ManagedObject;
+import org.apache.isis.core.metamodel.spec.ManagedObjects;
+import org.apache.isis.core.security.util.XrayUtil;
+
+import lombok.val;
+
+final class _Xray {
+
+    public static void publish(
+            final EntityChangeTrackerJpa entityChangeTrackerDefault,
+            final Provider<InteractionProvider> interactionProviderProvider) {
+
+        if(!XrayUi.isXrayEnabled()) {
+            return;
+        }
+
+        final long propertyChangeRecordCount = entityChangeTrackerDefault.countPotentialPropertyChangeRecords();
+
+        val enteringLabel = String.format("consider %d entity change records for publishing",
+                propertyChangeRecordCount);
+
+        XrayUtil.createSequenceHandle(interactionProviderProvider.get(), "ec-tracker")
+        .ifPresent(handle->{
+
+            handle.submit(sequenceData->{
+
+                sequenceData.alias("ec-tracker", "EntityChange-\nTracker-\n(Default)");
+
+                if(propertyChangeRecordCount==0) {
+                    sequenceData.setConnectionArrowColor(Color.GRAY);
+                    sequenceData.setConnectionLabelColor(Color.GRAY);
+                }
+
+                val callee = handle.getCallees().getFirstOrFail();
+                sequenceData.enter(handle.getCaller(), callee, enteringLabel);
+                //sequenceData.activate(callee);
+            });
+
+        });
+    }
+
+    public static void enlistCreated(
+            final ManagedObject entity,
+            final Provider<InteractionProvider> interactionProviderProvider) {
+        addSequence("enlistCreated", entity, interactionProviderProvider);
+    }
+
+    public static void enlistDeleting(
+            final ManagedObject entity,
+            final Provider<InteractionProvider> interactionProviderProvider) {
+        addSequence("enlistDeleting", entity, interactionProviderProvider);
+    }
+
+    public static void enlistUpdating(
+            final ManagedObject entity,
+            final Provider<InteractionProvider> interactionProviderProvider) {
+        addSequence("enlistUpdating", entity, interactionProviderProvider);
+    }
+
+    public static void recognizeLoaded(
+            final ManagedObject entity,
+            final Provider<InteractionProvider> interactionProviderProvider) {
+        addSequence("recognizeLoaded", entity, interactionProviderProvider);
+    }
+
+    public static void recognizePersisting(
+            final ManagedObject entity,
+            final Provider<InteractionProvider> interactionProviderProvider) {
+        addSequence("recognizePersisting", entity, interactionProviderProvider);
+    }
+
+    public static void recognizeUpdating(
+            final ManagedObject entity,
+            final Provider<InteractionProvider> interactionProviderProvider) {
+        addSequence("recognizeUpdating", entity, interactionProviderProvider);
+    }
+
+    // -- HELPER
+
+    private static void addSequence(
+            final String what,
+            final ManagedObject entity,
+            final Provider<InteractionProvider> interactionProviderProvider) {
+
+        if(!XrayUi.isXrayEnabled()) {
+            return;
+        }
+
+        val enteringLabel = String.format("%s %s",
+                what,
+                ManagedObjects.isNullOrUnspecifiedOrEmpty(entity)
+                    ? "<empty>"
+                    : String.format("%s:\n%s",
+                            entity.getSpecification().getLogicalTypeName(),
+                            "" + entity.getPojo()));
+
+        _XrayEvent.event(enteringLabel);
+
+        XrayUtil.createSequenceHandle(interactionProviderProvider.get(), "ec-tracker")
+        .ifPresent(handle->{
+
+            handle.submit(sequenceData->{
+
+                sequenceData.alias("ec-tracker", "EntityChange-\nTracker-\n(Default)");
+
+                val callee = handle.getCallees().getFirstOrFail();
+                sequenceData.enter(handle.getCaller(), callee, enteringLabel);
+                //sequenceData.activate(callee);
+            });
+
+        });
+
+    }
+
+
+
+
+}
diff --git a/viewers/wicket/ui/src/main/java/org/apache/isis/viewer/wicket/ui/pages/common/bootstrap/css/bootstrap-overrides-all-v2.css b/viewers/wicket/ui/src/main/java/org/apache/isis/viewer/wicket/ui/pages/common/bootstrap/css/bootstrap-overrides-all-v2.css
index de3fb3caa0..28c87ea119 100644
--- a/viewers/wicket/ui/src/main/java/org/apache/isis/viewer/wicket/ui/pages/common/bootstrap/css/bootstrap-overrides-all-v2.css
+++ b/viewers/wicket/ui/src/main/java/org/apache/isis/viewer/wicket/ui/pages/common/bootstrap/css/bootstrap-overrides-all-v2.css
@@ -1011,6 +1011,11 @@ footer .footer-image {
     /*padding-bottom: 7px;*/
 }
 
+.actionParametersForm .alert {
+    margin-top: 10px;
+}
+
+
 .propertyEditForm .buttons .wicket-ajax-indicator,
 .actionParametersForm .buttons .wicket-ajax-indicator,
 .additionalLinkList .wicket-ajax-indicator {


[isis] 12/12: ISIS-3110: removes circular dependency

Posted by da...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

danhaywood pushed a commit to branch ISIS-3110
in repository https://gitbox.apache.org/repos/asf/isis.git

commit 8464d7a2bce2d086b4b424c49660b695c4c76e25
Author: Dan Haywood <da...@haywood-associates.co.uk>
AuthorDate: Thu Aug 4 10:26:49 2022 +0100

    ISIS-3110: removes circular dependency
---
 .../publish/ObjectLifecyclePublisherDefault.java               | 10 +++++++---
 1 file changed, 7 insertions(+), 3 deletions(-)

diff --git a/core/runtimeservices/src/main/java/org/apache/isis/core/runtimeservices/publish/ObjectLifecyclePublisherDefault.java b/core/runtimeservices/src/main/java/org/apache/isis/core/runtimeservices/publish/ObjectLifecyclePublisherDefault.java
index 71489accdb..91a6473f28 100644
--- a/core/runtimeservices/src/main/java/org/apache/isis/core/runtimeservices/publish/ObjectLifecyclePublisherDefault.java
+++ b/core/runtimeservices/src/main/java/org/apache/isis/core/runtimeservices/publish/ObjectLifecyclePublisherDefault.java
@@ -63,10 +63,14 @@ public class ObjectLifecyclePublisherDefault implements ObjectLifecyclePublisher
 
     private final Provider<EntityChangeTracker> entityChangeTrackerProvider;
     private final Provider<LifecycleCallbackNotifier> lifecycleCallbackNotifierProvider;
-    private final InteractionService interactionService;
+    private final Provider<InteractionService> interactionServiceProvider;
 
-    EntityChangeTracker entityChangeTracker() {
-        return interactionService.isInInteraction() ? entityChangeTrackerProvider.get() : EntityChangeTracker.NOOP;
+    private InteractionService interactionService() {
+        return interactionServiceProvider.get();
+    }
+
+    private EntityChangeTracker entityChangeTracker() {
+        return interactionService().isInInteraction() ? entityChangeTrackerProvider.get() : EntityChangeTracker.NOOP;
     }
 
     LifecycleCallbackNotifier lifecycleCallbackNotifier() {


[isis] 04/12: ISIS-3110: reworks JPA and JDO auditing (EntityPropertyChange)

Posted by da...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

danhaywood pushed a commit to branch ISIS-3110
in repository https://gitbox.apache.org/repos/asf/isis.git

commit 9d51d073c8d5c009d9e90bb1c8c7d47830e3ff8e
Author: Dan Haywood <da...@haywood-associates.co.uk>
AuthorDate: Wed Aug 3 19:30:47 2022 +0100

    ISIS-3110: reworks JPA and JDO auditing (EntityPropertyChange)
---
 .../metamodel/objectmanager/ObjectManager.java     |  15 +-
 .../objectlifecycle/ObjectLifecyclePublisher.java  | 152 +++++-----
 .../objectlifecycle/PropertyChangeRecord.java      |  54 ++--
 .../objectlifecycle/PropertyChangeRecordId.java    |  70 +++++
 .../objectlifecycle/PropertyValuePlaceholder.java  |   1 +
 .../IsisModuleCoreRuntimeServices.java             |   2 +
 .../EntityPropertyChangePublisherDefault.java      |  60 ++--
 .../publish/LifecycleCallbackNotifier.java         | 158 ++++++++++
 .../publish/ObjectLifecyclePublisherDefault.java   |  89 +++---
 .../changetracking/EntityChangeTracker.java        |  56 ++--
 .../EntityPropertyChangePublisher.java             |   5 +-
 .../changetracking/EntityChangeTrackerDefault.java | 317 ++++++++-------------
 .../jpa/integration/changetracking/_Xray.java      |  11 -
 .../IsisModulePersistenceJdoDatanucleus.java       |  10 +-
 .../changetracking/JdoLifecycleListener.java       |  39 +--
 .../metamodel/facets/entity/JdoEntityFacet.java    |  11 +-
 .../jpa/applib/integration/IsisEntityListener.java |  65 +++--
 17 files changed, 607 insertions(+), 508 deletions(-)

diff --git a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/objectmanager/ObjectManager.java b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/objectmanager/ObjectManager.java
index 2907699e8d..03204bb5a6 100644
--- a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/objectmanager/ObjectManager.java
+++ b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/objectmanager/ObjectManager.java
@@ -145,7 +145,7 @@ public interface ObjectManager {
     public default ManagedObject adapt(
             final @Nullable Object pojo,
             final @NonNull Supplier<ObjectSpecification> fallbackElementType,
-            final EntityAdaptingMode bookmarking) {
+            final EntityAdaptingMode entityAdaptingMode) {
         if(pojo==null) {
             return ManagedObject.unspecified();
         }
@@ -159,11 +159,11 @@ public interface ObjectManager {
             return ManagedObject.unspecified();
         }
         return spec.isScalar()
-                ? autoBookmarked(spec, pojo, bookmarking)
+                ? managedObjectFor(spec, pojo, entityAdaptingMode)
                 : PackedManagedObject.pack(
                         spec.getElementSpecification().orElseGet(fallbackElementType),
                         _NullSafe.streamAutodetect(pojo)
-                        .map(element->adapt(element, bookmarking))
+                        .map(element->adapt(element, entityAdaptingMode))
                         .collect(Can.toCan()));
     }
 
@@ -190,7 +190,7 @@ public interface ObjectManager {
                 || pojo.getClass().equals(proposedSpec.getCorrespondingClass()))
             // if actual type matches spec's, we assume, that we don't need to reload,
             // so this is a shortcut for performance reasons
-            ? autoBookmarked(proposedSpec, pojo, EntityAdaptingMode.MEMOIZE_BOOKMARK)
+            ? managedObjectFor(proposedSpec, pojo, EntityAdaptingMode.MEMOIZE_BOOKMARK)
             // fallback, ignoring proposedSpec
             : adapt(pojo);
         return adapter;
@@ -198,13 +198,12 @@ public interface ObjectManager {
 
     // -- HELPER
 
-    private static ManagedObject autoBookmarked(
+    private static ManagedObject managedObjectFor(
             final ObjectSpecification spec,
             final Object pojo,
-            final EntityAdaptingMode bookmarking) {
+            final EntityAdaptingMode entityAdaptingMode) {
 
-        if(bookmarking.isMemoize()
-                && spec.isEntity()) {
+        if(entityAdaptingMode.isMemoize() && spec.isEntity()) {
             val entityFacet = spec.getFacet(EntityFacet.class);
             val state = entityFacet.getEntityState(pojo);
             if(state.isAttached()) {
diff --git a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/services/objectlifecycle/ObjectLifecyclePublisher.java b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/services/objectlifecycle/ObjectLifecyclePublisher.java
index 8d7adce84f..006f1a0b16 100644
--- a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/services/objectlifecycle/ObjectLifecyclePublisher.java
+++ b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/services/objectlifecycle/ObjectLifecyclePublisher.java
@@ -20,6 +20,8 @@ package org.apache.isis.core.metamodel.services.objectlifecycle;
 
 import java.sql.Timestamp;
 
+import org.springframework.lang.Nullable;
+
 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;
@@ -35,109 +37,99 @@ import org.apache.isis.core.metamodel.spec.feature.MixedIn;
 import lombok.NonNull;
 
 /**
- * Responsible for collecting, then immediately publishing changes to domain objects,
- * that is,
- * notify publishing subscribers and call the various persistence call-back facets.
+ * Responsible for collecting and then passing along changes (to the EntityChangeTracker, in persistence commons) so
+ * that they can be published; and is responsible for calling the various persistence call-back facets.
  *
  * @since 2.0 {index}
  */
 public interface ObjectLifecyclePublisher {
 
     /**
-     * Independent of the persistence stack, only triggered by {@link FactoryService}
-     * and internal {@link ObjectManager}.
+     * Independent of the persistence stack, called when an object has been created in-memory, for example by
+     * {@link FactoryService} and internal {@link ObjectManager}.
+     *
+     * <p>
+     *     Default implementation fires off callback/lifecycle events.
+     * </p>
+     *
      * @param domainObject - an entity or view-model
      */
     void onPostCreate(ManagedObject domainObject);
 
+    /**
+     * Called by both JPA and JDO, just after an object is retrieved from the database.
+     *
+     * <p>
+     *     Default implementation calls <code>EntityChangeTracker#recognizeLoaded(ManagedObject)</code> and
+     *     fires off callback/lifecycle events.
+     * </p>
+     *
+     * @param entity
+     */
     void onPostLoad(ManagedObject entity);
 
+    /**
+     * Called by both JPA and JDO, just before an entity is inserted into the database.
+     *
+     * <p>
+     *     Default implementation fires callbacks (including emitting the <code>PreStoreEvent</code>, eg as subscribed)
+     *     by the <code>TimestampService</code>.
+     * </p>
+     *
+     * @param entity
+     */
     void onPrePersist(ManagedObject entity);
 
+    /**
+     * Called by both JPA and JDO, just after an entity has been inserted into the database.
+     *
+     * <p>
+     *     Default implementation fires callbacks and enlists the entity within <code>EntityChangeTracker</code>
+     *     for create/persist.
+     * </p>
+     *
+     * @param entity
+     */
     void onPostPersist(ManagedObject entity);
 
-    void onPreUpdate(ManagedObject entity, Can<PropertyChangeRecord> changeRecords);
+    /**
+     * Called by both JPA and JDO (though JDO does <i>not</i> provide any changeRecords).
+     *
+     * <p>
+     *     Default implementation fires callbacks and enlists the entity within <code>EntityChangeTracker</code>
+     *     for update.
+     * </p>
+     *
+     * @param entity
+     * @param changeRecords - optional parameter to provide the pre-computed {@link PropertyChangeRecord}s from the ORM.  JPA does this, JDO does not.
+     */
+    void onPreUpdate(ManagedObject entity, @Nullable Can<PropertyChangeRecord> changeRecords);
 
+    /**
+     * Called by both JPA and JDO, after an existing entity has been updated.
+     *
+     * <p>
+     *     Default implementation fires callbacks.
+     * </p>
+     *
+     * @param entity
+     */
     void onPostUpdate(ManagedObject entity);
 
+    /**
+     * Called by both JPA and JDO, just beforean entity is deleted from the database.
+     *
+     * <p>
+     *     Default implementation fires callbacks and enlists the entity within <code>EntityChangeTracker</code>
+     *     for delete/remove.
+     * </p>
+     *
+     * @param entity
+     */
     void onPreRemove(ManagedObject entity);
 
     //void onPostRemove(ManagedObject entity);
 
-    // -- PUBLISHING PAYLOAD FACTORIES
-
-    static HasEnlistedEntityPropertyChanges publishingPayloadForCreation(
-            final @NonNull ManagedObject entity) {
-
-        return (timestamp, user, txId) -> entityPropertyChangesForCreation(timestamp, user, txId, entity);
-    }
-
-    private static Can<EntityPropertyChange> entityPropertyChangesForCreation(Timestamp timestamp, String user, TransactionId txId, ManagedObject entity) {
-        return propertyChangeRecordsForCreation(entity).stream()
-                .map(pcr -> pcr.toEntityPropertyChange(timestamp, user, txId))
-                .collect(Can.toCan());
-    }
-
-    static Can<PropertyChangeRecord> propertyChangeRecordsForCreation(ManagedObject entity) {
-        return entity
-                .getSpecification()
-                .streamProperties(MixedIn.EXCLUDED)
-                .filter(property -> EntityChangePublishingFacet.isPublishingEnabled(entity.getSpecification()))
-                .filter(property -> !EntityPropertyChangePublishingPolicyFacet.isExcludedFromPublishing(property))
-                .map(property ->
-                        PropertyChangeRecord
-                                .of(
-                                        entity,
-                                        property,
-                                        PreAndPostValue
-                                                .pre(PropertyValuePlaceholder.NEW)
-                                                .withPost(ManagedObjects.UnwrapUtil.single(property.get(entity, InteractionInitiatedBy.FRAMEWORK)))))
-                .collect(Can.toCan());
-    }
-
-    static HasEnlistedEntityPropertyChanges publishingPayloadForDeletion(
-            final @NonNull ManagedObject entity) {
-
-        return (timestamp, user, txId) -> entityPropertyChangesForDeletion(timestamp, user, txId, entity);
-
-    }
-
-    private static Can<EntityPropertyChange> entityPropertyChangesForDeletion(Timestamp timestamp, String user, TransactionId txId, ManagedObject entity) {
-        return propertyChangeRecordsForDeletion(entity).stream()
-                .map(pcr -> pcr.toEntityPropertyChange(timestamp, user, txId))
-                .collect(Can.toCan());
-    }
-
-    static Can<PropertyChangeRecord> propertyChangeRecordsForDeletion(ManagedObject entity) {
-        return entity
-                .getSpecification()
-                .streamProperties(MixedIn.EXCLUDED)
-                .filter(property -> EntityChangePublishingFacet.isPublishingEnabled(entity.getSpecification()))
-                .filter(property -> !EntityPropertyChangePublishingPolicyFacet.isExcludedFromPublishing(property))
-                .map(property ->
-                        PropertyChangeRecord
-                                .of(
-                                        entity,
-                                        property,
-                                        PreAndPostValue
-                                                .pre(ManagedObjects.UnwrapUtil.single(property.get(entity, InteractionInitiatedBy.FRAMEWORK)))
-                                                .withPost(PropertyValuePlaceholder.DELETED))
-                )
-                .collect(Can.toCan());
-    }
-
-    static HasEnlistedEntityPropertyChanges publishingPayloadForUpdate(final Can<PropertyChangeRecord> changeRecords) {
-        return (timestamp, user, txId) -> entityPropertyChangesForUpdate(timestamp, user, txId, changeRecords);
-    }
-
-    private static Can<EntityPropertyChange> entityPropertyChangesForUpdate(Timestamp timestamp, String user, TransactionId txId, Can<PropertyChangeRecord> changeRecords) {
-        return propertyChangeRecordsForUpdate(changeRecords)
-                .map(pcr -> pcr.toEntityPropertyChange(timestamp, user, txId));
-    }
-
-    static Can<PropertyChangeRecord> propertyChangeRecordsForUpdate(Can<PropertyChangeRecord> changeRecords) {
-        return changeRecords;
-    }
 
 
 }
diff --git a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/services/objectlifecycle/PropertyChangeRecord.java b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/services/objectlifecycle/PropertyChangeRecord.java
index 59104cb018..5afa259bb5 100644
--- a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/services/objectlifecycle/PropertyChangeRecord.java
+++ b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/services/objectlifecycle/PropertyChangeRecord.java
@@ -26,7 +26,7 @@ import org.apache.isis.applib.services.xactn.TransactionId;
 import org.apache.isis.core.metamodel.consent.InteractionInitiatedBy;
 import org.apache.isis.core.metamodel.spec.ManagedObject;
 import org.apache.isis.core.metamodel.spec.ManagedObjects;
-import org.apache.isis.core.metamodel.spec.feature.ObjectAssociation;
+import org.apache.isis.core.metamodel.spec.feature.OneToOneAssociation;
 
 import lombok.EqualsAndHashCode;
 import lombok.Getter;
@@ -34,42 +34,37 @@ import lombok.NonNull;
 import lombok.ToString;
 import lombok.val;
 
-@EqualsAndHashCode(of = {"bookmarkStr", "propertyId"})
-@ToString(of = {"bookmarkStr", "propertyId"})
+@EqualsAndHashCode(of = {"id"})
+@ToString(of = {"id"})
 public final class PropertyChangeRecord {
 
-    @Getter private final ManagedObject entity;
-    @Getter private final ObjectAssociation property;
-    @Getter private final Bookmark bookmark;
-    @Getter private final String propertyId;
+    @Getter
+    private final PropertyChangeRecordId id;
+
+    public ManagedObject getEntity() {return id.getEntity();}
+    public OneToOneAssociation getProperty() {return id.getProperty();}
+    public Bookmark getBookmark() {return id.getBookmark();}
+    public String getPropertyId() {return id.getPropertyId();}
+
     @Getter private PreAndPostValue preAndPostValue;
 
-    private final String bookmarkStr;
 
-    public static PropertyChangeRecord of(
-            final @NonNull ManagedObject entity,
-            final @NonNull ObjectAssociation property) {
-        return new PropertyChangeRecord(entity, property, null);
+    public static @NonNull PropertyChangeRecord of(
+            final @NonNull PropertyChangeRecordId id) {
+        return new PropertyChangeRecord(id, PreAndPostValue.pre(PropertyValuePlaceholder.NEW));
     }
 
     public static PropertyChangeRecord of(
-            final @NonNull ManagedObject entity,
-            final @NonNull ObjectAssociation property,
+            final @NonNull PropertyChangeRecordId id,
             final @NonNull PreAndPostValue preAndPostValue) {
-        return new PropertyChangeRecord(entity, property, preAndPostValue);
+        return new PropertyChangeRecord(id, preAndPostValue);
     }
 
     private PropertyChangeRecord(
-            final ManagedObject entity,
-            final ObjectAssociation property,
+            final @NonNull PropertyChangeRecordId id,
             final PreAndPostValue preAndPostValue) {
-        this.entity = entity;
-        this.property = property;
-        this.propertyId = property.getId();
-
-        this.bookmark = ManagedObjects.bookmarkElseFail(entity);
-        this.bookmarkStr = bookmark.toString();
 
+        this.id = id;
         this.preAndPostValue = preAndPostValue;
     }
 
@@ -79,15 +74,15 @@ public final class PropertyChangeRecord {
         return target.getLogicalTypeName() + "#" + propertyId;
     }
 
-    public void setPreValue(final Object pre) {
-        preAndPostValue = PreAndPostValue.pre(pre);
+    public void updatePreValueAsNew() {
+        preAndPostValue = PreAndPostValue.pre(PropertyValuePlaceholder.NEW);
     }
 
-    public void updatePreValue() {
-        setPreValue(getPropertyValue());
+    public void updatePreValueWithCurrent() {
+        preAndPostValue = PreAndPostValue.pre(getPropertyValue());
     }
 
-    public void updatePostValueAsNonDeleted() {
+    public void updatePostValueWithCurrent() {
         preAndPostValue = preAndPostValue.withPost(getPropertyValue());
     }
 
@@ -95,6 +90,7 @@ public final class PropertyChangeRecord {
         preAndPostValue = preAndPostValue.withPost(PropertyValuePlaceholder.DELETED);
     }
 
+
     // -- UTILITY
 
     public EntityPropertyChange toEntityPropertyChange(
@@ -120,7 +116,7 @@ public final class PropertyChangeRecord {
     // -- HELPER
 
     private Object getPropertyValue() {
-        val referencedAdapter = property.get(entity, InteractionInitiatedBy.FRAMEWORK);
+        val referencedAdapter = getProperty().get(getEntity(), InteractionInitiatedBy.FRAMEWORK);
         return ManagedObjects.UnwrapUtil.single(referencedAdapter);
     }
 
diff --git a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/services/objectlifecycle/PropertyChangeRecordId.java b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/services/objectlifecycle/PropertyChangeRecordId.java
new file mode 100644
index 0000000000..57674953be
--- /dev/null
+++ b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/services/objectlifecycle/PropertyChangeRecordId.java
@@ -0,0 +1,70 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you under the Apache License, Version 2.0 (the
+ *  "License"); you may not use this file except in compliance
+ *  with the License.  You may obtain a copy of the License at
+ *
+ *        http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing,
+ *  software distributed under the License is distributed on an
+ *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ *  KIND, either express or implied.  See the License for the
+ *  specific language governing permissions and limitations
+ *  under the License.
+ */
+package org.apache.isis.core.metamodel.services.objectlifecycle;
+
+import java.sql.Timestamp;
+
+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.consent.InteractionInitiatedBy;
+import org.apache.isis.core.metamodel.spec.ManagedObject;
+import org.apache.isis.core.metamodel.spec.ManagedObjects;
+import org.apache.isis.core.metamodel.spec.feature.ObjectAssociation;
+import org.apache.isis.core.metamodel.spec.feature.OneToOneAssociation;
+
+import lombok.EqualsAndHashCode;
+import lombok.Getter;
+import lombok.NonNull;
+import lombok.ToString;
+import lombok.val;
+
+@EqualsAndHashCode(of = {"bookmarkStr", "propertyId"})
+@ToString(of = {"bookmarkStr", "propertyId"})
+public final class PropertyChangeRecordId {
+
+    @Getter private final String bookmarkStr;
+    @Getter private final String propertyId;
+
+    @Getter private final ManagedObject entity;
+    @Getter private final Bookmark bookmark;
+    @Getter private OneToOneAssociation property;
+
+    public static PropertyChangeRecordId of(
+            final @NonNull ManagedObject entity,
+            final @NonNull OneToOneAssociation property) {
+        return new PropertyChangeRecordId(entity, property);
+    }
+    private PropertyChangeRecordId(
+            final ManagedObject entity,
+            final OneToOneAssociation property) {
+
+        // these exposed as a convenience
+        this.entity = entity;
+        this.property = property;
+        this.bookmark = ManagedObjects.bookmarkElseFail(entity);
+
+        // these are the key
+        this.bookmarkStr = bookmark.toString();
+        this.propertyId = property.getId();
+
+    }
+
+}
+
diff --git a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/services/objectlifecycle/PropertyValuePlaceholder.java b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/services/objectlifecycle/PropertyValuePlaceholder.java
index 7aebc666d2..7f03f3ecf9 100644
--- a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/services/objectlifecycle/PropertyValuePlaceholder.java
+++ b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/services/objectlifecycle/PropertyValuePlaceholder.java
@@ -25,6 +25,7 @@ package org.apache.isis.core.metamodel.services.objectlifecycle;
  */
 public enum PropertyValuePlaceholder {
 
+    UNKNOWN,
     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 d974c527a4..2552cec424 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
@@ -52,6 +52,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.LifecycleCallbackNotifier;
 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;
@@ -108,6 +109,7 @@ import org.apache.isis.core.runtimeservices.xmlsnapshot.XmlSnapshotServiceDefaul
         ObjectIconServiceDefault.class,
         ObjectLifecyclePublisherDefault.class,
         ObjectMementoServiceDefault.class,
+        LifecycleCallbackNotifier.class,
         SchemaValueMarshallerDefault.class,
         ScratchpadDefault.class,
         SerializingAdapterDefault.class,
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 076ff42315..83e4cda4e2 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
@@ -34,6 +34,7 @@ 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.runtimeservices.IsisModuleCoreRuntimeServices;
+import org.apache.isis.core.security.util.XrayUtil;
 import org.apache.isis.core.transaction.changetracking.EntityPropertyChangePublisher;
 
 import org.springframework.beans.factory.annotation.Qualifier;
@@ -44,6 +45,8 @@ import javax.annotation.PostConstruct;
 import javax.annotation.Priority;
 import javax.inject.Inject;
 import javax.inject.Named;
+import javax.inject.Provider;
+
 import java.util.List;
 
 @Service
@@ -59,6 +62,7 @@ public class EntityPropertyChangePublisherDefault implements EntityPropertyChang
     private final ClockService clockService;
     private final TransactionService transactionService;
     private final InteractionLayerTracker iaTracker;
+    private final Provider<HasEnlistedEntityPropertyChanges> hasEnlistedEntityPropertyChangesProvider;
 
     private Can<EntityPropertyChangeSubscriber> enabledSubscribers = Can.empty();
 
@@ -68,48 +72,50 @@ public class EntityPropertyChangePublisherDefault implements EntityPropertyChang
                 .filter(HasEnabling::isEnabled);
     }
 
-    @Override
-    public void publishChangedProperties(
-            final HasEnlistedEntityPropertyChanges hasEnlistedEntityPropertyChanges) {
-
-        transactionService.flushTransaction();
-        val payload = getPayload(hasEnlistedEntityPropertyChanges);
-        val xrayHandle = _Xray.enterEntityPropertyChangePublishing(
-                iaTracker,
-                payload,
-                enabledSubscribers,
-                ()->getCannotPublishReason(payload)
-                );
-
-        payload.forEach(propertyChange->{
-            for (val subscriber : enabledSubscribers) {
-                subscriber.onChanging(propertyChange);
-            }
-        });
-
-        _Xray.exitPublishing(xrayHandle);
+    private HasEnlistedEntityPropertyChanges getHasEnlistedEntityPropertyChanges() {
+        return hasEnlistedEntityPropertyChangesProvider.get();
     }
 
-    // -- HELPER
+    @Override
+    public void publishChangedProperties() {
 
-    private Can<EntityPropertyChange> getPayload(
-            HasEnlistedEntityPropertyChanges hasEnlistedEntityPropertyChanges) {
+        transactionService.flushTransaction();
 
         if(enabledSubscribers.isEmpty()) {
-            return Can.empty();
+            return;
         }
 
         val currentTime = clockService.getClock().nowAsJavaSqlTimestamp();
         val currentUser = userService.currentUserNameElseNobody();
-        val currentTransactionId = transactionService.currentTransactionId()
-                .orElse(TransactionId.empty());
+        val currentTransactionId = transactionService.currentTransactionId().orElse(TransactionId.empty());
 
-        return hasEnlistedEntityPropertyChanges.getPropertyChanges(
+        val propertyChanges = getHasEnlistedEntityPropertyChanges().getPropertyChanges(
                 currentTime,
                 currentUser,
                 currentTransactionId);
+
+        XrayUtil.SequenceHandle xrayHandle = null;
+        try {
+            xrayHandle = _Xray.enterEntityPropertyChangePublishing(
+                    iaTracker,
+                    propertyChanges,
+                    enabledSubscribers,
+                    () -> getCannotPublishReason(propertyChanges)
+            );
+
+            propertyChanges.forEach(propertyChange->{
+                for (val subscriber : enabledSubscribers) {
+                    subscriber.onChanging(propertyChange);
+                }
+            });
+        } finally {
+            _Xray.exitPublishing(xrayHandle);
+        }
     }
 
+
+    // -- HELPER
+
     // x-ray support
     private @Nullable String getCannotPublishReason(final @NonNull Can<EntityPropertyChange> payload) {
         return enabledSubscribers.isEmpty()
diff --git a/core/runtimeservices/src/main/java/org/apache/isis/core/runtimeservices/publish/LifecycleCallbackNotifier.java b/core/runtimeservices/src/main/java/org/apache/isis/core/runtimeservices/publish/LifecycleCallbackNotifier.java
new file mode 100644
index 0000000000..d6a941c67e
--- /dev/null
+++ b/core/runtimeservices/src/main/java/org/apache/isis/core/runtimeservices/publish/LifecycleCallbackNotifier.java
@@ -0,0 +1,158 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you under the Apache License, Version 2.0 (the
+ *  "License"); you may not use this file except in compliance
+ *  with the License.  You may obtain a copy of the License at
+ *
+ *        http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing,
+ *  software distributed under the License is distributed on an
+ *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ *  KIND, either express or implied.  See the License for the
+ *  specific language governing permissions and limitations
+ *  under the License.
+ */
+package org.apache.isis.core.runtimeservices.publish;
+
+import java.util.LinkedHashSet;
+import java.util.Optional;
+import java.util.Set;
+import java.util.function.Consumer;
+
+import javax.annotation.Priority;
+import javax.inject.Inject;
+import javax.inject.Named;
+
+import org.springframework.beans.factory.annotation.Qualifier;
+import org.springframework.stereotype.Component;
+
+import org.apache.isis.applib.annotation.InteractionScope;
+import org.apache.isis.applib.annotation.PriorityPrecedence;
+import org.apache.isis.applib.services.bookmark.Bookmark;
+import org.apache.isis.applib.services.eventbus.EventBusService;
+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;
+import org.apache.isis.core.metamodel.facets.object.callbacks.PersistedLifecycleEventFacet;
+import org.apache.isis.core.metamodel.facets.object.callbacks.PersistingCallbackFacet;
+import org.apache.isis.core.metamodel.facets.object.callbacks.PersistingLifecycleEventFacet;
+import org.apache.isis.core.metamodel.facets.object.callbacks.RemovingCallbackFacet;
+import org.apache.isis.core.metamodel.facets.object.callbacks.RemovingLifecycleEventFacet;
+import org.apache.isis.core.metamodel.facets.object.callbacks.UpdatedCallbackFacet;
+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.spec.ManagedObject;
+import org.apache.isis.core.runtimeservices.IsisModuleCoreRuntimeServices;
+import org.apache.isis.core.transaction.changetracking.PersistenceCallbackHandlerAbstract;
+import org.apache.isis.core.transaction.changetracking.events.PostStoreEvent;
+import org.apache.isis.core.transaction.changetracking.events.PreStoreEvent;
+
+/**
+ * Calls lifecycle callbacks for entities, ensuring that any given entity is only ever called once.
+ * @since 2.0 {@index}
+ */
+@Component
+@Named(IsisModuleCoreRuntimeServices.NAMESPACE + ".LifecycleCallbackNotifier")
+@Priority(PriorityPrecedence.EARLY)
+@Qualifier("Default")
+@InteractionScope
+//@Log4j2
+public class LifecycleCallbackNotifier extends PersistenceCallbackHandlerAbstract {
+
+    private final Set<ManagedObject> postCreated = new LinkedHashSet<>();
+    private final Set<ManagedObject> postLoaded = new LinkedHashSet<>();
+    private final Set<ManagedObject> prePersisted = new LinkedHashSet<>();
+    private final Set<ManagedObject> postPersisted = new LinkedHashSet<>();
+    private final Set<ManagedObject> preUpdated = new LinkedHashSet<>();
+    private final Set<ManagedObject> postUpdated = new LinkedHashSet<>();
+    private final Set<ManagedObject> preRemoved = new LinkedHashSet<>();
+
+    @Inject
+    public LifecycleCallbackNotifier(EventBusService eventBusService) {
+        super(eventBusService);
+    }
+
+    public void postCreate(ManagedObject entity) {
+        notify(entity,
+                postCreated,
+                e -> {
+                    CallbackFacet.callCallback(entity, CreatedCallbackFacet.class);
+                    postLifecycleEventIfRequired(entity, CreatedLifecycleEventFacet.class);
+                });
+    }
+
+    public void postLoad(ManagedObject entity) {
+        notify(entity,
+                postLoaded,
+                e -> {
+                    CallbackFacet.callCallback(entity, LoadedCallbackFacet.class);
+                    postLifecycleEventIfRequired(entity, LoadedLifecycleEventFacet.class);
+                });
+    }
+
+    public void prePersist(ManagedObject entity) {
+        notify(entity,
+                prePersisted,
+                e -> {
+                    eventBusService.post(PreStoreEvent.of(entity.getPojo()));
+                    CallbackFacet.callCallback(entity, PersistingCallbackFacet.class);
+                    postLifecycleEventIfRequired(entity, PersistingLifecycleEventFacet.class);
+                });
+    }
+
+    public void postPersist(ManagedObject entity) {
+        notify(entity,
+                postPersisted,
+                e -> {
+                    eventBusService.post(PostStoreEvent.of(entity.getPojo()));
+                    CallbackFacet.callCallback(entity, PersistedCallbackFacet.class);
+                    postLifecycleEventIfRequired(entity, PersistedLifecycleEventFacet.class);
+                });
+    }
+
+    public void preUpdate(ManagedObject entity) {
+        notify(entity,
+                preUpdated,
+                e -> {
+                    eventBusService.post(PreStoreEvent.of(entity.getPojo()));
+                    CallbackFacet.callCallback(entity, UpdatingCallbackFacet.class);
+                    postLifecycleEventIfRequired(entity, UpdatingLifecycleEventFacet.class);
+                });
+    }
+
+    public void postUpdate(ManagedObject entity) {
+        notify(entity,
+                postUpdated,
+                e -> {
+                    CallbackFacet.callCallback(entity, UpdatedCallbackFacet.class);
+                    postLifecycleEventIfRequired(entity, UpdatedLifecycleEventFacet.class);
+                });
+    }
+
+    public void preRemove(ManagedObject entity) {
+        notify(entity,
+                preRemoved,
+                e -> {
+                    CallbackFacet.callCallback(entity, RemovingCallbackFacet.class);
+                    postLifecycleEventIfRequired(entity, RemovingLifecycleEventFacet.class);
+                });
+    }
+
+    private static void notify(ManagedObject entity, Set<ManagedObject> notified, Consumer<ManagedObject> notify) {
+        Optional.of(entity)
+                .filter(x -> !notified.contains(x))
+                .ifPresent(x -> {
+                    notify.accept(entity);
+                    notified.add(x);
+                });
+    }
+
+}
diff --git a/core/runtimeservices/src/main/java/org/apache/isis/core/runtimeservices/publish/ObjectLifecyclePublisherDefault.java b/core/runtimeservices/src/main/java/org/apache/isis/core/runtimeservices/publish/ObjectLifecyclePublisherDefault.java
index cf3aa6f0f6..26feea1bfc 100644
--- a/core/runtimeservices/src/main/java/org/apache/isis/core/runtimeservices/publish/ObjectLifecyclePublisherDefault.java
+++ b/core/runtimeservices/src/main/java/org/apache/isis/core/runtimeservices/publish/ObjectLifecyclePublisherDefault.java
@@ -24,33 +24,27 @@ import javax.inject.Named;
 import javax.inject.Provider;
 
 import org.springframework.beans.factory.annotation.Qualifier;
+import org.springframework.lang.Nullable;
 import org.springframework.stereotype.Service;
 
 import org.apache.isis.applib.annotation.PriorityPrecedence;
-import org.apache.isis.applib.services.eventbus.EventBusService;
 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;
 import org.apache.isis.core.metamodel.facets.object.callbacks.PersistedLifecycleEventFacet;
-import org.apache.isis.core.metamodel.facets.object.callbacks.PersistingCallbackFacet;
-import org.apache.isis.core.metamodel.facets.object.callbacks.PersistingLifecycleEventFacet;
 import org.apache.isis.core.metamodel.facets.object.callbacks.RemovingCallbackFacet;
 import org.apache.isis.core.metamodel.facets.object.callbacks.RemovingLifecycleEventFacet;
 import org.apache.isis.core.metamodel.facets.object.callbacks.UpdatedCallbackFacet;
 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.facets.object.publish.entitychange.EntityChangePublishingFacet;
 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.runtimeservices.IsisModuleCoreRuntimeServices;
 import org.apache.isis.core.transaction.changetracking.EntityChangeTracker;
-import org.apache.isis.core.transaction.changetracking.PersistenceCallbackHandlerAbstract;
+import org.apache.isis.core.transaction.changetracking.events.PostStoreEvent;
 
 /**
  * @see ObjectLifecyclePublisher
@@ -61,87 +55,68 @@ import org.apache.isis.core.transaction.changetracking.PersistenceCallbackHandle
 @Priority(PriorityPrecedence.EARLY)
 @Qualifier("Default")
 //@Log4j2
-public class ObjectLifecyclePublisherDefault
-extends PersistenceCallbackHandlerAbstract
-implements
-    ObjectLifecyclePublisher {
+public class ObjectLifecyclePublisherDefault implements ObjectLifecyclePublisher {
 
     private final Provider<EntityChangeTracker> entityChangeTrackerProvider;
+    private final Provider<LifecycleCallbackNotifier> lifecycleCallbackNotifierProvider;
 
     @Inject
     public ObjectLifecyclePublisherDefault(
-            final EventBusService eventBusService,
-            final Provider<EntityChangeTracker> entityChangeTrackerProvider) {
-        super(eventBusService);
+            final Provider<EntityChangeTracker> entityChangeTrackerProvider,
+            final Provider<LifecycleCallbackNotifier> lifecycleCallbackNotifierProvider) {
         this.entityChangeTrackerProvider = entityChangeTrackerProvider;
+        this.lifecycleCallbackNotifierProvider = lifecycleCallbackNotifierProvider;
     }
 
     EntityChangeTracker entityChangeTracker() {
         return entityChangeTrackerProvider.get();
     }
-
-    @Override
-    public void onPostCreate(final ManagedObject domainObject) {
-        CallbackFacet.callCallback(domainObject, CreatedCallbackFacet.class);
-        postLifecycleEventIfRequired(domainObject, CreatedLifecycleEventFacet.class);
+    LifecycleCallbackNotifier lifecycleCallbackNotifier() {
+        return lifecycleCallbackNotifierProvider.get();
     }
 
     @Override
-    public void onPrePersist(final ManagedObject entity) {
-        CallbackFacet.callCallback(entity, PersistingCallbackFacet.class);
-        postLifecycleEventIfRequired(entity, PersistingLifecycleEventFacet.class);
+    public void onPostCreate(final ManagedObject entity) {
+        lifecycleCallbackNotifier().postCreate(entity);
     }
 
     @Override
-    public void onPreUpdate(
-            final ManagedObject entity,
-            final Can<PropertyChangeRecord> changeRecords) {
-
-        if(changeRecords.isEmpty()) {
-            return;
-        }
-
-        CallbackFacet.callCallback(entity, UpdatingCallbackFacet.class);
-        postLifecycleEventIfRequired(entity, UpdatingLifecycleEventFacet.class);
-
-        if(EntityChangePublishingFacet.isPublishingEnabled(entity.getSpecification())) {
-            entityChangeTracker().enlistUpdating(entity, changeRecords);
-        }
-
+    public void onPostLoad(final ManagedObject entity) {
+        entityChangeTracker().incrementLoaded(entity);
+        lifecycleCallbackNotifier().postLoad(entity);
     }
 
     @Override
-    public void onPreRemove(final ManagedObject entity) {
-        CallbackFacet.callCallback(entity, RemovingCallbackFacet.class);
-        postLifecycleEventIfRequired(entity, RemovingLifecycleEventFacet.class);
-
-        if(EntityChangePublishingFacet.isPublishingEnabled(entity.getSpecification())) {
-            entityChangeTracker().enlistDeleting(entity, ObjectLifecyclePublisher
-                    .propertyChangeRecordsForDeletion(entity));
-        }
+    public void onPrePersist(final ManagedObject entity) {
+        lifecycleCallbackNotifier().prePersist(entity);
     }
 
     @Override
     public void onPostPersist(final ManagedObject entity) {
-        CallbackFacet.callCallback(entity, PersistedCallbackFacet.class);
-        postLifecycleEventIfRequired(entity, PersistedLifecycleEventFacet.class);
+        entityChangeTracker().enlistCreated(entity);
+        lifecycleCallbackNotifier().postPersist(entity);
+    }
 
-        if(EntityChangePublishingFacet.isPublishingEnabled(entity.getSpecification())) {
-            entityChangeTracker().enlistCreated(entity, ObjectLifecyclePublisher
-                    .propertyChangeRecordsForCreation(entity));
-        }
+    @Override
+    public void onPreUpdate(
+            final ManagedObject entity,
+            @Nullable final Can<PropertyChangeRecord> changeRecords) {
+        entityChangeTracker().enlistUpdating(entity, changeRecords);
+        lifecycleCallbackNotifier().preUpdate(entity);
     }
 
+
     @Override
     public void onPostUpdate(final ManagedObject entity) {
-        CallbackFacet.callCallback(entity, UpdatedCallbackFacet.class);
-        postLifecycleEventIfRequired(entity, UpdatedLifecycleEventFacet.class);
+        lifecycleCallbackNotifier().postUpdate(entity);
     }
 
+
     @Override
-    public void onPostLoad(final ManagedObject entity) {
-        CallbackFacet.callCallback(entity, LoadedCallbackFacet.class);
-        postLifecycleEventIfRequired(entity, LoadedLifecycleEventFacet.class);
+    public void onPreRemove(final ManagedObject entity) {
+        entityChangeTracker().enlistDeleting(entity);
+        lifecycleCallbackNotifier().preRemove(entity);
     }
 
+
 }
diff --git a/core/transaction/src/main/java/org/apache/isis/core/transaction/changetracking/EntityChangeTracker.java b/core/transaction/src/main/java/org/apache/isis/core/transaction/changetracking/EntityChangeTracker.java
index 45acfa6121..eaf09281b4 100644
--- a/core/transaction/src/main/java/org/apache/isis/core/transaction/changetracking/EntityChangeTracker.java
+++ b/core/transaction/src/main/java/org/apache/isis/core/transaction/changetracking/EntityChangeTracker.java
@@ -18,6 +18,8 @@
  */
 package org.apache.isis.core.transaction.changetracking;
 
+import org.springframework.lang.Nullable;
+
 import org.apache.isis.commons.collections.Can;
 import org.apache.isis.core.metamodel.services.objectlifecycle.PropertyChangeRecord;
 import org.apache.isis.core.metamodel.spec.ManagedObject;
@@ -33,54 +35,50 @@ public interface EntityChangeTracker {
     /**
      * Publishing support: for object stores to enlist an object that has just been created,
      * capturing a dummy value <tt>'[NEW]'</tt> for the pre-modification value.
-     * <p>
-     * Fires the appropriate event and lifecycle callback: {@literal PERSISTED}
+     *
      * <p>
      * The post-modification values are captured when the transaction commits.
+     * </p>
      */
     void enlistCreated(ManagedObject entity);
 
-    void enlistCreated(ManagedObject entity, Can<PropertyChangeRecord> propertyChangeRecords);
-
-    /**
-     * Publishing support: for object stores to enlist an object that is about to be deleted,
-     * capturing the pre-deletion value of the properties of the {@link ManagedObject}.
-     * <p>
-     * Fires the appropriate event and lifecycle callback: {@literal REMOVING}
-     * <p>
-     * The post-modification values are captured  when the transaction commits.  In the case of deleted objects, a
-     * dummy value <tt>'[DELETED]'</tt> is used as the post-modification value.
-     */
-    void enlistDeleting(ManagedObject entity) ;
-
-    void enlistDeleting(ManagedObject entity, Can<PropertyChangeRecord> propertyChangeRecords);
-
     /**
      * Publishing support: for object stores to enlist an object that is about to be updated,
      * capturing the pre-modification values of the properties of the {@link ManagedObject}.
-     * <p>
-     * Fires the appropriate event and lifecycle callback: {@literal UPDATING}
+     *
      * <p>
      * The post-modification values are captured when the transaction commits.
+     *
+     * <p>
+     * Overload as an optimization for ORMs (specifically, JPA) where already have access to the changed records by
+     * accessing the ORM-specific data structures (<code>EntityManager</code>'s unit-of-work).
+     *
+     * </p>
+     *
+     * @param entity
+     * @param propertyChangeRecords - optional parameter (as a performance optimization) to provide the pre-computed {@link PropertyChangeRecord}s from the ORM.  JPA does this, JDO does not.
      */
-    void enlistUpdating(ManagedObject entity);
+    void enlistUpdating(ManagedObject entity, @Nullable Can<PropertyChangeRecord> propertyChangeRecords);
 
-    void enlistUpdating(ManagedObject entity, Can<PropertyChangeRecord> propertyChangeRecords);
 
     /**
-     * Fires the appropriate event and lifecycle callback: {@literal LOADED}
+     * Publishing support: for object stores to enlist an object that is about to be deleted,
+     * capturing the pre-deletion value of the properties of the {@link ManagedObject}.
+     *
+     * <p>
+     * The post-modification values are captured  when the transaction commits.  In the case of deleted objects, a
+     * dummy value <tt>'[DELETED]'</tt> is used as the post-modification value.
+     * </p>
      */
-    void recognizeLoaded(ManagedObject entity);
+    void enlistDeleting(ManagedObject entity) ;
 
     /**
-     * Fires the appropriate event and lifecycle callback: {@literal PERSISTING}
+     * Not strictly part of the concern of entity tracking, but allows the default implementation to also implement
+     * the {@link org.apache.isis.applib.services.metrics.MetricsService}.
      */
-    void recognizePersisting(ManagedObject entity);
+    void incrementLoaded(ManagedObject entity);
+
 
-    /**
-     * Fires the appropriate event and lifecycle callback: {@literal UPDATING}
-     */
-    void recognizeUpdating(ManagedObject entity);
 
 
 }
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 58ff0cff91..7627d74b55 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,7 +19,6 @@
 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.
@@ -35,9 +34,7 @@ public interface EntityPropertyChangePublisher {
      * a property of an entity has changed using the
      * {@link org.apache.isis.applib.services.publishing.spi.EntityPropertyChangeSubscriber#onChanging(EntityPropertyChange)}
      * callback.
-     *
-     * @param hasEnlistedEntityPropertyChanges
      */
-    void publishChangedProperties(HasEnlistedEntityPropertyChanges hasEnlistedEntityPropertyChanges);
+    void publishChangedProperties();
 
 }
diff --git a/persistence/commons/src/main/java/org/apache/isis/persistence/jpa/integration/changetracking/EntityChangeTrackerDefault.java b/persistence/commons/src/main/java/org/apache/isis/persistence/jpa/integration/changetracking/EntityChangeTrackerDefault.java
index 91cbbfeaa9..5a429a9f96 100644
--- a/persistence/commons/src/main/java/org/apache/isis/persistence/jpa/integration/changetracking/EntityChangeTrackerDefault.java
+++ b/persistence/commons/src/main/java/org/apache/isis/persistence/jpa/integration/changetracking/EntityChangeTrackerDefault.java
@@ -19,8 +19,8 @@
  */
 package org.apache.isis.persistence.jpa.integration.changetracking;
 
-import java.util.List;
 import java.util.Map;
+import java.util.Objects;
 import java.util.Optional;
 import java.util.Set;
 import java.util.concurrent.atomic.AtomicBoolean;
@@ -51,28 +51,14 @@ 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.commons.internal.base._Lazy;
-import org.apache.isis.commons.internal.collections._Lists;
 import org.apache.isis.commons.internal.collections._Maps;
 import org.apache.isis.commons.internal.collections._Sets;
 import org.apache.isis.commons.internal.exceptions._Exceptions;
-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;
-import org.apache.isis.core.metamodel.facets.object.callbacks.PersistingLifecycleEventFacet;
-import org.apache.isis.core.metamodel.facets.object.callbacks.RemovingCallbackFacet;
-import org.apache.isis.core.metamodel.facets.object.callbacks.RemovingLifecycleEventFacet;
-import org.apache.isis.core.metamodel.facets.object.callbacks.UpdatedCallbackFacet;
-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.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.PropertyChangeRecord;
-import org.apache.isis.core.metamodel.services.objectlifecycle.PropertyValuePlaceholder;
+import org.apache.isis.core.metamodel.services.objectlifecycle.PropertyChangeRecordId;
 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;
@@ -106,35 +92,15 @@ implements
     HasEnlistedEntityPropertyChanges,
     HasEnlistedEntityChanges {
 
-    /**
-     * If provided by the ORM.
-     */
-    private final List<PropertyChangeRecord> enlistedPropertyChangesOfCreated = _Lists.newArrayList();
-    /**
-     * If provided by the ORM.
-     */
-    private final List<PropertyChangeRecord> enlistedPropertyChangesOfUpdated = _Lists.newArrayList();
-    /**
-     * If provided by the ORM.
-     */
-    private final List<PropertyChangeRecord> enlistedPropertyChangesOfDeleted = _Lists.newArrayList();
 
     /**
-     * Contains initial change records having set the pre-values of every property of every object that was enlisted.
-     *
-     * <p>
-     *     ONLY USED IF THE ENLISTED PROPERTY CHANGES ({@link #enlistedPropertyChangesOfCreated}, {@link #enlistedPropertyChangesOfUpdated}, {@link #enlistedPropertyChangesOfDeleted}) were not provided already.
-     * </p>
+     * Contains a record for every objectId/propertyId that was changed.
      */
-    private final Map<String, PropertyChangeRecord> propertyChangeRecordsById = _Maps.newLinkedHashMap();
+    private final Map<PropertyChangeRecordId, PropertyChangeRecord> enlistedPropertyChangeRecordsById = _Maps.newLinkedHashMap();
 
     /**
      * Contains pre- and post- values of every property of every object that actually changed. A lazy snapshot,
      * triggered by internal call to {@link #snapshotPropertyChangeRecords()}.
-     *
-     * <p>
-     *     ONLY USED IF THE ENLISTED PROPERTY CHANGES ({@link #enlistedPropertyChangesOfCreated}, {@link #enlistedPropertyChangesOfUpdated}, {@link #enlistedPropertyChangesOfDeleted}) were not provided already.
-     * </p>
      */
     private final _Lazy<Set<PropertyChangeRecord>> entityPropertyChangeRecordsForPublishing
         = _Lazy.threadSafe(this::capturePostValuesAndDrain);
@@ -148,6 +114,10 @@ implements
     private final EntityChangesPublisher entityChangesPublisher;
     private final Provider<InteractionProvider> interactionProviderProvider;
 
+    private final LongAdder numberEntitiesLoaded = new LongAdder();
+    private final LongAdder entityChangeEventCount = new LongAdder();
+    private final AtomicBoolean persistentChangesEncountered = new AtomicBoolean();
+
     @Inject
     public EntityChangeTrackerDefault(
             final EntityPropertyChangePublisher entityPropertyChangePublisher,
@@ -160,70 +130,22 @@ implements
         this.interactionProviderProvider = interactionProviderProvider;
     }
 
-    private boolean isEnlisted(final @NonNull ManagedObject adapter) {
+    private boolean isEnlistedWrtChangeKind(final @NonNull ManagedObject adapter) {
         return ManagedObjects.bookmark(adapter)
         .map(changeKindByEnlistedAdapter::containsKey)
         .orElse(false);
     }
 
-    private void enlistCreatedInternal(final @NonNull ManagedObject adapter, @Nullable Can<PropertyChangeRecord> propertyChangeRecords) {
-        if(!isEntityEnabledForChangePublishing(adapter)) {
-            return;
-        }
-        enlistForChangeKindPublishing(adapter, EntityChangeKind.CREATE);
-        if (propertyChangeRecords != null) {
-            // provided by ORM
-            propertyChangeRecords.forEach(this.enlistedPropertyChangesOfCreated::add);
-        } else {
-            // home-grown approach
-            enlistForPreAndPostValuePublishing(adapter, record->record.setPreValue(PropertyValuePlaceholder.NEW));
-        }
-    }
-
-    private void enlistUpdatingInternal(final @NonNull ManagedObject entity, Can<PropertyChangeRecord> propertyChangeRecords) {
-        if(!isEntityEnabledForChangePublishing(entity)) {
-            return;
-        }
-        enlistForChangeKindPublishing(entity, EntityChangeKind.UPDATE);
-        if(propertyChangeRecords != null) {
-            // provided by ORM
-            propertyChangeRecords.forEach(this.enlistedPropertyChangesOfUpdated::add);
-        } else {
-            // home-grown approach
-            enlistForPreAndPostValuePublishing(entity, PropertyChangeRecord::updatePreValue);
-        }
-    }
-
-    private void enlistDeletingInternal(final @NonNull ManagedObject adapter, Can<PropertyChangeRecord> propertyChangeRecords) {
-        if(!isEntityEnabledForChangePublishing(adapter)) {
-            return;
-        }
-        final boolean enlisted = enlistForChangeKindPublishing(adapter, EntityChangeKind.DELETE);
-        if(enlisted) {
-            if (propertyChangeRecords != null) {
-                // provided by ORM
-                propertyChangeRecords.forEach(this.enlistedPropertyChangesOfDeleted::add);
-            } else {
-                // home-grown approach
-                enlistForPreAndPostValuePublishing(adapter, PropertyChangeRecord::updatePreValue);
-            }
-        }
-    }
-
     Set<PropertyChangeRecord> snapshotPropertyChangeRecords() {
         // this code path has side-effects, it locks the result for this transaction,
         // such that cannot enlist on top of it
         return entityPropertyChangeRecordsForPublishing.get();
     }
 
-    private boolean isOrmSuppliedChangeRecords() {
-        return !(enlistedPropertyChangesOfCreated.isEmpty() && enlistedPropertyChangesOfUpdated.isEmpty() && enlistedPropertyChangesOfDeleted.isEmpty());
-    }
-
-    private boolean isEntityEnabledForChangePublishing(final @NonNull ManagedObject adapter) {
+    private boolean isEntityExcludedForChangePublishing(ManagedObject entity) {
 
-        if(!EntityChangePublishingFacet.isPublishingEnabled(adapter.getSpecification())) {
-            return false; // ignore entities that are not enabled for entity change publishing
+        if(!EntityChangePublishingFacet.isPublishingEnabled(entity.getSpecification())) {
+            return true; // ignore entities that are not enabled for entity change publishing
         }
 
         if(entityPropertyChangeRecordsForPublishing.isMemoized()) {
@@ -231,10 +153,7 @@ implements
                     + "since changedObjectPropertiesRef was already prepared (memoized) for auditing.");
         }
 
-        entityChangeEventCount.increment();
-        enableCommandPublishing();
-
-        return true;
+        return false;
     }
 
     /**
@@ -254,20 +173,14 @@ implements
         _Xray.publish(this, interactionProviderProvider);
 
         log.debug("about to publish entity changes");
-        entityPropertyChangePublisher.publishChangedProperties(this);
+        entityPropertyChangePublisher.publishChangedProperties();
         entityChangesPublisher.publishChangingEntities(this);
     }
 
     private void postPublishing() {
         log.debug("purging entity change records");
 
-        // if ORM provided property change records ... as in JPA
-        this.enlistedPropertyChangesOfCreated.clear();
-        this.enlistedPropertyChangesOfUpdated.clear();
-        this.enlistedPropertyChangesOfDeleted.clear();
-
-        // if instead we had to infer ourselves (home-grown)... as in JDO
-        propertyChangeRecordsById.clear();
+        enlistedPropertyChangeRecordsById.clear();
         entityPropertyChangeRecordsForPublishing.clear();
 
         changeKindByEnlistedAdapter.clear();
@@ -309,12 +222,15 @@ implements
     // -- HELPER
 
     /**
-     * @return <code>true</code> if successfully enlisted, <code>false</code> if was already enlisted
+     * @return <code>true</code> if successfully enlisted, <code>false</code> if not (no longer) enlisted ... eg delete of an entity that was created earlier in the transaction
      */
     private boolean enlistForChangeKindPublishing(
             final @NonNull ManagedObject entity,
             final @NonNull EntityChangeKind changeKind) {
 
+        entityChangeEventCount.increment();
+        enableCommandPublishing();
+
         val bookmark = ManagedObjects.bookmarkElseFail(entity);
 
         val previousChangeKind = changeKindByEnlistedAdapter.get(bookmark);
@@ -345,23 +261,7 @@ implements
         case DELETE:
             return false;
         }
-        return previousChangeKind == null;
-    }
-
-    private void enlistForPreAndPostValuePublishing(
-            final ManagedObject entity,
-            final Consumer<PropertyChangeRecord> onNewChangeRecord) {
-
-        log.debug("enlist entity's property changes for publishing {}", entity);
-
-        entity.getSpecification().streamProperties(MixedIn.EXCLUDED)
-        .filter(property->!EntityPropertyChangePublishingPolicyFacet.isExcludedFromPublishing(property))
-        .map(property->PropertyChangeRecord.of(entity, property))
-        .filter(record->!propertyChangeRecordsById.containsKey(record.getPropertyId())) // already enlisted, so ignore
-        .forEach(record->{
-            onNewChangeRecord.accept(record);
-            propertyChangeRecordsById.put(record.getPropertyId(), record);
-        });
+        return false;
     }
 
     /**
@@ -370,34 +270,20 @@ implements
      */
     private Set<PropertyChangeRecord> capturePostValuesAndDrain() {
 
-        Set<PropertyChangeRecord> records;
-
-        if (isOrmSuppliedChangeRecords()) {
-            records = _Sets.newLinkedHashSet();
-            // TODO: might need to make this more sophisticated ?
-            records.addAll(enlistedPropertyChangesOfCreated);
-            records.addAll(enlistedPropertyChangesOfUpdated);
-            records.addAll(enlistedPropertyChangesOfDeleted);
-
-            enlistedPropertyChangesOfCreated.clear();
-            enlistedPropertyChangesOfUpdated.clear();
-            enlistedPropertyChangesOfDeleted.clear();
-        } else {
-            records = propertyChangeRecordsById.values().stream()
-                    // set post values, which have been left empty up to now
-                    .peek(rec->{
-                        // assuming this check correctly detects deleted entities (JDO)
-                        if(ManagedObjects.EntityUtil.isDetachedOrRemoved(rec.getEntity())) {
-                            rec.updatePostValueAsDeleted();
-                        } else {
-                            rec.updatePostValueAsNonDeleted();
-                        }
-                    })
-                    .filter(managedProperty->managedProperty.getPreAndPostValue().shouldPublish())
-                    .collect(_Sets.toUnmodifiable());
-
-            propertyChangeRecordsById.clear();
-        }
+        val records = enlistedPropertyChangeRecordsById.values().stream()
+                // set post values, which have been left empty up to now
+                .peek(rec -> {
+                    // assuming this check correctly detects deleted entities (JDO)
+                    if(ManagedObjects.EntityUtil.isDetachedOrRemoved(rec.getEntity())) {
+                        rec.updatePostValueAsDeleted();
+                    } else {
+                        rec.updatePostValueWithCurrent();
+                    }
+                })
+                .filter(managedProperty->managedProperty.getPreAndPostValue().shouldPublish())
+                .collect(_Sets.toUnmodifiable());
+
+        enlistedPropertyChangeRecordsById.clear();
 
         return records;
 
@@ -405,98 +291,119 @@ implements
 
     // side-effect free, used by XRay
     long countPotentialPropertyChangeRecords() {
-        return propertyChangeRecordsById.size();
+        return enlistedPropertyChangeRecordsById.size();
     }
 
-    // -- METRICS SERVICE
+    // -- ENTITY CHANGE TRACKING
 
     @Override
-    public int numberEntitiesLoaded() {
-        return Math.toIntExact(numberEntitiesLoaded.longValue());
+    public void enlistCreated(final ManagedObject entity) {
+
+        _Xray.enlistCreated(entity, interactionProviderProvider);
+
+        if (isEntityExcludedForChangePublishing(entity)) {
+            return;
+        }
+
+        log.debug("enlist entity's property changes for publishing {}", entity);
+        enlistForChangeKindPublishing(entity, EntityChangeKind.CREATE);
+
+        enlistForCreateOrUpdate(entity, PropertyChangeRecord::updatePreValueAsNew);
     }
 
     @Override
-    public int numberEntitiesDirtied() {
-        return changeKindByEnlistedAdapter.size();
-    }
+    public void enlistUpdating(
+            final ManagedObject entity,
+            @Nullable final Can<PropertyChangeRecord> ormPropertyChangeRecords) {
 
-    // -- ENTITY CHANGE TRACKING
+        _Xray.enlistUpdating(entity, interactionProviderProvider);
 
-    @Override
-    public void enlistCreated(final ManagedObject entity) {
-        enlistCreated(entity, null);
-    }
+        if (isEntityExcludedForChangePublishing(entity)) {
+            return;
+        }
 
+        // we call this come what may;
+        // additional properties may now have been changed, and the changeKind for publishing might also be modified
+        enlistForChangeKindPublishing(entity, EntityChangeKind.UPDATE);
 
-    @Override
-    public void enlistCreated(ManagedObject entity,  @Nullable final Can<PropertyChangeRecord> propertyChangeRecords) {
-        _Xray.enlistCreated(entity, interactionProviderProvider);
-        val hasAlreadyBeenEnlisted = isEnlisted(entity);
-        enlistCreatedInternal(entity, propertyChangeRecords);
+        if(ormPropertyChangeRecords != null) {
+            // provided by ORM
+            ormPropertyChangeRecords
+                    .stream()
+                    .filter(pcr -> !EntityPropertyChangePublishingPolicyFacet.isExcludedFromPublishing(pcr.getProperty()))
+                    .forEach(pcr -> this.enlistedPropertyChangeRecordsById.put(pcr.getId(), pcr)); // if already known, then we don't replace (keep first pre-value we know about)
+        } else {
+            // home-grown approach
+            log.debug("enlist entity's property changes for publishing {}", entity);
 
-        if(!hasAlreadyBeenEnlisted) {
-            CallbackFacet.callCallback(entity, PersistedCallbackFacet.class);
-            postLifecycleEventIfRequired(entity, PersistedLifecycleEventFacet.class);
+            enlistForCreateOrUpdate(entity, PropertyChangeRecord::updatePreValueWithCurrent);
         }
     }
 
-    @Override
-    public void enlistDeleting(final ManagedObject entity) {
-        enlistDeleting(entity, null);
+    private void enlistForCreateOrUpdate(ManagedObject entity, Consumer<PropertyChangeRecord> propertyChangeRecordConsumer) {
+        entity.getSpecification().streamProperties(MixedIn.EXCLUDED)
+                .filter(property->!EntityPropertyChangePublishingPolicyFacet.isExcludedFromPublishing(property))
+                .map(property -> PropertyChangeRecordId.of(entity, property))
+                .filter(pcrId -> ! enlistedPropertyChangeRecordsById.containsKey(pcrId)) // only if not previously seen
+                .map(pcrId -> enlistedPropertyChangeRecordsById.put(pcrId, PropertyChangeRecord.of(pcrId)))
+                .filter(Objects::nonNull)   // shouldn't happen, just keeping compiler happy
+                .forEach(propertyChangeRecordConsumer);
     }
 
+
     @Override
-    public void enlistDeleting(ManagedObject entity, final Can<PropertyChangeRecord> propertyChangeRecords) {
+    public void enlistDeleting(final ManagedObject entity) {
+
         _Xray.enlistDeleting(entity, interactionProviderProvider);
-        enlistDeletingInternal(entity, propertyChangeRecords);
-        CallbackFacet.callCallback(entity, RemovingCallbackFacet.class);
-        postLifecycleEventIfRequired(entity, RemovingLifecycleEventFacet.class);
-    }
 
-    @Override
-    public void enlistUpdating(final ManagedObject entity) {
-        enlistUpdating(entity, null);
-    }
+        if (isEntityExcludedForChangePublishing(entity)) {
+            return;
+        }
 
-    @Override
-    public void enlistUpdating(ManagedObject entity, final Can<PropertyChangeRecord> propertyChangeRecords) {
-        _Xray.enlistUpdating(entity, interactionProviderProvider);
-        val hasAlreadyBeenEnlisted = isEnlisted(entity);
-        // we call this come what may;
-        // additional properties may now have been changed, and the changeKind for publishing might also be modified
-        enlistUpdatingInternal(entity, propertyChangeRecords);
+        final boolean enlisted = enlistForChangeKindPublishing(entity, EntityChangeKind.DELETE);
+        if(enlisted) {
 
-        if(!hasAlreadyBeenEnlisted) {
-            // prevent an infinite loop... don't call the 'updating()' callback on this object if we have already done so
-            CallbackFacet.callCallback(entity, UpdatingCallbackFacet.class);
-            postLifecycleEventIfRequired(entity, UpdatingLifecycleEventFacet.class);
+            log.debug("enlist entity's property changes for publishing {}", entity);
+
+            entity.getSpecification()
+                    .streamProperties(MixedIn.EXCLUDED)
+                    .filter(property -> EntityChangePublishingFacet.isPublishingEnabled(entity.getSpecification()))
+                    .filter(property -> !EntityPropertyChangePublishingPolicyFacet.isExcludedFromPublishing(property))
+                    .map(property -> PropertyChangeRecordId.of(entity, property))
+                    .map(pcrId -> enlistedPropertyChangeRecordsById.computeIfAbsent(pcrId, PropertyChangeRecord::of))
+                    .forEach(pcr -> {
+                        pcr.updatePreValueWithCurrent();
+                        pcr.updatePostValueAsDeleted();
+                    });
         }
     }
 
+
+
+    /**
+     * Used only for the implementation of {@link MetricsService}.
+     * @param entity
+     */
     @Override
-    public void recognizeLoaded(final ManagedObject entity) {
+    public void incrementLoaded(final ManagedObject entity) {
         _Xray.recognizeLoaded(entity, interactionProviderProvider);
-        CallbackFacet.callCallback(entity, LoadedCallbackFacet.class);
-        postLifecycleEventIfRequired(entity, LoadedLifecycleEventFacet.class);
         numberEntitiesLoaded.increment();
     }
 
+
+    // -- METRICS SERVICE
+
     @Override
-    public void recognizePersisting(final ManagedObject entity) {
-        _Xray.recognizePersisting(entity, interactionProviderProvider);
-        CallbackFacet.callCallback(entity, PersistingCallbackFacet.class);
-        postLifecycleEventIfRequired(entity, PersistingLifecycleEventFacet.class);
+    public int numberEntitiesLoaded() {
+        return Math.toIntExact(numberEntitiesLoaded.longValue());
     }
 
     @Override
-    public void recognizeUpdating(final ManagedObject entity) {
-        _Xray.recognizeUpdating(entity, interactionProviderProvider);
-        CallbackFacet.callCallback(entity, UpdatedCallbackFacet.class);
-        postLifecycleEventIfRequired(entity, UpdatedLifecycleEventFacet.class);
+    public int numberEntitiesDirtied() {
+        return changeKindByEnlistedAdapter.size();
     }
 
-    private final LongAdder numberEntitiesLoaded = new LongAdder();
-    private final LongAdder entityChangeEventCount = new LongAdder();
-    private final AtomicBoolean persistentChangesEncountered = new AtomicBoolean();
+
+
 
 }
diff --git a/persistence/commons/src/main/java/org/apache/isis/persistence/jpa/integration/changetracking/_Xray.java b/persistence/commons/src/main/java/org/apache/isis/persistence/jpa/integration/changetracking/_Xray.java
index 7ee2ef86da..216a435458 100644
--- a/persistence/commons/src/main/java/org/apache/isis/persistence/jpa/integration/changetracking/_Xray.java
+++ b/persistence/commons/src/main/java/org/apache/isis/persistence/jpa/integration/changetracking/_Xray.java
@@ -91,17 +91,6 @@ final class _Xray {
         addSequence("recognizeLoaded", entity, interactionProviderProvider);
     }
 
-    public static void recognizePersisting(
-            final ManagedObject entity,
-            final Provider<InteractionProvider> interactionProviderProvider) {
-        addSequence("recognizePersisting", entity, interactionProviderProvider);
-    }
-
-    public static void recognizeUpdating(
-            final ManagedObject entity,
-            final Provider<InteractionProvider> interactionProviderProvider) {
-        addSequence("recognizeUpdating", entity, interactionProviderProvider);
-    }
 
     // -- HELPER
 
diff --git a/persistence/jdo/datanucleus/src/main/java/org/apache/isis/persistence/jdo/datanucleus/IsisModulePersistenceJdoDatanucleus.java b/persistence/jdo/datanucleus/src/main/java/org/apache/isis/persistence/jdo/datanucleus/IsisModulePersistenceJdoDatanucleus.java
index f3048871c2..e127e956a1 100644
--- a/persistence/jdo/datanucleus/src/main/java/org/apache/isis/persistence/jdo/datanucleus/IsisModulePersistenceJdoDatanucleus.java
+++ b/persistence/jdo/datanucleus/src/main/java/org/apache/isis/persistence/jdo/datanucleus/IsisModulePersistenceJdoDatanucleus.java
@@ -48,6 +48,7 @@ import org.apache.isis.core.config.IsisConfiguration;
 import org.apache.isis.core.config.beans.IsisBeanTypeRegistry;
 import org.apache.isis.core.config.beans.aoppatch.TransactionInterceptorFactory;
 import org.apache.isis.core.metamodel.context.MetaModelContext;
+import org.apache.isis.core.metamodel.services.objectlifecycle.ObjectLifecyclePublisher;
 import org.apache.isis.core.transaction.changetracking.EntityChangeTracker;
 import org.apache.isis.persistence.jdo.datanucleus.changetracking.JdoLifecycleListener;
 import org.apache.isis.persistence.jdo.datanucleus.config.DatanucleusSettings;
@@ -157,6 +158,7 @@ public class IsisModulePersistenceJdoDatanucleus {
             final DataSource dataSource,
             final MetaModelContext metaModelContext,
             final EventBusService eventBusService,
+            final ObjectLifecyclePublisher objectLifecyclePublisher,
             final Provider<EntityChangeTracker> entityChangeTrackerProvider,
             final IsisBeanTypeRegistry beanTypeRegistry,
             final DatanucleusSettings dnSettings) {
@@ -173,14 +175,14 @@ public class IsisModulePersistenceJdoDatanucleus {
                 val pu = createDefaultPersistenceUnit(beanTypeRegistry);
                 val pmf = new JDOPersistenceManagerFactory(pu, props);
                 pmf.setConnectionFactory(dataSource);
-                integrateWithApplicationLayer(metaModelContext, eventBusService, entityChangeTrackerProvider, pmf);
+                integrateWithApplicationLayer(metaModelContext, entityChangeTrackerProvider, objectLifecyclePublisher, pmf);
                 return pmf;
             }
             @Override
             protected PersistenceManagerFactory newPersistenceManagerFactory(final String name) {
                 val pmf = super.newPersistenceManagerFactory(name);
                 pmf.setConnectionFactory(dataSource); //might be too late, anyway, not sure if this is ever called
-                integrateWithApplicationLayer(metaModelContext, eventBusService, entityChangeTrackerProvider, pmf);
+                integrateWithApplicationLayer(metaModelContext, entityChangeTrackerProvider, objectLifecyclePublisher, pmf);
                 return pmf;
             }
         };
@@ -330,14 +332,14 @@ public class IsisModulePersistenceJdoDatanucleus {
 
     private static void integrateWithApplicationLayer(
             final MetaModelContext metaModelContext,
-            final EventBusService eventBusService,
             final Provider<EntityChangeTracker> entityChangeTrackerProvider,
+            final ObjectLifecyclePublisher objectLifecyclePublisher,
             final PersistenceManagerFactory pmf) {
 
         // install JDO specific entity change listeners ...
 
         val jdoLifecycleListener =
-                new JdoLifecycleListener(metaModelContext, eventBusService, entityChangeTrackerProvider);
+                new JdoLifecycleListener(metaModelContext, entityChangeTrackerProvider, objectLifecyclePublisher);
         pmf.addInstanceLifecycleListener(jdoLifecycleListener, (Class[]) null);
 
     }
diff --git a/persistence/jdo/datanucleus/src/main/java/org/apache/isis/persistence/jdo/datanucleus/changetracking/JdoLifecycleListener.java b/persistence/jdo/datanucleus/src/main/java/org/apache/isis/persistence/jdo/datanucleus/changetracking/JdoLifecycleListener.java
index 647aa388e3..79f182388d 100644
--- a/persistence/jdo/datanucleus/src/main/java/org/apache/isis/persistence/jdo/datanucleus/changetracking/JdoLifecycleListener.java
+++ b/persistence/jdo/datanucleus/src/main/java/org/apache/isis/persistence/jdo/datanucleus/changetracking/JdoLifecycleListener.java
@@ -34,7 +34,9 @@ import org.datanucleus.enhancement.Persistable;
 
 import org.apache.isis.applib.services.eventbus.EventBusService;
 import org.apache.isis.core.metamodel.context.MetaModelContext;
+import org.apache.isis.core.metamodel.facets.object.publish.entitychange.EntityChangePublishingFacet;
 import org.apache.isis.core.metamodel.objectmanager.ObjectManager.EntityAdaptingMode;
+import org.apache.isis.core.metamodel.services.objectlifecycle.ObjectLifecyclePublisher;
 import org.apache.isis.core.metamodel.spec.ManagedObject;
 import org.apache.isis.core.transaction.changetracking.EntityChangeTracker;
 import org.apache.isis.core.transaction.changetracking.events.PostStoreEvent;
@@ -65,8 +67,8 @@ implements AttachLifecycleListener, ClearLifecycleListener, CreateLifecycleListe
 DetachLifecycleListener, DirtyLifecycleListener, LoadLifecycleListener, StoreLifecycleListener {
 
     private final @NonNull MetaModelContext metaModelContext;
-    private final @NonNull EventBusService eventBusService;
     private final @NonNull Provider<EntityChangeTracker> entityChangeTrackerProvider;
+    private final @NonNull ObjectLifecyclePublisher objectLifecyclePublisher;
 
     // -- CALLBACKS
 
@@ -92,7 +94,9 @@ DetachLifecycleListener, DirtyLifecycleListener, LoadLifecycleListener, StoreLif
         log.debug("postLoad {}", ()->_Utils.debug(event));
         final Persistable pojo = _Utils.persistableFor(event);
         val entity = adaptEntityAndInjectServices(pojo, EntityAdaptingMode.MEMOIZE_BOOKMARK);
-        getEntityChangeTracker().recognizeLoaded(entity);
+
+        objectLifecyclePublisher.onPostLoad(entity);
+
     }
 
     @Override
@@ -101,13 +105,11 @@ DetachLifecycleListener, DirtyLifecycleListener, LoadLifecycleListener, StoreLif
 
         final Persistable pojo = _Utils.persistableFor(event);
 
-        eventBusService.post(PreStoreEvent.of(pojo));
-
         /* Called either when an entity is initially persisted, or when an entity is updated; fires the appropriate
          * lifecycle callback. So filter for those events when initially persisting. */
         if(pojo.dnGetStateManager().isNew(pojo)) {
             val entity = adaptEntity(pojo, EntityAdaptingMode.SKIP_MEMOIZATION);
-            getEntityChangeTracker().recognizePersisting(entity);
+            objectLifecyclePublisher.onPrePersist(entity);
         }
     }
 
@@ -116,23 +118,22 @@ DetachLifecycleListener, DirtyLifecycleListener, LoadLifecycleListener, StoreLif
         log.debug("postStore {}", ()->_Utils.debug(event));
 
         final Persistable pojo = _Utils.persistableFor(event);
-
         val entity = adaptEntityAndInjectServices(pojo, EntityAdaptingMode.MEMOIZE_BOOKMARK);
 
-        eventBusService.post(PostStoreEvent.of(pojo));
+        if(EntityChangePublishingFacet.isPublishingEnabled(entity.getSpecification())) {
 
-        /* Called either when an entity is initially persisted, or when an entity is updated;
-         * fires the appropriate lifecycle callback.*/
-        if(pojo.dnGetStateManager().isNew(pojo)) {
+            /* Called either when an entity is initially persisted, or when an entity is updated;
+             * fires the appropriate lifecycle callback.*/
+            if(pojo.dnGetStateManager().isNew(pojo)) {
 
-            getEntityChangeTracker().enlistCreated(entity);
+                objectLifecyclePublisher.onPostPersist(entity);
 
-        } else {
-            // the callback and transaction.enlist are done in the preStore callback
-            // (can't be done here, as the enlist requires to capture the 'before' values)
-            getEntityChangeTracker().recognizeUpdating(entity);
+            } else {
+                // the callback and transaction.enlist are done in the preStore callback
+                // (can't be done here, as the enlist requires to capture the 'before' values)
+                objectLifecyclePublisher.onPostUpdate(entity);
+            }
         }
-
     }
 
 
@@ -142,7 +143,8 @@ DetachLifecycleListener, DirtyLifecycleListener, LoadLifecycleListener, StoreLif
 
         final Persistable pojo = _Utils.persistableFor(event);
         val entity = adaptEntity(pojo, EntityAdaptingMode.MEMOIZE_BOOKMARK);
-        getEntityChangeTracker().enlistUpdating(entity);
+
+        objectLifecyclePublisher.onPreUpdate(entity, null);
     }
 
     @Override
@@ -156,7 +158,8 @@ DetachLifecycleListener, DirtyLifecycleListener, LoadLifecycleListener, StoreLif
 
         final Persistable pojo = _Utils.persistableFor(event);
         val entity = adaptEntity(pojo, EntityAdaptingMode.SKIP_MEMOIZATION);
-        getEntityChangeTracker().enlistDeleting(entity);
+
+        objectLifecyclePublisher.onPreRemove(entity);
     }
 
     @Override
diff --git a/persistence/jdo/datanucleus/src/main/java/org/apache/isis/persistence/jdo/datanucleus/metamodel/facets/entity/JdoEntityFacet.java b/persistence/jdo/datanucleus/src/main/java/org/apache/isis/persistence/jdo/datanucleus/metamodel/facets/entity/JdoEntityFacet.java
index f2ccfe1251..4c92f3a0b1 100644
--- a/persistence/jdo/datanucleus/src/main/java/org/apache/isis/persistence/jdo/datanucleus/metamodel/facets/entity/JdoEntityFacet.java
+++ b/persistence/jdo/datanucleus/src/main/java/org/apache/isis/persistence/jdo/datanucleus/metamodel/facets/entity/JdoEntityFacet.java
@@ -38,6 +38,7 @@ import org.apache.isis.applib.query.NamedQuery;
 import org.apache.isis.applib.query.Query;
 import org.apache.isis.applib.services.bookmark.Bookmark;
 import org.apache.isis.applib.services.bookmark.IdStringifier;
+import org.apache.isis.core.metamodel.services.objectlifecycle.ObjectLifecyclePublisher;
 import org.apache.isis.core.runtime.idstringifier.IdStringifierLookupService;
 import org.apache.isis.applib.services.exceprecog.Category;
 import org.apache.isis.applib.services.exceprecog.ExceptionRecognizerService;
@@ -412,16 +413,16 @@ implements EntityFacet {
 
     private Can<ManagedObject> fetchWithinTransaction(final Supplier<List<?>> fetcher) {
 
-        val entityChangeTracker = getFacetHolder().getServiceRegistry().lookupServiceElseFail(EntityChangeTracker.class);
+        val objectLifecyclePublisher = getFacetHolder().getServiceRegistry().lookupServiceElseFail(ObjectLifecyclePublisher.class);
 
         return getTransactionalProcessor().callWithinCurrentTransactionElseCreateNew(
                 ()->_NullSafe.stream(fetcher.get())
-                    .map(fetchedObject->adopt(entityChangeTracker, fetchedObject))
+                    .map(fetchedObject->adopt(objectLifecyclePublisher, fetchedObject))
                     .collect(Can.toCan()))
                 .getValue().orElseThrow();
     }
 
-    private ManagedObject adopt(final EntityChangeTracker entityChangeTracker, final Object fetchedObject) {
+    private ManagedObject adopt(final ObjectLifecyclePublisher objectLifecyclePublisher, final Object fetchedObject) {
         // handles lifecycle callbacks and injects services
 
         // ought not to be necessary, however for some queries it seems that the
@@ -429,8 +430,8 @@ implements EntityFacet {
         if(fetchedObject instanceof Persistable) {
             // an entity
             val entity = objectManager.adapt(fetchedObject);
-                    //fetchResultHandler.initializeEntityAfterFetched((Persistable) fetchedObject);
-            entityChangeTracker.recognizeLoaded(entity);
+
+            objectLifecyclePublisher.onPostLoad(entity);
             return entity;
         } else {
             // a value type
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 fd70d1a2ff..f49bc6672c 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
@@ -31,6 +31,7 @@ import javax.persistence.PreUpdate;
 import org.eclipse.persistence.sessions.UnitOfWork;
 import org.eclipse.persistence.sessions.changesets.DirectToFieldChangeRecord;
 
+import org.apache.isis.applib.services.eventbus.EventBusService;
 import org.apache.isis.applib.services.inject.ServiceInjector;
 import org.apache.isis.commons.collections.Can;
 import org.apache.isis.core.metamodel.facets.object.publish.entitychange.EntityChangePublishingFacet;
@@ -39,6 +40,8 @@ import org.apache.isis.core.metamodel.objectmanager.ObjectManager;
 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.core.metamodel.services.objectlifecycle.PropertyChangeRecordId;
+import org.apache.isis.core.transaction.changetracking.events.PreStoreEvent;
 import org.apache.isis.persistence.jpa.applib.services.JpaSupportService;
 
 import lombok.val;
@@ -67,18 +70,30 @@ public class IsisEntityListener {
     @Inject private ObjectLifecyclePublisher objectLifecyclePublisher;
     @Inject private Provider<JpaSupportService> jpaSupportServiceProvider;
     @Inject private ObjectManager objectManager;
+    @Inject private EventBusService eventBusService;
 
     @PrePersist void onPrePersist(final Object entityPojo) {
         log.debug("onPrePersist: {}", entityPojo);
         serviceInjector.injectServicesInto(entityPojo);
         val entity = objectManager.adapt(entityPojo);
+
         objectLifecyclePublisher.onPrePersist(entity);
     }
 
+    @PostLoad void onPostLoad(final Object entityPojo) {
+        log.debug("onPostLoad: {}", entityPojo);
+        serviceInjector.injectServicesInto(entityPojo);
+        val entity = objectManager.adapt(entityPojo);
+        objectLifecyclePublisher.onPostLoad(entity);
+    }
+
+
     @PreUpdate void onPreUpdate(final Object entityPojo) {
         log.debug("onPreUpdate: {}", entityPojo);
+
         serviceInjector.injectServicesInto(entityPojo);
         val entity = objectManager.adapt(entityPojo);
+
         val entityManagerResult = jpaSupportServiceProvider.get().getEntityManager(entityPojo.getClass());
         entityManagerResult.getValue().ifPresent(em -> {  // https://wiki.eclipse.org/EclipseLink/FAQ/JPA#How_to_access_what_changed_in_an_object_or_transaction.3F
             val unwrap = em.unwrap(UnitOfWork.class);
@@ -89,32 +104,27 @@ public class IsisEntityListener {
             }
 
             final Can<PropertyChangeRecord> propertyChangeRecords =
-            objectChanges
-            .getChanges()
-            .stream()
-            .filter(property-> EntityChangePublishingFacet.isPublishingEnabled(entity.getSpecification()))
-            .filter(DirectToFieldChangeRecord.class::isInstance)
-            .map(DirectToFieldChangeRecord.class::cast)
-            .map(changeRecord -> {
-                //XXX lombok val issue with nested lambda
-                final String propertyName = changeRecord.getAttribute();
-                return entity
-                        .getSpecification()
-                        .getProperty(propertyName)
-                        .filter(property->!property.isMixedIn())
-                        .filter(property->!EntityPropertyChangePublishingPolicyFacet.isExcludedFromPublishing(property))
-                        .map(property->PropertyChangeRecord.of(
-                                entity,
-                                property,
-                                PreAndPostValue
-                                    .pre(changeRecord.getOldValue())
-                                    .withPost(changeRecord.getNewValue())))
-                        .orElse(null); // ignore
-            })
-            .collect(Can.toCan()); // a Can<T> only collects non-null elements
+                objectChanges
+                .getChanges()
+                .stream()
+                .filter(property-> EntityChangePublishingFacet.isPublishingEnabled(entity.getSpecification()))
+                .filter(DirectToFieldChangeRecord.class::isInstance)
+                .map(DirectToFieldChangeRecord.class::cast)
+                .map(ormChangeRecord -> {
+                    //XXX lombok val issue with nested lambda
+                    final String propertyName = ormChangeRecord.getAttribute();
+                    return entity
+                            .getSpecification()
+                            .getProperty(propertyName)
+                            .filter(property->!property.isMixedIn())
+                            .filter(property->!EntityPropertyChangePublishingPolicyFacet.isExcludedFromPublishing(property))
+                            .map(property->PropertyChangeRecord.of(PropertyChangeRecordId.of(entity, property),
+                                                                   PreAndPostValue.pre(ormChangeRecord.getOldValue())))
+                            .orElse(null); // ignore
+                })
+                .collect(Can.toCan()); // a Can<T> only collects non-null elements
 
             objectLifecyclePublisher.onPreUpdate(entity, propertyChangeRecords);
-
         });
     }
 
@@ -141,11 +151,4 @@ public class IsisEntityListener {
         log.debug("onPostRemove: {}", entityPojo);
     }
 
-    @PostLoad void onPostLoad(final Object entityPojo) {
-        log.debug("onPostLoad: {}", entityPojo);
-        serviceInjector.injectServicesInto(entityPojo);
-        val entity = objectManager.adapt(entityPojo);
-        objectLifecyclePublisher.onPostLoad(entity);
-    }
-
 }


[isis] 10/12: ISIS-3110: moves the subscriber for EntityTrackerChangeDefault...

Posted by da...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

danhaywood pushed a commit to branch ISIS-3110
in repository https://gitbox.apache.org/repos/asf/isis.git

commit 5ed3321d043f53261c92845913f92da197893059
Author: Dan Haywood <da...@haywood-associates.co.uk>
AuthorDate: Thu Aug 4 09:30:13 2022 +0100

    ISIS-3110: moves the subscriber for EntityTrackerChangeDefault...
    
    ... into a singleton that can then actively check that there is an interaction in scope
---
 ...ctionInvocationFacetForDomainEventAbstract.java |  8 +--
 .../autocomplete/AutoCompleteFacetAbstract.java    |  4 +-
 .../services/publishing/ExecutionPublisher.java    |  4 +-
 .../specimpl/OneToManyAssociationMixedIn.java      |  4 +-
 .../specimpl/OneToOneAssociationMixedIn.java       |  4 +-
 .../executor/MemberExecutorServiceDefault.java     | 22 ++++--
 .../publish/EntityChangesPublisherDefault.java     |  2 +-
 .../EntityPropertyChangePublisherDefault.java      |  4 +-
 .../publish/ExecutionPublisherDefault.java         | 11 ++-
 .../publish/ObjectLifecyclePublisherDefault.java   | 16 ++---
 .../changetracking/EntityChangeTracker.java        | 15 +++-
 .../changetracking/EntityChangesPublisher.java     |  2 +-
 .../HasInteractionId_commandLogEntry.java          |  5 +-
 .../ApplicationPermissionRepositoryAbstract.java   |  4 +-
 .../dom/ApplicationRoleRepositoryAbstract.java     |  3 +-
 .../dom/ApplicationTenancyRepositoryAbstract.java  |  2 +-
 .../dom/ApplicationUserRepositoryAbstract.java     |  5 +-
 .../secman/applib/user/menu/MeService.java         |  3 +-
 .../integration/authorizor/AuthorizorSecman.java   | 10 +--
 .../facets/TenantedAuthorizationFacetDefault.java  |  4 +-
 .../facets/TenantedAuthorizationPostProcessor.java |  1 +
 .../commons/IsisModulePersistenceCommons.java      |  1 +
 .../changetracking/EntityChangeTrackerDefault.java | 79 +++++++++++++++++++---
 .../IsisModulePersistenceJdoDatanucleus.java       | 10 +--
 .../changetracking/JdoLifecycleListener.java       |  9 ++-
 .../persistence/jpa/JpaBootstrappingTest.java      |  2 +-
 ...rgetRespondListenerToResetQueryResultCache.java |  3 +-
 27 files changed, 165 insertions(+), 72 deletions(-)

diff --git a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/actions/action/invocation/ActionInvocationFacetForDomainEventAbstract.java b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/actions/action/invocation/ActionInvocationFacetForDomainEventAbstract.java
index 36efa2a4ed..2be7bf26d8 100644
--- a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/actions/action/invocation/ActionInvocationFacetForDomainEventAbstract.java
+++ b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/actions/action/invocation/ActionInvocationFacetForDomainEventAbstract.java
@@ -152,7 +152,7 @@ implements ImperativeFacet {
         final ActionSemanticsFacet semanticsFacet = getFacetHolder().getFacet(ActionSemanticsFacet.class);
         final boolean cacheable = semanticsFacet != null && semanticsFacet.value().isSafeAndRequestCacheable();
         if(cacheable) {
-            final QueryResultsCache queryResultsCache = getQueryResultsCache();
+            final QueryResultsCache queryResultsCache = queryResultsCache();
             final Object[] targetPojoPlusExecutionParameters = _Arrays.combine(executionParameters, targetPojo);
             return queryResultsCache.execute(
                     ()->CanonicalInvoker.invoke(method, targetPojo, executionParameters),
@@ -163,11 +163,11 @@ implements ImperativeFacet {
         }
     }
 
-    private QueryResultsCache getQueryResultsCache() {
+    private QueryResultsCache queryResultsCache() {
         return serviceRegistry.lookupServiceElseFail(QueryResultsCache.class);
     }
 
-    private InteractionDtoFactory getInteractionDtoServiceInternal() {
+    private InteractionDtoFactory interactionDtoFactory() {
         return serviceRegistry.lookupServiceElseFail(InteractionDtoFactory.class);
     }
 
@@ -184,7 +184,7 @@ implements ImperativeFacet {
         public Object execute(final ActionInvocation currentExecution) {
 
             // update the current execution with the DTO (memento)
-            val invocationDto = getInteractionDtoServiceInternal()
+            val invocationDto = interactionDtoFactory()
             .asActionInvocationDto(owningAction, head, initialArgs);
 
             currentExecution.setDto(invocationDto);
diff --git a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/object/autocomplete/AutoCompleteFacetAbstract.java b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/object/autocomplete/AutoCompleteFacetAbstract.java
index e5a31b73bb..5cca5267fe 100644
--- a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/object/autocomplete/AutoCompleteFacetAbstract.java
+++ b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/object/autocomplete/AutoCompleteFacetAbstract.java
@@ -73,7 +73,7 @@ implements AutoCompleteFacet {
             final String search,
             final InteractionInitiatedBy interactionInitiatedBy) {
 
-        val resultAdapter = getPublisherDispatchService()
+        val resultAdapter = executionPublisher()
         .withPublishingSuppressed(()->{
                 final Object list = _Reflect.invokeMethodOn(repositoryMethod, getRepository(), search)
                         .ifFailure(e->log.warn("failure while executing auto-complete", e))
@@ -90,7 +90,7 @@ implements AutoCompleteFacet {
         return getServiceRegistry().lookupService(repositoryClass).orElse(null);
     }
 
-    private ExecutionPublisher getPublisherDispatchService() {
+    private ExecutionPublisher executionPublisher() {
         return getServiceRegistry().lookupServiceElseFail(ExecutionPublisher.class);
     }
 
diff --git a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/services/publishing/ExecutionPublisher.java b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/services/publishing/ExecutionPublisher.java
index 197e043ca9..4007aace35 100644
--- a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/services/publishing/ExecutionPublisher.java
+++ b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/services/publishing/ExecutionPublisher.java
@@ -20,6 +20,8 @@ package org.apache.isis.core.metamodel.services.publishing;
 
 import java.util.function.Supplier;
 
+import org.springframework.beans.factory.DisposableBean;
+
 import org.apache.isis.applib.annotation.Action;
 import org.apache.isis.applib.annotation.Property;
 import org.apache.isis.applib.services.iactn.Execution;
@@ -33,7 +35,7 @@ import org.apache.isis.applib.services.publishing.spi.ExecutionSubscriber;
  *
  * @see ExecutionSubscriber
  */
-public interface ExecutionPublisher {
+public interface ExecutionPublisher extends DisposableBean {
 
     /**
      * Notifies {@link ExecutionSubscriber}s of an action invocation through
diff --git a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/specloader/specimpl/OneToManyAssociationMixedIn.java b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/specloader/specimpl/OneToManyAssociationMixedIn.java
index a216d1e76e..911fabc6a6 100644
--- a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/specloader/specimpl/OneToManyAssociationMixedIn.java
+++ b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/specloader/specimpl/OneToManyAssociationMixedIn.java
@@ -145,7 +145,7 @@ implements MixedInMember {
             final ManagedObject ownerAdapter,
             final InteractionInitiatedBy interactionInitiatedBy) {
 
-        return getPublishingServiceInternal().withPublishingSuppressed(
+        return executionPublisher().withPublishingSuppressed(
                 () -> mixinAction.executeInternal(
                         headFor(ownerAdapter), Can.empty(), interactionInitiatedBy));
     }
@@ -176,7 +176,7 @@ implements MixedInMember {
                 || _Annotations.synthesize(javaMethod, Domain.Include.class).isPresent();
     }
 
-    private ExecutionPublisher getPublishingServiceInternal() {
+    private ExecutionPublisher executionPublisher() {
         return getServiceRegistry().lookupServiceElseFail(ExecutionPublisher.class);
     }
 
diff --git a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/specloader/specimpl/OneToOneAssociationMixedIn.java b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/specloader/specimpl/OneToOneAssociationMixedIn.java
index 8618c0de79..d90bfc5cf3 100644
--- a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/specloader/specimpl/OneToOneAssociationMixedIn.java
+++ b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/specloader/specimpl/OneToOneAssociationMixedIn.java
@@ -128,7 +128,7 @@ implements MixedInMember {
 
         val head = headFor(mixedInAdapter);
 
-        return getPublisherDispatchService().withPublishingSuppressed(
+        return executionPublisher().withPublishingSuppressed(
                 () -> mixinAction.executeInternal(head, Can.empty(), interactionInitiatedBy)
         );
     }
@@ -159,7 +159,7 @@ implements MixedInMember {
                 || _Annotations.synthesize(javaMethod, Domain.Include.class).isPresent();
     }
 
-    private ExecutionPublisher getPublisherDispatchService() {
+    private ExecutionPublisher executionPublisher() {
         return getServiceRegistry().lookupServiceElseFail(ExecutionPublisher.class);
     }
 
diff --git a/core/runtimeservices/src/main/java/org/apache/isis/core/runtimeservices/executor/MemberExecutorServiceDefault.java b/core/runtimeservices/src/main/java/org/apache/isis/core/runtimeservices/executor/MemberExecutorServiceDefault.java
index 4978fd27d2..2768bb108b 100644
--- a/core/runtimeservices/src/main/java/org/apache/isis/core/runtimeservices/executor/MemberExecutorServiceDefault.java
+++ b/core/runtimeservices/src/main/java/org/apache/isis/core/runtimeservices/executor/MemberExecutorServiceDefault.java
@@ -51,7 +51,6 @@ import org.apache.isis.core.metamodel.consent.InteractionInitiatedBy;
 import org.apache.isis.core.metamodel.execution.InteractionInternal;
 import org.apache.isis.core.metamodel.execution.MemberExecutorService;
 import org.apache.isis.core.metamodel.facetapi.FacetHolder;
-import org.apache.isis.core.metamodel.facets.actions.action.invocation.IdentifierUtil;
 import org.apache.isis.core.metamodel.facets.members.publish.command.CommandPublishingFacet;
 import org.apache.isis.core.metamodel.facets.members.publish.execution.ExecutionPublishingFacet;
 import org.apache.isis.core.metamodel.facets.properties.property.modify.PropertySetterOrClearFacetForDomainEventAbstract.EditingVariant;
@@ -91,12 +90,20 @@ implements MemberExecutorService {
     private final @Getter ObjectManager objectManager;
     private final @Getter ClockService clockService;
     private final @Getter ServiceInjector serviceInjector;
-    private final @Getter Provider<MetricsService> metricsService;
+    private final @Getter Provider<MetricsService> metricsServiceProvider;
     private final @Getter InteractionDtoFactory interactionDtoFactory;
-    private final @Getter Provider<ExecutionPublisher> executionPublisher;
+    private final @Getter Provider<ExecutionPublisher> executionPublisherProvider;
     private final @Getter MetamodelEventService metamodelEventService;
     private final @Getter TransactionService transactionService;
 
+    private MetricsService metricsService() {
+        return metricsServiceProvider.get();
+    }
+
+    private ExecutionPublisher executionPublisher() {
+        return executionPublisherProvider.get();
+    }
+
     @Override
     public Optional<InteractionInternal> getInteraction() {
         return interactionLayerTracker.currentInteraction()
@@ -150,7 +157,7 @@ implements MemberExecutorService {
         val memberExecutor = actionExecutorFactory.createExecutor(owningAction, head, argumentAdapters);
 
         // sets up startedAt and completedAt on the execution, also manages the execution call graph
-        interaction.execute(memberExecutor, actionInvocation, clockService, metricsService.get(), command);
+        interaction.execute(memberExecutor, actionInvocation, clockService, metricsService(), command);
 
         // handle any exceptions
         final Execution<ActionInvocationDto, ?> priorExecution =
@@ -179,13 +186,14 @@ implements MemberExecutorService {
 
         // publish (if not a contributed association, query-only mixin)
         if (ExecutionPublishingFacet.isPublishingEnabled(facetHolder)) {
-            executionPublisher.get().publishActionInvocation(priorExecution);
+            executionPublisher().publishActionInvocation(priorExecution);
         }
 
         val result = resultFilteredHonoringVisibility(method, returnedAdapter, interactionInitiatedBy);
         _Xray.exitInvocation(xrayHandle);
         return result;
     }
+
     @Override
     public ManagedObject setOrClearProperty(
             final @NonNull OneToOneAssociation owningProperty,
@@ -218,7 +226,7 @@ implements MemberExecutorService {
                         interactionInitiatedBy, editingVariant);
 
         // sets up startedAt and completedAt on the execution, also manages the execution call graph
-        val targetPojo = interaction.execute(executor, propertyEdit, clockService, metricsService.get(), command);
+        val targetPojo = interaction.execute(executor, propertyEdit, clockService, metricsService(), command);
 
         // handle any exceptions
         final Execution<?, ?> priorExecution = interaction.getPriorExecution();
@@ -235,7 +243,7 @@ implements MemberExecutorService {
         // publish (if not a contributed association, query-only mixin)
         val publishedPropertyFacet = facetHolder.getFacet(ExecutionPublishingFacet.class);
         if (publishedPropertyFacet != null) {
-            executionPublisher.get().publishPropertyEdit(priorExecution);
+            executionPublisher().publishPropertyEdit(priorExecution);
         }
 
         val result = getObjectManager().adapt(targetPojo);
diff --git a/core/runtimeservices/src/main/java/org/apache/isis/core/runtimeservices/publish/EntityChangesPublisherDefault.java b/core/runtimeservices/src/main/java/org/apache/isis/core/runtimeservices/publish/EntityChangesPublisherDefault.java
index 69b0b251e1..883f5cb60c 100644
--- a/core/runtimeservices/src/main/java/org/apache/isis/core/runtimeservices/publish/EntityChangesPublisherDefault.java
+++ b/core/runtimeservices/src/main/java/org/apache/isis/core/runtimeservices/publish/EntityChangesPublisherDefault.java
@@ -85,7 +85,7 @@ public class EntityChangesPublisherDefault implements EntityChangesPublisher {
 
     // -- HELPER
 
-    private Optional<EntityChanges> getPayload(HasEnlistedEntityChanges hasEnlistedEntityChanges) {
+    private Optional<EntityChanges> getPayload(final @NonNull HasEnlistedEntityChanges hasEnlistedEntityChanges) {
         return enabledSubscribers.isEmpty()
                 ? Optional.empty()
                 : hasEnlistedEntityChanges.getEntityChanges(
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 83e4cda4e2..c83f1406e7 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
@@ -72,7 +72,7 @@ public class EntityPropertyChangePublisherDefault implements EntityPropertyChang
                 .filter(HasEnabling::isEnabled);
     }
 
-    private HasEnlistedEntityPropertyChanges getHasEnlistedEntityPropertyChanges() {
+    private HasEnlistedEntityPropertyChanges hasEnlistedEntityPropertyChanges() {
         return hasEnlistedEntityPropertyChangesProvider.get();
     }
 
@@ -89,7 +89,7 @@ public class EntityPropertyChangePublisherDefault implements EntityPropertyChang
         val currentUser = userService.currentUserNameElseNobody();
         val currentTransactionId = transactionService.currentTransactionId().orElse(TransactionId.empty());
 
-        val propertyChanges = getHasEnlistedEntityPropertyChanges().getPropertyChanges(
+        val propertyChanges = hasEnlistedEntityPropertyChanges().getPropertyChanges(
                 currentTime,
                 currentUser,
                 currentTransactionId);
diff --git a/core/runtimeservices/src/main/java/org/apache/isis/core/runtimeservices/publish/ExecutionPublisherDefault.java b/core/runtimeservices/src/main/java/org/apache/isis/core/runtimeservices/publish/ExecutionPublisherDefault.java
index 62fe2a68f4..c8e641a8b5 100644
--- a/core/runtimeservices/src/main/java/org/apache/isis/core/runtimeservices/publish/ExecutionPublisherDefault.java
+++ b/core/runtimeservices/src/main/java/org/apache/isis/core/runtimeservices/publish/ExecutionPublisherDefault.java
@@ -22,6 +22,7 @@ import java.util.List;
 import java.util.concurrent.atomic.LongAdder;
 import java.util.function.Supplier;
 
+import org.springframework.beans.factory.DisposableBean;
 import org.springframework.lang.Nullable;
 import javax.annotation.PostConstruct;
 import javax.annotation.Priority;
@@ -57,6 +58,10 @@ implements ExecutionPublisher {
     private final InteractionLayerTracker iaTracker;
 
     private Can<ExecutionSubscriber> enabledSubscribers = Can.empty();
+    /**
+     * this is the reason that this service is @InteractionScope'd
+     */
+    private final LongAdder suppressionRequestCounter = new LongAdder();
 
     @PostConstruct
     public void init() {
@@ -64,6 +69,11 @@ implements ExecutionPublisher {
                 .filter(HasEnabling::isEnabled);
     }
 
+    @Override
+    public void destroy() throws Exception {
+        suppressionRequestCounter.reset();
+    }
+
     @Override
     public void publishActionInvocation(final Execution<?,?> execution) {
         notifySubscribers(execution);
@@ -104,7 +114,6 @@ implements ExecutionPublisher {
 
     }
 
-    private final LongAdder suppressionRequestCounter = new LongAdder();
 
     private boolean canPublish() {
         return enabledSubscribers.isNotEmpty()
diff --git a/core/runtimeservices/src/main/java/org/apache/isis/core/runtimeservices/publish/ObjectLifecyclePublisherDefault.java b/core/runtimeservices/src/main/java/org/apache/isis/core/runtimeservices/publish/ObjectLifecyclePublisherDefault.java
index 26feea1bfc..71489accdb 100644
--- a/core/runtimeservices/src/main/java/org/apache/isis/core/runtimeservices/publish/ObjectLifecyclePublisherDefault.java
+++ b/core/runtimeservices/src/main/java/org/apache/isis/core/runtimeservices/publish/ObjectLifecyclePublisherDefault.java
@@ -28,6 +28,7 @@ import org.springframework.lang.Nullable;
 import org.springframework.stereotype.Service;
 
 import org.apache.isis.applib.annotation.PriorityPrecedence;
+import org.apache.isis.applib.services.iactnlayer.InteractionService;
 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;
@@ -46,6 +47,8 @@ import org.apache.isis.core.runtimeservices.IsisModuleCoreRuntimeServices;
 import org.apache.isis.core.transaction.changetracking.EntityChangeTracker;
 import org.apache.isis.core.transaction.changetracking.events.PostStoreEvent;
 
+import lombok.RequiredArgsConstructor;
+
 /**
  * @see ObjectLifecyclePublisher
  * @since 2.0 {@index}
@@ -54,23 +57,18 @@ import org.apache.isis.core.transaction.changetracking.events.PostStoreEvent;
 @Named(IsisModuleCoreRuntimeServices.NAMESPACE + ".ObjectLifecyclePublisherDefault")
 @Priority(PriorityPrecedence.EARLY)
 @Qualifier("Default")
+@RequiredArgsConstructor(onConstructor_ = {@Inject})
 //@Log4j2
 public class ObjectLifecyclePublisherDefault implements ObjectLifecyclePublisher {
 
     private final Provider<EntityChangeTracker> entityChangeTrackerProvider;
     private final Provider<LifecycleCallbackNotifier> lifecycleCallbackNotifierProvider;
-
-    @Inject
-    public ObjectLifecyclePublisherDefault(
-            final Provider<EntityChangeTracker> entityChangeTrackerProvider,
-            final Provider<LifecycleCallbackNotifier> lifecycleCallbackNotifierProvider) {
-        this.entityChangeTrackerProvider = entityChangeTrackerProvider;
-        this.lifecycleCallbackNotifierProvider = lifecycleCallbackNotifierProvider;
-    }
+    private final InteractionService interactionService;
 
     EntityChangeTracker entityChangeTracker() {
-        return entityChangeTrackerProvider.get();
+        return interactionService.isInInteraction() ? entityChangeTrackerProvider.get() : EntityChangeTracker.NOOP;
     }
+
     LifecycleCallbackNotifier lifecycleCallbackNotifier() {
         return lifecycleCallbackNotifierProvider.get();
     }
diff --git a/core/transaction/src/main/java/org/apache/isis/core/transaction/changetracking/EntityChangeTracker.java b/core/transaction/src/main/java/org/apache/isis/core/transaction/changetracking/EntityChangeTracker.java
index eaf09281b4..a75a7ead10 100644
--- a/core/transaction/src/main/java/org/apache/isis/core/transaction/changetracking/EntityChangeTracker.java
+++ b/core/transaction/src/main/java/org/apache/isis/core/transaction/changetracking/EntityChangeTracker.java
@@ -18,6 +18,7 @@
  */
 package org.apache.isis.core.transaction.changetracking;
 
+import org.springframework.beans.factory.DisposableBean;
 import org.springframework.lang.Nullable;
 
 import org.apache.isis.commons.collections.Can;
@@ -30,7 +31,19 @@ import org.apache.isis.core.metamodel.spec.ManagedObject;
  *
  * @since 1.x but renamed/refactored for v2 {@index}
  */
-public interface EntityChangeTracker {
+public interface EntityChangeTracker extends DisposableBean {
+
+    /**
+     * Provided primarily for testing, but also used in cases where an attempt is made to resolve a bean but
+     * there is no active interaction.
+     */
+    EntityChangeTracker NOOP = new EntityChangeTracker() {
+        @Override public void destroy() throws Exception {}
+        @Override public void enlistCreated(ManagedObject entity) {}
+        @Override public void enlistUpdating(ManagedObject entity, Can<PropertyChangeRecord> propertyChangeRecords) {}
+        @Override public void enlistDeleting(ManagedObject entity) {}
+        @Override public void incrementLoaded(ManagedObject entity) {}
+    };
 
     /**
      * Publishing support: for object stores to enlist an object that has just been created,
diff --git a/core/transaction/src/main/java/org/apache/isis/core/transaction/changetracking/EntityChangesPublisher.java b/core/transaction/src/main/java/org/apache/isis/core/transaction/changetracking/EntityChangesPublisher.java
index d3c6efec0e..a5583acd70 100644
--- a/core/transaction/src/main/java/org/apache/isis/core/transaction/changetracking/EntityChangesPublisher.java
+++ b/core/transaction/src/main/java/org/apache/isis/core/transaction/changetracking/EntityChangesPublisher.java
@@ -33,6 +33,6 @@ public interface EntityChangesPublisher {
      * an {@link org.apache.isis.applib.services.iactn.Interaction}, calling
      * the {@link EntityChangesSubscriber#onChanging(EntityChanges)} callback.
      */
-    void publishChangingEntities(HasEnlistedEntityChanges hasEnlistedEntityChanges);
+    void publishChangingEntities(final HasEnlistedEntityChanges hasEnlistedEntityChanges);
 
 }
diff --git a/extensions/core/commandlog/applib/src/main/java/org/apache/isis/extensions/commandlog/applib/contributions/HasInteractionId_commandLogEntry.java b/extensions/core/commandlog/applib/src/main/java/org/apache/isis/extensions/commandlog/applib/contributions/HasInteractionId_commandLogEntry.java
index 2245fb3420..9961cb3ac7 100644
--- a/extensions/core/commandlog/applib/src/main/java/org/apache/isis/extensions/commandlog/applib/contributions/HasInteractionId_commandLogEntry.java
+++ b/extensions/core/commandlog/applib/src/main/java/org/apache/isis/extensions/commandlog/applib/contributions/HasInteractionId_commandLogEntry.java
@@ -21,6 +21,7 @@
 package org.apache.isis.extensions.commandlog.applib.contributions;
 
 import javax.inject.Inject;
+import javax.inject.Provider;
 
 import org.apache.isis.applib.annotation.Property;
 import org.apache.isis.applib.mixins.system.HasInteractionId;
@@ -48,7 +49,7 @@ public class HasInteractionId_commandLogEntry {
 
 
     public CommandLogEntry prop() {
-        return queryResultsCache.execute(this::doProp, getClass(), "prop");
+        return queryResultsCacheProvider.get().execute(this::doProp, getClass(), "prop");
     }
 
     private CommandLogEntry doProp() {
@@ -64,6 +65,6 @@ public class HasInteractionId_commandLogEntry {
     }
 
     @Inject CommandLogEntryRepository<? extends CommandLogEntry> commandLogEntryRepository;
-    @Inject QueryResultsCache queryResultsCache;
+    @Inject Provider<QueryResultsCache> queryResultsCacheProvider;
 
 }
diff --git a/extensions/security/secman/applib/src/main/java/org/apache/isis/extensions/secman/applib/permission/dom/ApplicationPermissionRepositoryAbstract.java b/extensions/security/secman/applib/src/main/java/org/apache/isis/extensions/secman/applib/permission/dom/ApplicationPermissionRepositoryAbstract.java
index 4035605680..0f94b8ab4a 100644
--- a/extensions/security/secman/applib/src/main/java/org/apache/isis/extensions/secman/applib/permission/dom/ApplicationPermissionRepositoryAbstract.java
+++ b/extensions/security/secman/applib/src/main/java/org/apache/isis/extensions/secman/applib/permission/dom/ApplicationPermissionRepositoryAbstract.java
@@ -24,6 +24,7 @@ import java.util.Optional;
 import java.util.stream.Collectors;
 
 import javax.inject.Inject;
+import javax.inject.Provider;
 
 import org.apache.isis.applib.query.Query;
 import org.apache.isis.applib.services.appfeat.ApplicationFeature;
@@ -54,11 +55,10 @@ implements ApplicationPermissionRepository {
 
     @Inject private RepositoryService repository;
     @Inject private ApplicationFeatureRepository featureRepository;
-    @Inject private ApplicationRoleRepository roleRepository;
     @Inject private FactoryService factory;
     @Inject private MessageService messages;
 
-    @Inject private javax.inject.Provider<QueryResultsCache> queryResultsCacheProvider;
+    @Inject private Provider<QueryResultsCache> queryResultsCacheProvider;
 
     private final Class<P> applicationPermissionClass;
 
diff --git a/extensions/security/secman/applib/src/main/java/org/apache/isis/extensions/secman/applib/role/dom/ApplicationRoleRepositoryAbstract.java b/extensions/security/secman/applib/src/main/java/org/apache/isis/extensions/secman/applib/role/dom/ApplicationRoleRepositoryAbstract.java
index b23ab86855..59d7d2f902 100644
--- a/extensions/security/secman/applib/src/main/java/org/apache/isis/extensions/secman/applib/role/dom/ApplicationRoleRepositoryAbstract.java
+++ b/extensions/security/secman/applib/src/main/java/org/apache/isis/extensions/secman/applib/role/dom/ApplicationRoleRepositoryAbstract.java
@@ -51,8 +51,7 @@ implements ApplicationRoleRepository {
     @Inject private FactoryService factoryService;
     @Inject private RepositoryService repository;
     @Inject private IsisConfiguration config;
-    @Inject RegexReplacer regexReplacer;
-
+    @Inject private RegexReplacer regexReplacer;
     @Inject private Provider<QueryResultsCache> queryResultsCacheProvider;
 
     private final Class<R> applicationRoleClass;
diff --git a/extensions/security/secman/applib/src/main/java/org/apache/isis/extensions/secman/applib/tenancy/dom/ApplicationTenancyRepositoryAbstract.java b/extensions/security/secman/applib/src/main/java/org/apache/isis/extensions/secman/applib/tenancy/dom/ApplicationTenancyRepositoryAbstract.java
index bc441a40a4..502e189012 100644
--- a/extensions/security/secman/applib/src/main/java/org/apache/isis/extensions/secman/applib/tenancy/dom/ApplicationTenancyRepositoryAbstract.java
+++ b/extensions/security/secman/applib/src/main/java/org/apache/isis/extensions/secman/applib/tenancy/dom/ApplicationTenancyRepositoryAbstract.java
@@ -40,8 +40,8 @@ implements ApplicationTenancyRepository {
 
     @Inject private FactoryService factory;
     @Inject private RepositoryService repository;
+    @Inject private RegexReplacer regexReplacer;
     @Inject private Provider<QueryResultsCache> queryResultsCacheProvider;
-    @Inject RegexReplacer regexReplacer;
 
 
     private final Class<T> applicationTenancyClass;
diff --git a/extensions/security/secman/applib/src/main/java/org/apache/isis/extensions/secman/applib/user/dom/ApplicationUserRepositoryAbstract.java b/extensions/security/secman/applib/src/main/java/org/apache/isis/extensions/secman/applib/user/dom/ApplicationUserRepositoryAbstract.java
index a0f6c56381..e046776c3e 100644
--- a/extensions/security/secman/applib/src/main/java/org/apache/isis/extensions/secman/applib/user/dom/ApplicationUserRepositoryAbstract.java
+++ b/extensions/security/secman/applib/src/main/java/org/apache/isis/extensions/secman/applib/user/dom/ApplicationUserRepositoryAbstract.java
@@ -25,6 +25,7 @@ import java.util.Optional;
 import java.util.function.Consumer;
 
 import javax.inject.Inject;
+import javax.inject.Provider;
 
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.beans.factory.annotation.Qualifier;
@@ -57,12 +58,12 @@ implements ApplicationUserRepository {
     @Inject private RepositoryService repository;
 	@Inject protected IsisConfiguration config;
     @Inject private EventBusService eventBusService;
-    @Inject RegexReplacer regexReplacer;
+    @Inject private RegexReplacer regexReplacer;
+    @Inject private Provider<QueryResultsCache> queryResultsCacheProvider;
 
     // empty if no candidate is available
     @Autowired(required = false) @Qualifier("secman") PasswordEncoder passwordEncoder;
 
-    @Inject private javax.inject.Provider<QueryResultsCache> queryResultsCacheProvider;
 
     private final Class<U> applicationUserClass;
 
diff --git a/extensions/security/secman/applib/src/main/java/org/apache/isis/extensions/secman/applib/user/menu/MeService.java b/extensions/security/secman/applib/src/main/java/org/apache/isis/extensions/secman/applib/user/menu/MeService.java
index 3611c45dff..18a0bcdfe5 100644
--- a/extensions/security/secman/applib/src/main/java/org/apache/isis/extensions/secman/applib/user/menu/MeService.java
+++ b/extensions/security/secman/applib/src/main/java/org/apache/isis/extensions/secman/applib/user/menu/MeService.java
@@ -22,6 +22,7 @@ import java.util.concurrent.Callable;
 
 import javax.inject.Inject;
 import javax.inject.Named;
+import javax.inject.Provider;
 
 import org.springframework.context.event.EventListener;
 import org.springframework.stereotype.Component;
@@ -68,7 +69,7 @@ public class MeService {
 
     final ApplicationUserRepository applicationUserRepository;
     final UserService userService;
-    final javax.inject.Provider<QueryResultsCache> queryResultsCacheProvider;
+    final Provider<QueryResultsCache> queryResultsCacheProvider;
 
 
     @ObjectSupport public String iconName() {
diff --git a/extensions/security/secman/integration/src/main/java/org/apache/isis/extensions/secman/integration/authorizor/AuthorizorSecman.java b/extensions/security/secman/integration/src/main/java/org/apache/isis/extensions/secman/integration/authorizor/AuthorizorSecman.java
index 8b4bfa41b4..95e1fd4c8e 100644
--- a/extensions/security/secman/integration/src/main/java/org/apache/isis/extensions/secman/integration/authorizor/AuthorizorSecman.java
+++ b/extensions/security/secman/integration/src/main/java/org/apache/isis/extensions/secman/integration/authorizor/AuthorizorSecman.java
@@ -95,21 +95,17 @@ public class AuthorizorSecman implements Authorizor {
     @InteractionScope
     static class PermissionCache implements DisposableBean {
 
-        private Map<String, Optional<ApplicationPermissionValueSet>> permissionsByUsername;
+        private final Map<String, Optional<ApplicationPermissionValueSet>> permissionsByUsername = _Maps.newHashMap();
 
         @Override
-        public void destroy() throws Exception {
-            permissionsByUsername = null;
+        public void destroy() {
+            permissionsByUsername.clear();
         }
 
         Optional<ApplicationPermissionValueSet> computeIfAbsent(
                 final @NonNull String userName,
                 final Supplier<Optional<ApplicationPermissionValueSet>> lookup) {
 
-            if(permissionsByUsername==null) {
-                permissionsByUsername = _Maps.newHashMap();
-            }
-
             return permissionsByUsername.computeIfAbsent(userName, __->lookup.get());
         }
 
diff --git a/extensions/security/secman/integration/src/main/java/org/apache/isis/extensions/secman/integration/facets/TenantedAuthorizationFacetDefault.java b/extensions/security/secman/integration/src/main/java/org/apache/isis/extensions/secman/integration/facets/TenantedAuthorizationFacetDefault.java
index a86191527d..2b2a1dd19e 100644
--- a/extensions/security/secman/integration/src/main/java/org/apache/isis/extensions/secman/integration/facets/TenantedAuthorizationFacetDefault.java
+++ b/extensions/security/secman/integration/src/main/java/org/apache/isis/extensions/secman/integration/facets/TenantedAuthorizationFacetDefault.java
@@ -37,14 +37,14 @@ public class TenantedAuthorizationFacetDefault
 extends FacetAbstract
 implements TenantedAuthorizationFacet {
 
-    private static final Class<? extends Facet> type() {
+    private static Class<? extends Facet> type() {
         return TenantedAuthorizationFacet.class;
     }
 
     private final List<ApplicationTenancyEvaluator> evaluators;
     private final ApplicationUserRepository applicationUserRepository;
-    private final Provider<QueryResultsCache> queryResultsCacheProvider;
     private final UserService userService;
+    private final Provider<QueryResultsCache> queryResultsCacheProvider;
 
     public TenantedAuthorizationFacetDefault(
             final List<ApplicationTenancyEvaluator> evaluators,
diff --git a/extensions/security/secman/integration/src/main/java/org/apache/isis/extensions/secman/integration/facets/TenantedAuthorizationPostProcessor.java b/extensions/security/secman/integration/src/main/java/org/apache/isis/extensions/secman/integration/facets/TenantedAuthorizationPostProcessor.java
index 9a8bdaa8ea..38747bf5e5 100644
--- a/extensions/security/secman/integration/src/main/java/org/apache/isis/extensions/secman/integration/facets/TenantedAuthorizationPostProcessor.java
+++ b/extensions/security/secman/integration/src/main/java/org/apache/isis/extensions/secman/integration/facets/TenantedAuthorizationPostProcessor.java
@@ -66,6 +66,7 @@ extends ObjectSpecificationPostProcessorAbstract {
     @Inject UserService userService;
     @Inject @Lazy ApplicationUserRepository userRepository;
     @Inject Provider<QueryResultsCache> queryResultsCacheProvider;
+
     @Autowired(required=false) List<ApplicationTenancyEvaluator> applicationTenancyEvaluators;
 
     @Inject
diff --git a/persistence/commons/src/main/java/org/apache/isis/persistence/commons/IsisModulePersistenceCommons.java b/persistence/commons/src/main/java/org/apache/isis/persistence/commons/IsisModulePersistenceCommons.java
index c2613ac727..8d791d2131 100644
--- a/persistence/commons/src/main/java/org/apache/isis/persistence/commons/IsisModulePersistenceCommons.java
+++ b/persistence/commons/src/main/java/org/apache/isis/persistence/commons/IsisModulePersistenceCommons.java
@@ -31,6 +31,7 @@ import org.apache.isis.persistence.jpa.integration.changetracking.EntityChangeTr
 
         // @Service's
         EntityChangeTrackerDefault.class,
+        EntityChangeTrackerDefault.TransactionSubscriber.class,
 
 })
 public class IsisModulePersistenceCommons {
diff --git a/persistence/commons/src/main/java/org/apache/isis/persistence/jpa/integration/changetracking/EntityChangeTrackerDefault.java b/persistence/commons/src/main/java/org/apache/isis/persistence/jpa/integration/changetracking/EntityChangeTrackerDefault.java
index 620f5c96ec..f2b40b61f8 100644
--- a/persistence/commons/src/main/java/org/apache/isis/persistence/jpa/integration/changetracking/EntityChangeTrackerDefault.java
+++ b/persistence/commons/src/main/java/org/apache/isis/persistence/jpa/integration/changetracking/EntityChangeTrackerDefault.java
@@ -31,10 +31,12 @@ import javax.inject.Inject;
 import javax.inject.Named;
 import javax.inject.Provider;
 
+import org.springframework.beans.factory.DisposableBean;
 import org.springframework.beans.factory.annotation.Qualifier;
 import org.springframework.context.event.EventListener;
 import org.springframework.core.annotation.Order;
 import org.springframework.lang.Nullable;
+import org.springframework.stereotype.Component;
 import org.springframework.stereotype.Service;
 
 import org.apache.isis.applib.annotation.DomainObject;
@@ -44,6 +46,7 @@ import org.apache.isis.applib.annotation.PriorityPrecedence;
 import org.apache.isis.applib.services.bookmark.Bookmark;
 import org.apache.isis.applib.services.iactn.Interaction;
 import org.apache.isis.applib.services.iactn.InteractionProvider;
+import org.apache.isis.applib.services.iactnlayer.InteractionService;
 import org.apache.isis.applib.services.metrics.MetricsService;
 import org.apache.isis.applib.services.publishing.spi.EntityChanges;
 import org.apache.isis.applib.services.publishing.spi.EntityPropertyChange;
@@ -85,6 +88,7 @@ import lombok.extern.log4j.Log4j2;
  * service <i>is</i> interaction-scoped, a new instance of the service is created for each interaction, and so the
  * data held in this service is private to each user's interaction.
  * </p>
+ *
  * @since 2.0 {@index}
  */
 @Service
@@ -102,6 +106,10 @@ implements
     HasEnlistedEntityChanges {
 
 
+    private final EntityPropertyChangePublisher entityPropertyChangePublisher;
+    private final EntityChangesPublisher entityChangesPublisher;
+    private final Provider<InteractionProvider> interactionProviderProvider;
+
     /**
      * Contains a record for every objectId/propertyId that was changed.
      */
@@ -122,9 +130,17 @@ implements
     private final LongAdder entityChangeEventCount = new LongAdder();
     private final AtomicBoolean persistentChangesEncountered = new AtomicBoolean();
 
-    private final EntityPropertyChangePublisher entityPropertyChangePublisher;
-    private final EntityChangesPublisher entityChangesPublisher;
-    private final Provider<InteractionProvider> interactionProviderProvider;
+
+    @Override
+    public void destroy() throws Exception {
+        enlistedPropertyChangeRecordsById.clear();
+        entityPropertyChangeRecordsForPublishing.clear();
+        changeKindByEnlistedAdapter.clear();
+
+        numberEntitiesLoaded.reset();
+        entityChangeEventCount.reset();
+        persistentChangesEncountered.set(false);
+    }
 
     Set<PropertyChangeRecord> snapshotPropertyChangeRecords() {
         // this code path has side-effects, it locks the result for this transaction,
@@ -172,11 +188,58 @@ implements
     }
 
     /**
-     * TRANSACTION END BOUNDARY
-     * @apiNote intended to be called during before transaction completion by the framework internally
+     * Subscribes to transactions and forwards onto the current interaction's EntityChangeTracker, if available.
+     *
+     * <p>
+     *     Note that this service has singleton-scope, unlike {@link EntityChangeTrackerDefault} which has
+     *     {@link InteractionScope interaction scope}. The problem with using {@link EntityChangeTrackerDefault} as
+     *     the direct subscriber is that if there's no {@link Interaction}, then Spring will fail to activate an instance resulting in an
+     *     {@link org.springframework.beans.factory.support.ScopeNotActiveException}.  Now, admittedly that exception
+     *     gets swallowed in the call stack somewhere, but it's still not pretty.
+     * </p>
+     *
+     * <p>
+     *     This design, instead, at least lets us check if there's an interaction in scope, and effectively ignore
+     *     the call if not.
+     * </p>
      */
-    @EventListener(value = TransactionBeforeCompletionEvent.class) @Order(PriorityPrecedence.LATE)
-    public void onTransactionCompleting(final TransactionBeforeCompletionEvent event) {
+    @Component
+    @Named("isis.persistence.commons.EntityChangeTrackerDefault.TransactionSubscriber")
+    @Priority(PriorityPrecedence.EARLY)
+    @Qualifier("default")
+    @RequiredArgsConstructor(onConstructor_ = {@Inject})
+    public static class TransactionSubscriber {
+
+        private final InteractionService interactionService;
+        private final Provider<EntityChangeTrackerDefault> entityChangeTrackerProvider;
+
+        /**
+         * TRANSACTION END BOUNDARY
+         * @apiNote intended to be called during before transaction completion by the framework internally
+         */
+        @EventListener(value = TransactionBeforeCompletionEvent.class)
+        @Order(PriorityPrecedence.LATE)
+        public void onTransactionCompleting(final TransactionBeforeCompletionEvent event) {
+
+            if(!interactionService.isInInteraction()) {
+                // discard request is there is no interaction in scope.
+                // this shouldn't ever really occur, but some low-level (could be improved?) integration tests do
+                // hit this case.
+                return;
+            }
+            entityChangeTracker().onTransactionCompleting(event);
+        }
+
+        private EntityChangeTrackerDefault entityChangeTracker() {
+            return entityChangeTrackerProvider.get();
+        }
+    }
+
+    /**
+     * As called by {@link TransactionSubscriber}, so long as there is an {@link Interaction} in
+     * {@link InteractionScope scope}.
+     */
+    void onTransactionCompleting(final TransactionBeforeCompletionEvent event) {
         try {
             doPublish();
         } finally {
@@ -392,6 +455,4 @@ implements
     }
 
 
-
-
 }
diff --git a/persistence/jdo/datanucleus/src/main/java/org/apache/isis/persistence/jdo/datanucleus/IsisModulePersistenceJdoDatanucleus.java b/persistence/jdo/datanucleus/src/main/java/org/apache/isis/persistence/jdo/datanucleus/IsisModulePersistenceJdoDatanucleus.java
index e127e956a1..d9ad45994c 100644
--- a/persistence/jdo/datanucleus/src/main/java/org/apache/isis/persistence/jdo/datanucleus/IsisModulePersistenceJdoDatanucleus.java
+++ b/persistence/jdo/datanucleus/src/main/java/org/apache/isis/persistence/jdo/datanucleus/IsisModulePersistenceJdoDatanucleus.java
@@ -42,6 +42,7 @@ import org.springframework.dao.support.PersistenceExceptionTranslator;
 import org.springframework.transaction.interceptor.TransactionInterceptor;
 
 import org.apache.isis.applib.services.eventbus.EventBusService;
+import org.apache.isis.applib.services.iactnlayer.InteractionService;
 import org.apache.isis.commons.internal.assertions._Assert;
 import org.apache.isis.commons.internal.base._NullSafe;
 import org.apache.isis.core.config.IsisConfiguration;
@@ -157,7 +158,7 @@ public class IsisModulePersistenceJdoDatanucleus {
             final IsisConfiguration isisConfiguration,
             final DataSource dataSource,
             final MetaModelContext metaModelContext,
-            final EventBusService eventBusService,
+            final InteractionService interactionService,
             final ObjectLifecyclePublisher objectLifecyclePublisher,
             final Provider<EntityChangeTracker> entityChangeTrackerProvider,
             final IsisBeanTypeRegistry beanTypeRegistry,
@@ -175,14 +176,14 @@ public class IsisModulePersistenceJdoDatanucleus {
                 val pu = createDefaultPersistenceUnit(beanTypeRegistry);
                 val pmf = new JDOPersistenceManagerFactory(pu, props);
                 pmf.setConnectionFactory(dataSource);
-                integrateWithApplicationLayer(metaModelContext, entityChangeTrackerProvider, objectLifecyclePublisher, pmf);
+                integrateWithApplicationLayer(metaModelContext, entityChangeTrackerProvider, objectLifecyclePublisher, interactionService, pmf);
                 return pmf;
             }
             @Override
             protected PersistenceManagerFactory newPersistenceManagerFactory(final String name) {
                 val pmf = super.newPersistenceManagerFactory(name);
                 pmf.setConnectionFactory(dataSource); //might be too late, anyway, not sure if this is ever called
-                integrateWithApplicationLayer(metaModelContext, entityChangeTrackerProvider, objectLifecyclePublisher, pmf);
+                integrateWithApplicationLayer(metaModelContext, entityChangeTrackerProvider, objectLifecyclePublisher, interactionService, pmf);
                 return pmf;
             }
         };
@@ -334,12 +335,13 @@ public class IsisModulePersistenceJdoDatanucleus {
             final MetaModelContext metaModelContext,
             final Provider<EntityChangeTracker> entityChangeTrackerProvider,
             final ObjectLifecyclePublisher objectLifecyclePublisher,
+            final InteractionService interactionService,
             final PersistenceManagerFactory pmf) {
 
         // install JDO specific entity change listeners ...
 
         val jdoLifecycleListener =
-                new JdoLifecycleListener(metaModelContext, entityChangeTrackerProvider, objectLifecyclePublisher);
+                new JdoLifecycleListener(metaModelContext, entityChangeTrackerProvider, objectLifecyclePublisher, interactionService);
         pmf.addInstanceLifecycleListener(jdoLifecycleListener, (Class[]) null);
 
     }
diff --git a/persistence/jdo/datanucleus/src/main/java/org/apache/isis/persistence/jdo/datanucleus/changetracking/JdoLifecycleListener.java b/persistence/jdo/datanucleus/src/main/java/org/apache/isis/persistence/jdo/datanucleus/changetracking/JdoLifecycleListener.java
index 79f182388d..5eafecd564 100644
--- a/persistence/jdo/datanucleus/src/main/java/org/apache/isis/persistence/jdo/datanucleus/changetracking/JdoLifecycleListener.java
+++ b/persistence/jdo/datanucleus/src/main/java/org/apache/isis/persistence/jdo/datanucleus/changetracking/JdoLifecycleListener.java
@@ -32,15 +32,13 @@ import javax.jdo.listener.StoreLifecycleListener;
 
 import org.datanucleus.enhancement.Persistable;
 
-import org.apache.isis.applib.services.eventbus.EventBusService;
+import org.apache.isis.applib.services.iactnlayer.InteractionService;
 import org.apache.isis.core.metamodel.context.MetaModelContext;
 import org.apache.isis.core.metamodel.facets.object.publish.entitychange.EntityChangePublishingFacet;
 import org.apache.isis.core.metamodel.objectmanager.ObjectManager.EntityAdaptingMode;
 import org.apache.isis.core.metamodel.services.objectlifecycle.ObjectLifecyclePublisher;
 import org.apache.isis.core.metamodel.spec.ManagedObject;
 import org.apache.isis.core.transaction.changetracking.EntityChangeTracker;
-import org.apache.isis.core.transaction.changetracking.events.PostStoreEvent;
-import org.apache.isis.core.transaction.changetracking.events.PreStoreEvent;
 
 import lombok.NonNull;
 import lombok.RequiredArgsConstructor;
@@ -69,6 +67,7 @@ DetachLifecycleListener, DirtyLifecycleListener, LoadLifecycleListener, StoreLif
     private final @NonNull MetaModelContext metaModelContext;
     private final @NonNull Provider<EntityChangeTracker> entityChangeTrackerProvider;
     private final @NonNull ObjectLifecyclePublisher objectLifecyclePublisher;
+    private final @NonNull InteractionService interactionService;
 
     // -- CALLBACKS
 
@@ -210,8 +209,8 @@ DetachLifecycleListener, DirtyLifecycleListener, LoadLifecycleListener, StoreLif
 
     // -- DEPENDENCIES
 
-    private EntityChangeTracker getEntityChangeTracker() {
-        return entityChangeTrackerProvider.get();
+    private EntityChangeTracker entityChangeTracker() {
+        return interactionService.isInInteraction() ? entityChangeTrackerProvider.get() : EntityChangeTracker.NOOP;
     }
 
 }
diff --git a/regressiontests/stable-persistence-jpa/src/test/java/org/apache/isis/testdomain/persistence/jpa/JpaBootstrappingTest.java b/regressiontests/stable-persistence-jpa/src/test/java/org/apache/isis/testdomain/persistence/jpa/JpaBootstrappingTest.java
index aea9fc23cc..1f409f9306 100644
--- a/regressiontests/stable-persistence-jpa/src/test/java/org/apache/isis/testdomain/persistence/jpa/JpaBootstrappingTest.java
+++ b/regressiontests/stable-persistence-jpa/src/test/java/org/apache/isis/testdomain/persistence/jpa/JpaBootstrappingTest.java
@@ -60,7 +60,7 @@ import lombok.val;
         })
 @TestPropertySource(IsisPresets.UseLog4j2Test)
 @Transactional @TestMethodOrder(MethodOrderer.OrderAnnotation.class)
- @DirtiesContext // doesn't seem to tidy up correctly ... I see InteractionService still injected into entities in the _next_ tests run (JpaExceptionTranslationTest_usingTransactional)
+// @DirtiesContext // doesn't seem to tidy up correctly ... I see InteractionService still injected into entities in the _next_ tests run (JpaExceptionTranslationTest_usingTransactional)
 class JpaBootstrappingTest extends IsisIntegrationTestAbstract {
 
     @Inject private Optional<PlatformTransactionManager> platformTransactionManager;
diff --git a/viewers/wicket/viewer/src/main/java/org/apache/isis/viewer/wicket/viewer/wicketapp/TargetRespondListenerToResetQueryResultCache.java b/viewers/wicket/viewer/src/main/java/org/apache/isis/viewer/wicket/viewer/wicketapp/TargetRespondListenerToResetQueryResultCache.java
index 4cc1f1dd8a..3bb2cd8588 100644
--- a/viewers/wicket/viewer/src/main/java/org/apache/isis/viewer/wicket/viewer/wicketapp/TargetRespondListenerToResetQueryResultCache.java
+++ b/viewers/wicket/viewer/src/main/java/org/apache/isis/viewer/wicket/viewer/wicketapp/TargetRespondListenerToResetQueryResultCache.java
@@ -19,6 +19,7 @@
 package org.apache.isis.viewer.wicket.viewer.wicketapp;
 
 import javax.inject.Inject;
+import javax.inject.Provider;
 
 import org.apache.wicket.ajax.AjaxRequestTarget;
 
@@ -29,7 +30,7 @@ import lombok.extern.log4j.Log4j2;
 @Log4j2
 class TargetRespondListenerToResetQueryResultCache implements AjaxRequestTarget.ITargetRespondListener {
 
-    @Inject private javax.inject.Provider<QueryResultsCache> queryResultsCacheProvider;
+    @Inject private Provider<QueryResultsCache> queryResultsCacheProvider;
 
     @Override
     public void onTargetRespond(final AjaxRequestTarget target) {


[isis] 06/12: ISIS-3110: fixes integ test

Posted by da...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

danhaywood pushed a commit to branch ISIS-3110
in repository https://gitbox.apache.org/repos/asf/isis.git

commit 776c7465b12820b3943da7a2c10e09965349605f
Author: Dan Haywood <da...@haywood-associates.co.uk>
AuthorDate: Thu Aug 4 06:45:59 2022 +0100

    ISIS-3110: fixes integ test
---
 .../IsisModuleExtExecutionOutboxPersistenceJpa.java  |  3 +++
 .../integtests/OutboxRestClient_IntegTest.java       | 20 +++++++++++---------
 2 files changed, 14 insertions(+), 9 deletions(-)

diff --git a/extensions/core/executionoutbox/persistence-jpa/src/main/java/org/apache/isis/extensions/executionoutbox/jpa/IsisModuleExtExecutionOutboxPersistenceJpa.java b/extensions/core/executionoutbox/persistence-jpa/src/main/java/org/apache/isis/extensions/executionoutbox/jpa/IsisModuleExtExecutionOutboxPersistenceJpa.java
index c7929ec451..76521823bf 100644
--- a/extensions/core/executionoutbox/persistence-jpa/src/main/java/org/apache/isis/extensions/executionoutbox/jpa/IsisModuleExtExecutionOutboxPersistenceJpa.java
+++ b/extensions/core/executionoutbox/persistence-jpa/src/main/java/org/apache/isis/extensions/executionoutbox/jpa/IsisModuleExtExecutionOutboxPersistenceJpa.java
@@ -18,10 +18,13 @@
  */
 package org.apache.isis.extensions.executionoutbox.jpa;
 
+import javax.inject.Inject;
+
 import org.springframework.boot.autoconfigure.domain.EntityScan;
 import org.springframework.context.annotation.Configuration;
 import org.springframework.context.annotation.Import;
 
+import org.apache.isis.applib.services.iactnlayer.InteractionService;
 import org.apache.isis.extensions.executionoutbox.applib.IsisModuleExtExecutionOutboxApplib;
 import org.apache.isis.extensions.executionoutbox.jpa.dom.ExecutionOutboxEntry;
 import org.apache.isis.extensions.executionoutbox.jpa.dom.ExecutionOutboxEntryPK;
diff --git a/extensions/core/executionoutbox/restclient/src/test/java/org/apache/isis/extensions/executionoutbox/restclient/integtests/OutboxRestClient_IntegTest.java b/extensions/core/executionoutbox/restclient/src/test/java/org/apache/isis/extensions/executionoutbox/restclient/integtests/OutboxRestClient_IntegTest.java
index 1c3db06c86..0ba779bfe4 100644
--- a/extensions/core/executionoutbox/restclient/src/test/java/org/apache/isis/extensions/executionoutbox/restclient/integtests/OutboxRestClient_IntegTest.java
+++ b/extensions/core/executionoutbox/restclient/src/test/java/org/apache/isis/extensions/executionoutbox/restclient/integtests/OutboxRestClient_IntegTest.java
@@ -106,19 +106,21 @@ public class OutboxRestClient_IntegTest  {
 
     @BeforeEach
     void beforeEach() {
-        transactionService.runTransactional(Propagation.REQUIRED, () -> {
-            counterRepository.removeAll();
-            executionOutboxEntryRepository.removeAll();
+        interactionService.runAnonymous(() -> {
+            transactionService.runTransactional(Propagation.REQUIRED, () -> {
+                counterRepository.removeAll();
+                executionOutboxEntryRepository.removeAll();
 
-            assertThat(counterRepository.find()).isEmpty();
+                assertThat(counterRepository.find()).isEmpty();
 
-            counter1 = counterRepository.persist(Counter.builder().name("counter-1").build());
-            counter2 = counterRepository.persist(Counter.builder().name("counter-2").build());
+                counter1 = counterRepository.persist(Counter.builder().name("counter-1").build());
+                counter2 = counterRepository.persist(Counter.builder().name("counter-2").build());
 
-            assertThat(counterRepository.find()).hasSize(2);
+                assertThat(counterRepository.find()).hasSize(2);
 
-            List<? extends ExecutionOutboxEntry> all = executionOutboxEntryRepository.findOldest();
-            assertThat(all).isEmpty();
+                List<? extends ExecutionOutboxEntry> all = executionOutboxEntryRepository.findOldest();
+                assertThat(all).isEmpty();
+            });
         });
 
         outboxClient = restEndpointService.newClient(port, "any", "any-password-because-security-bypass-module-is-configured")


[isis] 09/12: ISIS-3110: fixes up integ test issues

Posted by da...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

danhaywood pushed a commit to branch ISIS-3110
in repository https://gitbox.apache.org/repos/asf/isis.git

commit be76012fbaa393105dd980804a824df3e92dfb54
Author: Dan Haywood <da...@haywood-associates.co.uk>
AuthorDate: Thu Aug 4 08:08:22 2022 +0100

    ISIS-3110: fixes up integ test issues
---
 .../changetracking/EntityChangeTrackerDefault.java         | 14 ++++++++++++--
 .../testdomain/persistence/jpa/JpaBootstrappingTest.java   |  3 ++-
 .../JpaExceptionTranslationTest_usingTransactional.java    |  1 +
 3 files changed, 15 insertions(+), 3 deletions(-)

diff --git a/persistence/commons/src/main/java/org/apache/isis/persistence/jpa/integration/changetracking/EntityChangeTrackerDefault.java b/persistence/commons/src/main/java/org/apache/isis/persistence/jpa/integration/changetracking/EntityChangeTrackerDefault.java
index 8c9450a0c8..620f5c96ec 100644
--- a/persistence/commons/src/main/java/org/apache/isis/persistence/jpa/integration/changetracking/EntityChangeTrackerDefault.java
+++ b/persistence/commons/src/main/java/org/apache/isis/persistence/jpa/integration/changetracking/EntityChangeTrackerDefault.java
@@ -37,11 +37,11 @@ import org.springframework.core.annotation.Order;
 import org.springframework.lang.Nullable;
 import org.springframework.stereotype.Service;
 
+import org.apache.isis.applib.annotation.DomainObject;
 import org.apache.isis.applib.annotation.EntityChangeKind;
 import org.apache.isis.applib.annotation.InteractionScope;
 import org.apache.isis.applib.annotation.PriorityPrecedence;
 import org.apache.isis.applib.services.bookmark.Bookmark;
-import org.apache.isis.applib.services.eventbus.EventBusService;
 import org.apache.isis.applib.services.iactn.Interaction;
 import org.apache.isis.applib.services.iactn.InteractionProvider;
 import org.apache.isis.applib.services.metrics.MetricsService;
@@ -75,13 +75,23 @@ import lombok.val;
 import lombok.extern.log4j.Log4j2;
 
 /**
+ * This service keeps track of all of the changes within a transactoin, for entities for which entity property change
+ * publishing is enabled (typically using the
+ * {@link DomainObject#entityChangePublishing() @DomainObject(entityChangePublishing=)} annotation attribute.
+ *
+ * <p>
+ * The service is {@link InteractionScope}d.  In theory this could happen multiple times per interaction, so the
+ * data structures are cleared on each commit for potential reuse within the same interaction.  (Of course, because the
+ * service <i>is</i> interaction-scoped, a new instance of the service is created for each interaction, and so the
+ * data held in this service is private to each user's interaction.
+ * </p>
  * @since 2.0 {@index}
  */
 @Service
 @Named("isis.persistence.commons.EntityChangeTrackerDefault")
 @Priority(PriorityPrecedence.EARLY)
 @Qualifier("default")
-@InteractionScope
+@InteractionScope   // see note above regarding this
 @RequiredArgsConstructor(onConstructor_ = {@Inject})
 @Log4j2
 public class EntityChangeTrackerDefault
diff --git a/regressiontests/stable-persistence-jpa/src/test/java/org/apache/isis/testdomain/persistence/jpa/JpaBootstrappingTest.java b/regressiontests/stable-persistence-jpa/src/test/java/org/apache/isis/testdomain/persistence/jpa/JpaBootstrappingTest.java
index 26ff00f9da..aea9fc23cc 100644
--- a/regressiontests/stable-persistence-jpa/src/test/java/org/apache/isis/testdomain/persistence/jpa/JpaBootstrappingTest.java
+++ b/regressiontests/stable-persistence-jpa/src/test/java/org/apache/isis/testdomain/persistence/jpa/JpaBootstrappingTest.java
@@ -29,6 +29,7 @@ import org.junit.jupiter.api.Order;
 import org.junit.jupiter.api.Test;
 import org.junit.jupiter.api.TestMethodOrder;
 import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.test.annotation.DirtiesContext;
 import org.springframework.test.annotation.Rollback;
 import org.springframework.test.context.TestPropertySource;
 import org.springframework.transaction.PlatformTransactionManager;
@@ -59,7 +60,7 @@ import lombok.val;
         })
 @TestPropertySource(IsisPresets.UseLog4j2Test)
 @Transactional @TestMethodOrder(MethodOrderer.OrderAnnotation.class)
-//@DirtiesContext
+ @DirtiesContext // doesn't seem to tidy up correctly ... I see InteractionService still injected into entities in the _next_ tests run (JpaExceptionTranslationTest_usingTransactional)
 class JpaBootstrappingTest extends IsisIntegrationTestAbstract {
 
     @Inject private Optional<PlatformTransactionManager> platformTransactionManager;
diff --git a/regressiontests/stable-persistence-jpa/src/test/java/org/apache/isis/testdomain/persistence/jpa/JpaExceptionTranslationTest_usingTransactional.java b/regressiontests/stable-persistence-jpa/src/test/java/org/apache/isis/testdomain/persistence/jpa/JpaExceptionTranslationTest_usingTransactional.java
index 371bc9cd0a..9ac3e2f1ce 100644
--- a/regressiontests/stable-persistence-jpa/src/test/java/org/apache/isis/testdomain/persistence/jpa/JpaExceptionTranslationTest_usingTransactional.java
+++ b/regressiontests/stable-persistence-jpa/src/test/java/org/apache/isis/testdomain/persistence/jpa/JpaExceptionTranslationTest_usingTransactional.java
@@ -30,6 +30,7 @@ import org.junit.jupiter.api.Test;
 import org.junit.jupiter.api.TestMethodOrder;
 import org.springframework.boot.test.context.SpringBootTest;
 import org.springframework.dao.DataIntegrityViolationException;
+import org.springframework.test.annotation.DirtiesContext;
 import org.springframework.test.annotation.Rollback;
 import org.springframework.test.context.TestPropertySource;
 import org.springframework.test.context.TestPropertySources;


[isis] 05/12: ISIS-3110: fixes JDO aud trail integ test

Posted by da...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

danhaywood pushed a commit to branch ISIS-3110
in repository https://gitbox.apache.org/repos/asf/isis.git

commit 8a48540269ea048a0ca6b9527937f57153467a9a
Author: Dan Haywood <da...@haywood-associates.co.uk>
AuthorDate: Wed Aug 3 22:27:38 2022 +0100

    ISIS-3110: fixes JDO aud trail integ test
---
 .../objectlifecycle/PropertyChangeRecord.java      | 63 +++++++++------
 .../changetracking/EntityChangeTrackerDefault.java | 90 ++++++++++------------
 .../jpa/applib/integration/IsisEntityListener.java |  7 +-
 3 files changed, 84 insertions(+), 76 deletions(-)

diff --git a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/services/objectlifecycle/PropertyChangeRecord.java b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/services/objectlifecycle/PropertyChangeRecord.java
index 5afa259bb5..b93cab05dc 100644
--- a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/services/objectlifecycle/PropertyChangeRecord.java
+++ b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/services/objectlifecycle/PropertyChangeRecord.java
@@ -38,34 +38,43 @@ import lombok.val;
 @ToString(of = {"id"})
 public final class PropertyChangeRecord {
 
-    @Getter
-    private final PropertyChangeRecordId id;
+    @Getter private final PropertyChangeRecordId id;
+    @Getter private PreAndPostValue preAndPostValue;
 
     public ManagedObject getEntity() {return id.getEntity();}
     public OneToOneAssociation getProperty() {return id.getProperty();}
     public Bookmark getBookmark() {return id.getBookmark();}
     public String getPropertyId() {return id.getPropertyId();}
 
-    @Getter private PreAndPostValue preAndPostValue;
 
+    public static PropertyChangeRecord ofNew(
+            final @NonNull PropertyChangeRecordId pcrId) {
+        return new PropertyChangeRecord(pcrId)
+                        .withPreValueSetToNew();
+    }
 
-    public static @NonNull PropertyChangeRecord of(
-            final @NonNull PropertyChangeRecordId id) {
-        return new PropertyChangeRecord(id, PreAndPostValue.pre(PropertyValuePlaceholder.NEW));
+    public static PropertyChangeRecord ofCurrent(
+            final @NonNull PropertyChangeRecordId pcrId) {
+        return new PropertyChangeRecord(pcrId)
+                        .withPreValueSetToCurrent();
     }
 
-    public static PropertyChangeRecord of(
-            final @NonNull PropertyChangeRecordId id,
-            final @NonNull PreAndPostValue preAndPostValue) {
-        return new PropertyChangeRecord(id, preAndPostValue);
+    public static PropertyChangeRecord ofCurrent(
+            final @NonNull PropertyChangeRecordId pcrId,
+            final Object currentValue) {
+        return new PropertyChangeRecord(pcrId)
+                        .withPreValueSetTo(currentValue);
     }
 
-    private PropertyChangeRecord(
-            final @NonNull PropertyChangeRecordId id,
-            final PreAndPostValue preAndPostValue) {
+    public static PropertyChangeRecord ofDeleting(
+            final @NonNull PropertyChangeRecordId id) {
+        return new PropertyChangeRecord(id)
+                        .withPreValueSetToCurrent()
+                        .withPostValueSetToDeleted();
+    }
 
+    private PropertyChangeRecord(final @NonNull PropertyChangeRecordId id) {
         this.id = id;
-        this.preAndPostValue = preAndPostValue;
     }
 
     public String getLogicalMemberIdentifier() {
@@ -74,20 +83,30 @@ public final class PropertyChangeRecord {
         return target.getLogicalTypeName() + "#" + propertyId;
     }
 
-    public void updatePreValueAsNew() {
-        preAndPostValue = PreAndPostValue.pre(PropertyValuePlaceholder.NEW);
+    public PropertyChangeRecord withPreValueSetToNew() {
+        return withPreValueSetTo(PropertyValuePlaceholder.NEW);
+    }
+
+    public PropertyChangeRecord withPreValueSetToCurrent() {
+        return withPreValueSetTo(getPropertyValue());
+    }
+
+    public PropertyChangeRecord withPostValueSetToCurrent() {
+        return withPostValueSetTo(getPropertyValue());
     }
 
-    public void updatePreValueWithCurrent() {
-        preAndPostValue = PreAndPostValue.pre(getPropertyValue());
+    public PropertyChangeRecord withPostValueSetToDeleted() {
+        return withPostValueSetTo(PropertyValuePlaceholder.DELETED);
     }
 
-    public void updatePostValueWithCurrent() {
-        preAndPostValue = preAndPostValue.withPost(getPropertyValue());
+    private PropertyChangeRecord withPreValueSetTo(Object preValue) {
+        this.preAndPostValue = PreAndPostValue.pre(preValue);
+        return this;
     }
 
-    public void updatePostValueAsDeleted() {
-        preAndPostValue = preAndPostValue.withPost(PropertyValuePlaceholder.DELETED);
+    private PropertyChangeRecord withPostValueSetTo(Object postValue) {
+        this.preAndPostValue = preAndPostValue.withPost(postValue);
+        return this;
     }
 
 
diff --git a/persistence/commons/src/main/java/org/apache/isis/persistence/jpa/integration/changetracking/EntityChangeTrackerDefault.java b/persistence/commons/src/main/java/org/apache/isis/persistence/jpa/integration/changetracking/EntityChangeTrackerDefault.java
index 5a429a9f96..dbf8a49d21 100644
--- a/persistence/commons/src/main/java/org/apache/isis/persistence/jpa/integration/changetracking/EntityChangeTrackerDefault.java
+++ b/persistence/commons/src/main/java/org/apache/isis/persistence/jpa/integration/changetracking/EntityChangeTrackerDefault.java
@@ -25,7 +25,6 @@ import java.util.Optional;
 import java.util.Set;
 import java.util.concurrent.atomic.AtomicBoolean;
 import java.util.concurrent.atomic.LongAdder;
-import java.util.function.Consumer;
 
 import javax.annotation.Priority;
 import javax.inject.Inject;
@@ -109,7 +108,6 @@ implements
     @Getter(AccessLevel.PACKAGE)
     private final Map<Bookmark, EntityChangeKind> changeKindByEnlistedAdapter = _Maps.newLinkedHashMap();
 
-
     private final EntityPropertyChangePublisher entityPropertyChangePublisher;
     private final EntityChangesPublisher entityChangesPublisher;
     private final Provider<InteractionProvider> interactionProviderProvider;
@@ -130,18 +128,37 @@ implements
         this.interactionProviderProvider = interactionProviderProvider;
     }
 
-    private boolean isEnlistedWrtChangeKind(final @NonNull ManagedObject adapter) {
-        return ManagedObjects.bookmark(adapter)
-        .map(changeKindByEnlistedAdapter::containsKey)
-        .orElse(false);
-    }
-
     Set<PropertyChangeRecord> snapshotPropertyChangeRecords() {
         // this code path has side-effects, it locks the result for this transaction,
         // such that cannot enlist on top of it
         return entityPropertyChangeRecordsForPublishing.get();
     }
 
+    /**
+     * For any enlisted Object Properties collects those, that are meant for publishing,
+     * then clears enlisted objects.
+     */
+    private Set<PropertyChangeRecord> capturePostValuesAndDrain() {
+
+        val records = enlistedPropertyChangeRecordsById.values().stream()
+                // set post values, which have been left empty up to now
+                .peek(rec -> {
+                    // assuming this check correctly detects deleted entities (JDO)
+                    if(ManagedObjects.EntityUtil.isDetachedOrRemoved(rec.getEntity())) {
+                        rec.withPostValueSetToDeleted();
+                    } else {
+                        rec.withPostValueSetToCurrent();
+                    }
+                })
+                .filter(managedProperty->managedProperty.getPreAndPostValue().shouldPublish())
+                .collect(_Sets.toUnmodifiable());
+
+        enlistedPropertyChangeRecordsById.clear();
+
+        return records;
+
+    }
+
     private boolean isEntityExcludedForChangePublishing(ManagedObject entity) {
 
         if(!EntityChangePublishingFacet.isPublishingEnabled(entity.getSpecification())) {
@@ -264,30 +281,6 @@ implements
         return false;
     }
 
-    /**
-     * For any enlisted Object Properties collects those, that are meant for publishing,
-     * then clears enlisted objects.
-     */
-    private Set<PropertyChangeRecord> capturePostValuesAndDrain() {
-
-        val records = enlistedPropertyChangeRecordsById.values().stream()
-                // set post values, which have been left empty up to now
-                .peek(rec -> {
-                    // assuming this check correctly detects deleted entities (JDO)
-                    if(ManagedObjects.EntityUtil.isDetachedOrRemoved(rec.getEntity())) {
-                        rec.updatePostValueAsDeleted();
-                    } else {
-                        rec.updatePostValueWithCurrent();
-                    }
-                })
-                .filter(managedProperty->managedProperty.getPreAndPostValue().shouldPublish())
-                .collect(_Sets.toUnmodifiable());
-
-        enlistedPropertyChangeRecordsById.clear();
-
-        return records;
-
-    }
 
     // side-effect free, used by XRay
     long countPotentialPropertyChangeRecords() {
@@ -308,7 +301,11 @@ implements
         log.debug("enlist entity's property changes for publishing {}", entity);
         enlistForChangeKindPublishing(entity, EntityChangeKind.CREATE);
 
-        enlistForCreateOrUpdate(entity, PropertyChangeRecord::updatePreValueAsNew);
+        entity.getSpecification().streamProperties(MixedIn.EXCLUDED)
+                .filter(property->!EntityPropertyChangePublishingPolicyFacet.isExcludedFromPublishing(property))
+                .map(property -> PropertyChangeRecordId.of(entity, property))
+                .filter(pcrId -> ! enlistedPropertyChangeRecordsById.containsKey(pcrId)) // only if not previously seen
+                .forEach(pcrId -> enlistedPropertyChangeRecordsById.put(pcrId, PropertyChangeRecord.ofNew(pcrId)));
     }
 
     @Override
@@ -331,25 +328,22 @@ implements
             ormPropertyChangeRecords
                     .stream()
                     .filter(pcr -> !EntityPropertyChangePublishingPolicyFacet.isExcludedFromPublishing(pcr.getProperty()))
-                    .forEach(pcr -> this.enlistedPropertyChangeRecordsById.put(pcr.getId(), pcr)); // if already known, then we don't replace (keep first pre-value we know about)
+                    .filter(pcr -> ! enlistedPropertyChangeRecordsById.containsKey(pcr.getId())) // only if not previously seen
+                    .forEach(pcr -> this.enlistedPropertyChangeRecordsById.put(pcr.getId(), pcr));
         } else {
             // home-grown approach
             log.debug("enlist entity's property changes for publishing {}", entity);
 
-            enlistForCreateOrUpdate(entity, PropertyChangeRecord::updatePreValueWithCurrent);
+            entity.getSpecification().streamProperties(MixedIn.EXCLUDED)
+                    .filter(property->!EntityPropertyChangePublishingPolicyFacet.isExcludedFromPublishing(property))
+                    .map(property -> PropertyChangeRecordId.of(entity, property))
+                    .filter(pcrId -> ! enlistedPropertyChangeRecordsById.containsKey(pcrId)) // only if not previously seen
+                    .map(pcrId -> enlistedPropertyChangeRecordsById.put(pcrId, PropertyChangeRecord.ofCurrent(pcrId)))
+                    .filter(Objects::nonNull)   // shouldn't happen, just keeping compiler happy
+                    .forEach(PropertyChangeRecord::withPreValueSetToCurrent);
         }
     }
 
-    private void enlistForCreateOrUpdate(ManagedObject entity, Consumer<PropertyChangeRecord> propertyChangeRecordConsumer) {
-        entity.getSpecification().streamProperties(MixedIn.EXCLUDED)
-                .filter(property->!EntityPropertyChangePublishingPolicyFacet.isExcludedFromPublishing(property))
-                .map(property -> PropertyChangeRecordId.of(entity, property))
-                .filter(pcrId -> ! enlistedPropertyChangeRecordsById.containsKey(pcrId)) // only if not previously seen
-                .map(pcrId -> enlistedPropertyChangeRecordsById.put(pcrId, PropertyChangeRecord.of(pcrId)))
-                .filter(Objects::nonNull)   // shouldn't happen, just keeping compiler happy
-                .forEach(propertyChangeRecordConsumer);
-    }
-
 
     @Override
     public void enlistDeleting(final ManagedObject entity) {
@@ -370,11 +364,7 @@ implements
                     .filter(property -> EntityChangePublishingFacet.isPublishingEnabled(entity.getSpecification()))
                     .filter(property -> !EntityPropertyChangePublishingPolicyFacet.isExcludedFromPublishing(property))
                     .map(property -> PropertyChangeRecordId.of(entity, property))
-                    .map(pcrId -> enlistedPropertyChangeRecordsById.computeIfAbsent(pcrId, PropertyChangeRecord::of))
-                    .forEach(pcr -> {
-                        pcr.updatePreValueWithCurrent();
-                        pcr.updatePostValueAsDeleted();
-                    });
+                    .forEach(pcrId -> enlistedPropertyChangeRecordsById.computeIfAbsent(pcrId, id -> PropertyChangeRecord.ofDeleting(id)));
         }
     }
 
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 f49bc6672c..e6cea2121f 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
@@ -116,10 +116,9 @@ public class IsisEntityListener {
                     return entity
                             .getSpecification()
                             .getProperty(propertyName)
-                            .filter(property->!property.isMixedIn())
-                            .filter(property->!EntityPropertyChangePublishingPolicyFacet.isExcludedFromPublishing(property))
-                            .map(property->PropertyChangeRecord.of(PropertyChangeRecordId.of(entity, property),
-                                                                   PreAndPostValue.pre(ormChangeRecord.getOldValue())))
+                            .filter(property -> !property.isMixedIn())
+                            .filter(property -> !EntityPropertyChangePublishingPolicyFacet.isExcludedFromPublishing(property))
+                            .map(property -> PropertyChangeRecord.ofCurrent(PropertyChangeRecordId.of(entity, property), ormChangeRecord.getOldValue()))
                             .orElse(null); // ignore
                 })
                 .collect(Can.toCan()); // a Can<T> only collects non-null elements


[isis] 11/12: ISIS-3110: removes circular dependency

Posted by da...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

danhaywood pushed a commit to branch ISIS-3110
in repository https://gitbox.apache.org/repos/asf/isis.git

commit 872adf79dabba0ac51b5c0048ca5f467cab33976
Author: Dan Haywood <da...@haywood-associates.co.uk>
AuthorDate: Thu Aug 4 10:23:02 2022 +0100

    ISIS-3110: removes circular dependency
---
 .../datanucleus/IsisModulePersistenceJdoDatanucleus.java   | 14 +++-----------
 .../datanucleus/changetracking/JdoLifecycleListener.java   |  7 -------
 .../isis/testdomain/persistence/jpa/JpaJaxbTest.java       |  2 ++
 3 files changed, 5 insertions(+), 18 deletions(-)

diff --git a/persistence/jdo/datanucleus/src/main/java/org/apache/isis/persistence/jdo/datanucleus/IsisModulePersistenceJdoDatanucleus.java b/persistence/jdo/datanucleus/src/main/java/org/apache/isis/persistence/jdo/datanucleus/IsisModulePersistenceJdoDatanucleus.java
index d9ad45994c..81460561d2 100644
--- a/persistence/jdo/datanucleus/src/main/java/org/apache/isis/persistence/jdo/datanucleus/IsisModulePersistenceJdoDatanucleus.java
+++ b/persistence/jdo/datanucleus/src/main/java/org/apache/isis/persistence/jdo/datanucleus/IsisModulePersistenceJdoDatanucleus.java
@@ -18,7 +18,6 @@
  */
 package org.apache.isis.persistence.jdo.datanucleus;
 
-import java.util.ArrayList;
 import java.util.Collections;
 import java.util.LinkedHashSet;
 import java.util.List;
@@ -41,7 +40,6 @@ import org.springframework.context.annotation.Primary;
 import org.springframework.dao.support.PersistenceExceptionTranslator;
 import org.springframework.transaction.interceptor.TransactionInterceptor;
 
-import org.apache.isis.applib.services.eventbus.EventBusService;
 import org.apache.isis.applib.services.iactnlayer.InteractionService;
 import org.apache.isis.commons.internal.assertions._Assert;
 import org.apache.isis.commons.internal.base._NullSafe;
@@ -158,16 +156,12 @@ public class IsisModulePersistenceJdoDatanucleus {
             final IsisConfiguration isisConfiguration,
             final DataSource dataSource,
             final MetaModelContext metaModelContext,
-            final InteractionService interactionService,
             final ObjectLifecyclePublisher objectLifecyclePublisher,
-            final Provider<EntityChangeTracker> entityChangeTrackerProvider,
             final IsisBeanTypeRegistry beanTypeRegistry,
             final DatanucleusSettings dnSettings) {
 
         _Assert.assertNotNull(dataSource, "a datasource is required");
 
-        final Set<String> classNamesNotEnhanced = new LinkedHashSet<>();
-
         autoCreateSchemas(dataSource, isisConfiguration);
 
         val lpmfBean = new LocalPersistenceManagerFactoryBean() {
@@ -176,14 +170,14 @@ public class IsisModulePersistenceJdoDatanucleus {
                 val pu = createDefaultPersistenceUnit(beanTypeRegistry);
                 val pmf = new JDOPersistenceManagerFactory(pu, props);
                 pmf.setConnectionFactory(dataSource);
-                integrateWithApplicationLayer(metaModelContext, entityChangeTrackerProvider, objectLifecyclePublisher, interactionService, pmf);
+                integrateWithApplicationLayer(metaModelContext, objectLifecyclePublisher, pmf);
                 return pmf;
             }
             @Override
             protected PersistenceManagerFactory newPersistenceManagerFactory(final String name) {
                 val pmf = super.newPersistenceManagerFactory(name);
                 pmf.setConnectionFactory(dataSource); //might be too late, anyway, not sure if this is ever called
-                integrateWithApplicationLayer(metaModelContext, entityChangeTrackerProvider, objectLifecyclePublisher, interactionService, pmf);
+                integrateWithApplicationLayer(metaModelContext, objectLifecyclePublisher, pmf);
                 return pmf;
             }
         };
@@ -333,15 +327,13 @@ public class IsisModulePersistenceJdoDatanucleus {
 
     private static void integrateWithApplicationLayer(
             final MetaModelContext metaModelContext,
-            final Provider<EntityChangeTracker> entityChangeTrackerProvider,
             final ObjectLifecyclePublisher objectLifecyclePublisher,
-            final InteractionService interactionService,
             final PersistenceManagerFactory pmf) {
 
         // install JDO specific entity change listeners ...
 
         val jdoLifecycleListener =
-                new JdoLifecycleListener(metaModelContext, entityChangeTrackerProvider, objectLifecyclePublisher, interactionService);
+                new JdoLifecycleListener(metaModelContext, objectLifecyclePublisher);
         pmf.addInstanceLifecycleListener(jdoLifecycleListener, (Class[]) null);
 
     }
diff --git a/persistence/jdo/datanucleus/src/main/java/org/apache/isis/persistence/jdo/datanucleus/changetracking/JdoLifecycleListener.java b/persistence/jdo/datanucleus/src/main/java/org/apache/isis/persistence/jdo/datanucleus/changetracking/JdoLifecycleListener.java
index 5eafecd564..43f1c480e1 100644
--- a/persistence/jdo/datanucleus/src/main/java/org/apache/isis/persistence/jdo/datanucleus/changetracking/JdoLifecycleListener.java
+++ b/persistence/jdo/datanucleus/src/main/java/org/apache/isis/persistence/jdo/datanucleus/changetracking/JdoLifecycleListener.java
@@ -65,9 +65,7 @@ implements AttachLifecycleListener, ClearLifecycleListener, CreateLifecycleListe
 DetachLifecycleListener, DirtyLifecycleListener, LoadLifecycleListener, StoreLifecycleListener {
 
     private final @NonNull MetaModelContext metaModelContext;
-    private final @NonNull Provider<EntityChangeTracker> entityChangeTrackerProvider;
     private final @NonNull ObjectLifecyclePublisher objectLifecyclePublisher;
-    private final @NonNull InteractionService interactionService;
 
     // -- CALLBACKS
 
@@ -207,10 +205,5 @@ DetachLifecycleListener, DirtyLifecycleListener, LoadLifecycleListener, StoreLif
         return _Utils.adaptEntityAndInjectServices(metaModelContext, pojo, bookmarking);
     }
 
-    // -- DEPENDENCIES
-
-    private EntityChangeTracker entityChangeTracker() {
-        return interactionService.isInInteraction() ? entityChangeTrackerProvider.get() : EntityChangeTracker.NOOP;
-    }
 
 }
diff --git a/regressiontests/stable-persistence-jpa/src/test/java/org/apache/isis/testdomain/persistence/jpa/JpaJaxbTest.java b/regressiontests/stable-persistence-jpa/src/test/java/org/apache/isis/testdomain/persistence/jpa/JpaJaxbTest.java
index 206f59c44a..d96060b8e2 100644
--- a/regressiontests/stable-persistence-jpa/src/test/java/org/apache/isis/testdomain/persistence/jpa/JpaJaxbTest.java
+++ b/regressiontests/stable-persistence-jpa/src/test/java/org/apache/isis/testdomain/persistence/jpa/JpaJaxbTest.java
@@ -24,6 +24,8 @@ import org.junit.jupiter.api.Test;
 import org.springframework.boot.test.context.SpringBootTest;
 import org.springframework.test.context.TestPropertySource;
 
+import org.apache.isis.applib.annotation.InteractionScope;
+import org.apache.isis.applib.services.iactnlayer.InteractionService;
 import org.apache.isis.applib.services.jaxb.JaxbService;
 import org.apache.isis.core.config.presets.IsisPresets;
 import org.apache.isis.testdomain.RegressionTestAbstract;


[isis] 03/12: ISIS-3110: factors out persistence-commons

Posted by da...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

danhaywood pushed a commit to branch ISIS-3110
in repository https://gitbox.apache.org/repos/asf/isis.git

commit 15d4901117b9500367dc5a70ad820fb30abe8053
Author: Dan Haywood <da...@haywood-associates.co.uk>
AuthorDate: Wed Aug 3 13:38:06 2022 +0100

    ISIS-3110: factors out persistence-commons
---
 core/pom.xml                                       |   6 +
 persistence/{jdo/integration => commons}/pom.xml   |  36 +-
 .../commons/IsisModulePersistenceCommons.java}     |  15 +-
 .../EntityChangeTrackerDefault.java}               |  19 +-
 .../changetracking/_ChangingEntitiesFactory.java   |   3 +-
 .../changetracking/_SimpleChangingEntities.java    |   0
 .../jpa/integration/changetracking/_Xray.java      |   3 +-
 persistence/jdo/integration/pom.xml                |  10 +-
 .../IsisModulePersistenceJdoIntegration.java       |   5 +-
 .../changetracking/EntityChangeTrackerJdo.java     | 430 ---------------------
 .../changetracking/_ChangingEntitiesFactory.java   | 143 -------
 .../changetracking/_SimpleChangingEntities.java    | 121 ------
 .../jdo/integration/changetracking/_Xray.java      | 145 -------
 persistence/jpa/integration/pom.xml                |  41 +-
 .../IsisModulePersistenceJpaIntegration.java       |   4 +-
 15 files changed, 68 insertions(+), 913 deletions(-)

diff --git a/core/pom.xml b/core/pom.xml
index f43d342c73..d8d36b8915 100644
--- a/core/pom.xml
+++ b/core/pom.xml
@@ -572,6 +572,11 @@
 				<artifactId>isis-core-codegen-bytebuddy</artifactId>
 				<version>2.0.0-SNAPSHOT</version>
 			</dependency>
+			<dependency>
+				<groupId>org.apache.isis.persistence</groupId>
+				<artifactId>isis-persistence-commons</artifactId>
+				<version>2.0.0-SNAPSHOT</version>
+			</dependency>
 			<dependency>
 				<groupId>org.apache.isis.persistence</groupId>
 				<artifactId>isis-persistence-jdo-applib</artifactId>
@@ -1628,6 +1633,7 @@
 		<module>../viewers/restfulobjects</module>
 		<module>../viewers/wicket</module>
 
+		<module>../persistence/commons</module>
 		<module>../persistence/jdo</module>
 		<module>../persistence/jpa</module>
 
diff --git a/persistence/jdo/integration/pom.xml b/persistence/commons/pom.xml
similarity index 62%
copy from persistence/jdo/integration/pom.xml
copy to persistence/commons/pom.xml
index f684aa5350..b8e9a17c2a 100644
--- a/persistence/jdo/integration/pom.xml
+++ b/persistence/commons/pom.xml
@@ -14,30 +14,27 @@
 	<modelVersion>4.0.0</modelVersion>
 
 	<parent>
-		<groupId>org.apache.isis.persistence</groupId>
-		<artifactId>isis-persistence-jdo</artifactId>
+		<groupId>org.apache.isis.core</groupId>
+		<artifactId>isis-core</artifactId>
 		<version>2.0.0-SNAPSHOT</version>
+		<relativePath>../../core/pom.xml</relativePath>
 	</parent>
 
-	<artifactId>isis-persistence-jdo-integration</artifactId>
+	<groupId>org.apache.isis.persistence</groupId>
+	<artifactId>isis-persistence-commons</artifactId>
 
-	<name>Apache Isis Persistence - JDO (integration)</name>
+	<name>Apache Isis Persistence - Commons</name>
 	<description>
-        JDO Integration (powered by DataNucleus)
+		Apache Isis Common utilities for persistence stacks
     </description>
 
 	<properties>
-		<jar-plugin.automaticModuleName>org.apache.isis.persistence.jdo.integration</jar-plugin.automaticModuleName>
-		<git-plugin.propertiesDir>org/apache/isis/persistence/jdo/integration</git-plugin.propertiesDir>
+		<jar-plugin.automaticModuleName>org.apache.isis.persistence.commons</jar-plugin.automaticModuleName>
+		<git-plugin.propertiesDir>org/apache/isis/persistence/commons</git-plugin.propertiesDir>
 	</properties>
 
 	<dependencies>
 
-<!-- 		<dependency> -->
-<!-- 			<groupId>org.apache.isis.persistence</groupId> -->
-<!-- 			<artifactId>isis-persistence-jdo-provider</artifactId> -->
-<!-- 		</dependency> -->
-
 		<dependency>
 			<groupId>org.apache.isis.commons</groupId>
 			<artifactId>isis-commons</artifactId>
@@ -53,21 +50,6 @@
 			<artifactId>isis-core-runtime</artifactId>
 		</dependency>
 
-		<dependency>
-			<groupId>org.apache.isis.persistence</groupId>
-			<artifactId>isis-persistence-jdo-applib</artifactId>
-		</dependency>
-
-		<dependency>
-			<groupId>org.apache.isis.persistence</groupId>
-			<artifactId>isis-persistence-jdo-metamodel</artifactId>
-		</dependency>
-
-		<dependency>
-			<groupId>org.apache.isis.persistence</groupId>
-			<artifactId>isis-persistence-jdo-spring</artifactId>
-		</dependency>
-
 		<!-- TESTING -->
 
         <dependency>
diff --git a/persistence/jdo/integration/src/main/java/org/apache/isis/persistence/jdo/integration/IsisModulePersistenceJdoIntegration.java b/persistence/commons/src/main/java/org/apache/isis/persistence/commons/IsisModulePersistenceCommons.java
similarity index 68%
copy from persistence/jdo/integration/src/main/java/org/apache/isis/persistence/jdo/integration/IsisModulePersistenceJdoIntegration.java
copy to persistence/commons/src/main/java/org/apache/isis/persistence/commons/IsisModulePersistenceCommons.java
index aa7a5c07db..c2613ac727 100644
--- a/persistence/jdo/integration/src/main/java/org/apache/isis/persistence/jdo/integration/IsisModulePersistenceJdoIntegration.java
+++ b/persistence/commons/src/main/java/org/apache/isis/persistence/commons/IsisModulePersistenceCommons.java
@@ -16,26 +16,23 @@
  *  specific language governing permissions and limitations
  *  under the License.
  */
-package org.apache.isis.persistence.jdo.integration;
+package org.apache.isis.persistence.commons;
 
 import org.springframework.context.annotation.Configuration;
 import org.springframework.context.annotation.Import;
 
 import org.apache.isis.core.runtime.IsisModuleCoreRuntime;
-import org.apache.isis.persistence.jdo.applib.IsisModulePersistenceJdoApplib;
-import org.apache.isis.persistence.jdo.integration.changetracking.EntityChangeTrackerJdo;
-import org.apache.isis.persistence.jdo.metamodel.IsisModulePersistenceJdoMetamodel;
+import org.apache.isis.persistence.jpa.integration.changetracking.EntityChangeTrackerDefault;
 
 @Configuration
 @Import({
         // modules
         IsisModuleCoreRuntime.class,
-        IsisModulePersistenceJdoApplib.class,
-        IsisModulePersistenceJdoMetamodel.class,
 
-        // services
-        EntityChangeTrackerJdo.class,
+        // @Service's
+        EntityChangeTrackerDefault.class,
+
 })
-public class IsisModulePersistenceJdoIntegration {
+public class IsisModulePersistenceCommons {
 
 }
diff --git a/persistence/jpa/integration/src/main/java/org/apache/isis/persistence/jpa/integration/changetracking/EntityChangeTrackerJpa.java b/persistence/commons/src/main/java/org/apache/isis/persistence/jpa/integration/changetracking/EntityChangeTrackerDefault.java
similarity index 98%
rename from persistence/jpa/integration/src/main/java/org/apache/isis/persistence/jpa/integration/changetracking/EntityChangeTrackerJpa.java
rename to persistence/commons/src/main/java/org/apache/isis/persistence/jpa/integration/changetracking/EntityChangeTrackerDefault.java
index 2cee271ff1..91cbbfeaa9 100644
--- a/persistence/jpa/integration/src/main/java/org/apache/isis/persistence/jpa/integration/changetracking/EntityChangeTrackerJpa.java
+++ b/persistence/commons/src/main/java/org/apache/isis/persistence/jpa/integration/changetracking/EntityChangeTrackerDefault.java
@@ -15,6 +15,7 @@
  *  KIND, either express or implied.  See the License for the
  *  specific language governing permissions and limitations
  *  under the License.
+ *
  */
 package org.apache.isis.persistence.jpa.integration.changetracking;
 
@@ -92,12 +93,12 @@ import lombok.extern.log4j.Log4j2;
  * @since 2.0 {@index}
  */
 @Service
-@Named("isis.transaction.EntityChangeTrackerJpa")
+@Named("isis.persistence.commons.EntityChangeTrackerDefault")
 @Priority(PriorityPrecedence.EARLY)
-@Qualifier("jdo")
+@Qualifier("default")
 @InteractionScope
 @Log4j2
-public class EntityChangeTrackerJpa
+public class EntityChangeTrackerDefault
 extends PersistenceCallbackHandlerAbstract
 implements
     MetricsService,
@@ -148,7 +149,7 @@ implements
     private final Provider<InteractionProvider> interactionProviderProvider;
 
     @Inject
-    public EntityChangeTrackerJpa(
+    public EntityChangeTrackerDefault(
             final EntityPropertyChangePublisher entityPropertyChangePublisher,
             final EntityChangesPublisher entityChangesPublisher,
             final EventBusService eventBusService,
@@ -225,7 +226,6 @@ implements
             return false; // ignore entities that are not enabled for entity change publishing
         }
 
-        // if home-grown
         if(entityPropertyChangeRecordsForPublishing.isMemoized()) {
             throw _Exceptions.illegalState("Cannot enlist additional changes for auditing, "
                     + "since changedObjectPropertiesRef was already prepared (memoized) for auditing.");
@@ -260,14 +260,17 @@ implements
 
     private void postPublishing() {
         log.debug("purging entity change records");
+
+        // if ORM provided property change records ... as in JPA
         this.enlistedPropertyChangesOfCreated.clear();
         this.enlistedPropertyChangesOfUpdated.clear();
         this.enlistedPropertyChangesOfDeleted.clear();
 
-//        propertyChangeRecordsById.clear();
-        changeKindByEnlistedAdapter.clear();
-//        entityPropertyChangeRecordsForPublishing.clear();
+        // if instead we had to infer ourselves (home-grown)... as in JDO
+        propertyChangeRecordsById.clear();
+        entityPropertyChangeRecordsForPublishing.clear();
 
+        changeKindByEnlistedAdapter.clear();
         entityChangeEventCount.reset();
         numberEntitiesLoaded.reset();
     }
diff --git a/persistence/jpa/integration/src/main/java/org/apache/isis/persistence/jpa/integration/changetracking/_ChangingEntitiesFactory.java b/persistence/commons/src/main/java/org/apache/isis/persistence/jpa/integration/changetracking/_ChangingEntitiesFactory.java
similarity index 98%
rename from persistence/jpa/integration/src/main/java/org/apache/isis/persistence/jpa/integration/changetracking/_ChangingEntitiesFactory.java
rename to persistence/commons/src/main/java/org/apache/isis/persistence/jpa/integration/changetracking/_ChangingEntitiesFactory.java
index c3e142433f..8b5998f4ac 100644
--- a/persistence/jpa/integration/src/main/java/org/apache/isis/persistence/jpa/integration/changetracking/_ChangingEntitiesFactory.java
+++ b/persistence/commons/src/main/java/org/apache/isis/persistence/jpa/integration/changetracking/_ChangingEntitiesFactory.java
@@ -15,6 +15,7 @@
  *  KIND, either express or implied.  See the License for the
  *  specific language governing permissions and limitations
  *  under the License.
+ *
  */
 package org.apache.isis.persistence.jpa.integration.changetracking;
 
@@ -40,7 +41,7 @@ final class _ChangingEntitiesFactory {
     public static Optional<EntityChanges> createChangingEntities(
             final java.sql.Timestamp completedAt,
             final String userName,
-            final EntityChangeTrackerJpa entityChangeTracker) {
+            final EntityChangeTrackerDefault entityChangeTracker) {
 
         if(entityChangeTracker.getChangeKindByEnlistedAdapter().isEmpty()) {
             return Optional.empty();
diff --git a/persistence/jpa/integration/src/main/java/org/apache/isis/persistence/jpa/integration/changetracking/_SimpleChangingEntities.java b/persistence/commons/src/main/java/org/apache/isis/persistence/jpa/integration/changetracking/_SimpleChangingEntities.java
similarity index 100%
rename from persistence/jpa/integration/src/main/java/org/apache/isis/persistence/jpa/integration/changetracking/_SimpleChangingEntities.java
rename to persistence/commons/src/main/java/org/apache/isis/persistence/jpa/integration/changetracking/_SimpleChangingEntities.java
diff --git a/persistence/jpa/integration/src/main/java/org/apache/isis/persistence/jpa/integration/changetracking/_Xray.java b/persistence/commons/src/main/java/org/apache/isis/persistence/jpa/integration/changetracking/_Xray.java
similarity index 98%
rename from persistence/jpa/integration/src/main/java/org/apache/isis/persistence/jpa/integration/changetracking/_Xray.java
rename to persistence/commons/src/main/java/org/apache/isis/persistence/jpa/integration/changetracking/_Xray.java
index 50955f25fb..7ee2ef86da 100644
--- a/persistence/jpa/integration/src/main/java/org/apache/isis/persistence/jpa/integration/changetracking/_Xray.java
+++ b/persistence/commons/src/main/java/org/apache/isis/persistence/jpa/integration/changetracking/_Xray.java
@@ -15,6 +15,7 @@
  *  KIND, either express or implied.  See the License for the
  *  specific language governing permissions and limitations
  *  under the License.
+ *
  */
 package org.apache.isis.persistence.jpa.integration.changetracking;
 
@@ -34,7 +35,7 @@ import lombok.val;
 final class _Xray {
 
     public static void publish(
-            final EntityChangeTrackerJpa entityChangeTrackerDefault,
+            final EntityChangeTrackerDefault entityChangeTrackerDefault,
             final Provider<InteractionProvider> interactionProviderProvider) {
 
         if(!XrayUi.isXrayEnabled()) {
diff --git a/persistence/jdo/integration/pom.xml b/persistence/jdo/integration/pom.xml
index f684aa5350..327e286a0b 100644
--- a/persistence/jdo/integration/pom.xml
+++ b/persistence/jdo/integration/pom.xml
@@ -33,11 +33,6 @@
 
 	<dependencies>
 
-<!-- 		<dependency> -->
-<!-- 			<groupId>org.apache.isis.persistence</groupId> -->
-<!-- 			<artifactId>isis-persistence-jdo-provider</artifactId> -->
-<!-- 		</dependency> -->
-
 		<dependency>
 			<groupId>org.apache.isis.commons</groupId>
 			<artifactId>isis-commons</artifactId>
@@ -53,6 +48,11 @@
 			<artifactId>isis-core-runtime</artifactId>
 		</dependency>
 
+		<dependency>
+			<groupId>org.apache.isis.persistence</groupId>
+			<artifactId>isis-persistence-commons</artifactId>
+		</dependency>
+
 		<dependency>
 			<groupId>org.apache.isis.persistence</groupId>
 			<artifactId>isis-persistence-jdo-applib</artifactId>
diff --git a/persistence/jdo/integration/src/main/java/org/apache/isis/persistence/jdo/integration/IsisModulePersistenceJdoIntegration.java b/persistence/jdo/integration/src/main/java/org/apache/isis/persistence/jdo/integration/IsisModulePersistenceJdoIntegration.java
index aa7a5c07db..9c9ccab436 100644
--- a/persistence/jdo/integration/src/main/java/org/apache/isis/persistence/jdo/integration/IsisModulePersistenceJdoIntegration.java
+++ b/persistence/jdo/integration/src/main/java/org/apache/isis/persistence/jdo/integration/IsisModulePersistenceJdoIntegration.java
@@ -22,19 +22,18 @@ import org.springframework.context.annotation.Configuration;
 import org.springframework.context.annotation.Import;
 
 import org.apache.isis.core.runtime.IsisModuleCoreRuntime;
+import org.apache.isis.persistence.commons.IsisModulePersistenceCommons;
 import org.apache.isis.persistence.jdo.applib.IsisModulePersistenceJdoApplib;
-import org.apache.isis.persistence.jdo.integration.changetracking.EntityChangeTrackerJdo;
 import org.apache.isis.persistence.jdo.metamodel.IsisModulePersistenceJdoMetamodel;
 
 @Configuration
 @Import({
         // modules
         IsisModuleCoreRuntime.class,
+        IsisModulePersistenceCommons.class,
         IsisModulePersistenceJdoApplib.class,
         IsisModulePersistenceJdoMetamodel.class,
 
-        // services
-        EntityChangeTrackerJdo.class,
 })
 public class IsisModulePersistenceJdoIntegration {
 
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
deleted file mode 100644
index fd39f99375..0000000000
--- a/persistence/jdo/integration/src/main/java/org/apache/isis/persistence/jdo/integration/changetracking/EntityChangeTrackerJdo.java
+++ /dev/null
@@ -1,430 +0,0 @@
-/*
- *  Licensed to the Apache Software Foundation (ASF) under one
- *  or more contributor license agreements.  See the NOTICE file
- *  distributed with this work for additional information
- *  regarding copyright ownership.  The ASF licenses this file
- *  to you under the Apache License, Version 2.0 (the
- *  "License"); you may not use this file except in compliance
- *  with the License.  You may obtain a copy of the License at
- *
- *        http://www.apache.org/licenses/LICENSE-2.0
- *
- *  Unless required by applicable law or agreed to in writing,
- *  software distributed under the License is distributed on an
- *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- *  KIND, either express or implied.  See the License for the
- *  specific language governing permissions and limitations
- *  under the License.
- */
-package org.apache.isis.persistence.jdo.integration.changetracking;
-
-import java.util.Map;
-import java.util.Optional;
-import java.util.Set;
-import java.util.concurrent.atomic.AtomicBoolean;
-import java.util.concurrent.atomic.LongAdder;
-import java.util.function.Consumer;
-
-import javax.annotation.Priority;
-import javax.inject.Inject;
-import javax.inject.Named;
-import javax.inject.Provider;
-
-import org.springframework.beans.factory.annotation.Qualifier;
-import org.springframework.context.event.EventListener;
-import org.springframework.core.annotation.Order;
-import org.springframework.stereotype.Service;
-
-import org.apache.isis.applib.annotation.EntityChangeKind;
-import org.apache.isis.applib.annotation.InteractionScope;
-import org.apache.isis.applib.annotation.PriorityPrecedence;
-import org.apache.isis.applib.services.bookmark.Bookmark;
-import org.apache.isis.applib.services.eventbus.EventBusService;
-import org.apache.isis.applib.services.iactn.Interaction;
-import org.apache.isis.applib.services.iactn.InteractionProvider;
-import org.apache.isis.applib.services.metrics.MetricsService;
-import org.apache.isis.applib.services.publishing.spi.EntityChanges;
-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.commons.internal.base._Lazy;
-import org.apache.isis.commons.internal.collections._Maps;
-import org.apache.isis.commons.internal.collections._Sets;
-import org.apache.isis.commons.internal.exceptions._Exceptions;
-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;
-import org.apache.isis.core.metamodel.facets.object.callbacks.PersistingLifecycleEventFacet;
-import org.apache.isis.core.metamodel.facets.object.callbacks.RemovingCallbackFacet;
-import org.apache.isis.core.metamodel.facets.object.callbacks.RemovingLifecycleEventFacet;
-import org.apache.isis.core.metamodel.facets.object.callbacks.UpdatedCallbackFacet;
-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.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.PropertyChangeRecord;
-import org.apache.isis.core.metamodel.services.objectlifecycle.PropertyValuePlaceholder;
-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.MixedIn;
-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.PersistenceCallbackHandlerAbstract;
-import org.apache.isis.core.transaction.events.TransactionBeforeCompletionEvent;
-
-import lombok.AccessLevel;
-import lombok.Getter;
-import lombok.NonNull;
-import lombok.val;
-import lombok.extern.log4j.Log4j2;
-
-/**
- * @since 2.0 {@index}
- */
-@Service
-@Named("isis.transaction.EntityChangeTrackerJdo")
-@Priority(PriorityPrecedence.EARLY)
-@Qualifier("jdo")
-@InteractionScope
-@Log4j2
-public class EntityChangeTrackerJdo
-extends PersistenceCallbackHandlerAbstract
-implements
-    MetricsService,
-    EntityChangeTracker,
-    HasEnlistedEntityPropertyChanges,
-    HasEnlistedEntityChanges {
-
-    /**
-     * Contains initial change records having set the pre-values of every property of every object that was enlisted.
-     */
-    private final Map<String, PropertyChangeRecord> propertyChangeRecordsById = _Maps.newLinkedHashMap();
-
-    /**
-     * Contains pre- and post- values of every property of every object that actually changed. A lazy snapshot,
-     * triggered by internal call to {@link #snapshotPropertyChangeRecords()}.
-     */
-    private final _Lazy<Set<PropertyChangeRecord>> entityPropertyChangeRecordsForPublishing
-        = _Lazy.threadSafe(this::capturePostValuesAndDrain);
-
-    @Getter(AccessLevel.PACKAGE)
-    private final Map<Bookmark, EntityChangeKind> changeKindByEnlistedAdapter = _Maps.newLinkedHashMap();
-
-    private final EntityPropertyChangePublisher entityPropertyChangePublisher;
-    private final EntityChangesPublisher entityChangesPublisher;
-    private final Provider<InteractionProvider> interactionProviderProvider;
-
-    @Inject
-    public EntityChangeTrackerJdo(
-            final EntityPropertyChangePublisher entityPropertyChangePublisher,
-            final EntityChangesPublisher entityChangesPublisher,
-            final EventBusService eventBusService,
-            final Provider<InteractionProvider> interactionProviderProvider) {
-        super(eventBusService);
-        this.entityPropertyChangePublisher = entityPropertyChangePublisher;
-        this.entityChangesPublisher = entityChangesPublisher;
-        this.interactionProviderProvider = interactionProviderProvider;
-    }
-
-    private boolean isEnlisted(final @NonNull ManagedObject adapter) {
-        return ManagedObjects.bookmark(adapter)
-        .map(changeKindByEnlistedAdapter::containsKey)
-        .orElse(false);
-    }
-
-    private void enlistCreatedInternal(final @NonNull ManagedObject adapter) {
-        if(!isEntityEnabledForChangePublishing(adapter)) {
-            return;
-        }
-        enlistForChangeKindPublishing(adapter, EntityChangeKind.CREATE);
-        enlistForPreAndPostValuePublishing(adapter, record->record.setPreValue(PropertyValuePlaceholder.NEW));
-    }
-
-    private void enlistUpdatingInternal(
-            final @NonNull ManagedObject entity) {
-        if(!isEntityEnabledForChangePublishing(entity)) {
-            return;
-        }
-        enlistForChangeKindPublishing(entity, EntityChangeKind.UPDATE);
-        enlistForPreAndPostValuePublishing(entity, PropertyChangeRecord::updatePreValue);
-    }
-
-    private void enlistDeletingInternal(final @NonNull ManagedObject adapter) {
-        if(!isEntityEnabledForChangePublishing(adapter)) {
-            return;
-        }
-        final boolean enlisted = enlistForChangeKindPublishing(adapter, EntityChangeKind.DELETE);
-        if(enlisted) {
-            enlistForPreAndPostValuePublishing(adapter, PropertyChangeRecord::updatePreValue);
-        }
-    }
-
-    Set<PropertyChangeRecord> snapshotPropertyChangeRecords() {
-        // this code path has side-effects, it locks the result for this transaction,
-        // such that cannot enlist on top of it
-        return entityPropertyChangeRecordsForPublishing.get();
-    }
-
-    private boolean isEntityEnabledForChangePublishing(final @NonNull ManagedObject adapter) {
-
-        if(!EntityChangePublishingFacet.isPublishingEnabled(adapter.getSpecification())) {
-            return false; // ignore entities that are not enabled for entity change publishing
-        }
-
-        if(entityPropertyChangeRecordsForPublishing.isMemoized()) {
-            throw _Exceptions.illegalState("Cannot enlist additional changes for auditing, "
-                    + "since changedObjectPropertiesRef was already prepared (memoized) for auditing.");
-        }
-
-        entityChangeEventCount.increment();
-        enableCommandPublishing();
-
-        return true;
-    }
-
-    /**
-     * TRANSACTION END BOUNDARY
-     * @apiNote intended to be called during before transaction completion by the framework internally
-     */
-    @EventListener(value = TransactionBeforeCompletionEvent.class) @Order(PriorityPrecedence.LATE)
-    public void onTransactionCompleting(final TransactionBeforeCompletionEvent event) {
-        try {
-            doPublish();
-        } finally {
-            postPublishing();
-        }
-    }
-
-    private void doPublish() {
-        _Xray.publish(this, interactionProviderProvider);
-
-        log.debug("about to publish entity changes");
-        entityPropertyChangePublisher.publishChangedProperties(this);
-        entityChangesPublisher.publishChangingEntities(this);
-    }
-
-    private void postPublishing() {
-        log.debug("purging entity change records");
-        propertyChangeRecordsById.clear();
-        changeKindByEnlistedAdapter.clear();
-        entityPropertyChangeRecordsForPublishing.clear();
-        entityChangeEventCount.reset();
-        numberEntitiesLoaded.reset();
-    }
-
-    private void enableCommandPublishing() {
-        val alreadySet = persistentChangesEncountered.getAndSet(true);
-        if(!alreadySet) {
-            val command = currentInteraction().getCommand();
-            command.updater().setSystemStateChanged(true);
-        }
-    }
-
-    @Override
-    public Optional<EntityChanges> getEntityChanges(
-            final java.sql.Timestamp timestamp,
-            final String userName) {
-        return _ChangingEntitiesFactory.createChangingEntities(timestamp, userName, this);
-    }
-
-    @Override
-    public Can<EntityPropertyChange> getPropertyChanges(
-            final java.sql.Timestamp timestamp,
-            final String userName,
-            final TransactionId txId) {
-
-        return snapshotPropertyChangeRecords().stream()
-                .map(propertyChangeRecord -> propertyChangeRecord.toEntityPropertyChange(timestamp, userName, txId))
-                .collect(Can.toCan());
-    }
-
-    // -- DEPENDENCIES
-
-    Interaction currentInteraction() {
-        return interactionProviderProvider.get().currentInteractionElseFail();
-    }
-
-    // -- HELPER
-
-    /**
-     * @return <code>true</code> if successfully enlisted, <code>false</code> if was already enlisted
-     */
-    private boolean enlistForChangeKindPublishing(
-            final @NonNull ManagedObject entity,
-            final @NonNull EntityChangeKind changeKind) {
-
-        val bookmark = ManagedObjects.bookmarkElseFail(entity);
-
-        val previousChangeKind = changeKindByEnlistedAdapter.get(bookmark);
-        if(previousChangeKind == null) {
-            changeKindByEnlistedAdapter.put(bookmark, changeKind);
-            return true;
-        }
-        switch (previousChangeKind) {
-        case CREATE:
-            switch (changeKind) {
-            case DELETE:
-                changeKindByEnlistedAdapter.remove(bookmark);
-            case CREATE:
-            case UPDATE:
-                return false;
-            }
-            break;
-        case UPDATE:
-            switch (changeKind) {
-            case DELETE:
-                changeKindByEnlistedAdapter.put(bookmark, changeKind);
-                return true;
-            case CREATE:
-            case UPDATE:
-                return false;
-            }
-            break;
-        case DELETE:
-            return false;
-        }
-        return previousChangeKind == null;
-    }
-
-    private void enlistForPreAndPostValuePublishing(
-            final ManagedObject entity,
-            final Consumer<PropertyChangeRecord> onNewChangeRecord) {
-
-        log.debug("enlist entity's property changes for publishing {}", entity);
-
-        entity.getSpecification().streamProperties(MixedIn.EXCLUDED)
-        .filter(property->!EntityPropertyChangePublishingPolicyFacet.isExcludedFromPublishing(property))
-        .map(property->PropertyChangeRecord.of(entity, property))
-        .filter(record->!propertyChangeRecordsById.containsKey(record.getPropertyId())) // already enlisted, so ignore
-        .forEach(record->{
-            onNewChangeRecord.accept(record);
-            propertyChangeRecordsById.put(record.getPropertyId(), record);
-        });
-    }
-
-    /**
-     * For any enlisted Object Properties collects those, that are meant for publishing,
-     * then clears enlisted objects.
-     */
-    private Set<PropertyChangeRecord> capturePostValuesAndDrain() {
-
-        val records = propertyChangeRecordsById.values().stream()
-                // set post values, which have been left empty up to now
-                .peek(rec->{
-                    // assuming this check correctly detects deleted entities (JDO)
-                    if(EntityUtil.isDetachedOrRemoved(rec.getEntity())) {
-                        rec.updatePostValueAsDeleted();
-                    } else {
-                        rec.updatePostValueAsNonDeleted();
-                    }
-                })
-                .filter(managedProperty->managedProperty.getPreAndPostValue().shouldPublish())
-                .collect(_Sets.toUnmodifiable());
-
-        propertyChangeRecordsById.clear();
-
-        return records;
-
-    }
-
-    // side-effect free, used by XRay
-    long countPotentialPropertyChangeRecords() {
-        return propertyChangeRecordsById.size();
-    }
-
-    // -- METRICS SERVICE
-
-    @Override
-    public int numberEntitiesLoaded() {
-        return Math.toIntExact(numberEntitiesLoaded.longValue());
-    }
-
-    @Override
-    public int numberEntitiesDirtied() {
-        return changeKindByEnlistedAdapter.size();
-    }
-
-    // -- ENTITY CHANGE TRACKING
-
-    @Override
-    public void enlistCreated(final ManagedObject entity) {
-        _Xray.enlistCreated(entity, interactionProviderProvider);
-        val hasAlreadyBeenEnlisted = isEnlisted(entity);
-        enlistCreatedInternal(entity);
-
-        if(!hasAlreadyBeenEnlisted) {
-            CallbackFacet.callCallback(entity, PersistedCallbackFacet.class);
-            postLifecycleEventIfRequired(entity, PersistedLifecycleEventFacet.class);
-        }
-    }
-
-    @Override
-    public void enlistCreated(ManagedObject entity, Can<PropertyChangeRecord> propertyChangeRecords) {
-    }
-
-    @Override
-    public void enlistDeleting(final ManagedObject entity) {
-        _Xray.enlistDeleting(entity, interactionProviderProvider);
-        enlistDeletingInternal(entity);
-        CallbackFacet.callCallback(entity, RemovingCallbackFacet.class);
-        postLifecycleEventIfRequired(entity, RemovingLifecycleEventFacet.class);
-    }
-
-    @Override
-    public void enlistDeleting(ManagedObject entity, Can<PropertyChangeRecord> propertyChangeRecords) {
-    }
-
-    @Override
-    public void enlistUpdating(final ManagedObject entity) {
-        _Xray.enlistUpdating(entity, interactionProviderProvider);
-        val hasAlreadyBeenEnlisted = isEnlisted(entity);
-        // we call this come what may;
-        // additional properties may now have been changed, and the changeKind for publishing might also be modified
-        enlistUpdatingInternal(entity);
-
-        if(!hasAlreadyBeenEnlisted) {
-            // prevent an infinite loop... don't call the 'updating()' callback on this object if we have already done so
-            CallbackFacet.callCallback(entity, UpdatingCallbackFacet.class);
-            postLifecycleEventIfRequired(entity, UpdatingLifecycleEventFacet.class);
-        }
-    }
-
-    @Override
-    public void enlistUpdating(ManagedObject entity, Can<PropertyChangeRecord> propertyChangeRecords) {
-    }
-
-    @Override
-    public void recognizeLoaded(final ManagedObject entity) {
-        _Xray.recognizeLoaded(entity, interactionProviderProvider);
-        CallbackFacet.callCallback(entity, LoadedCallbackFacet.class);
-        postLifecycleEventIfRequired(entity, LoadedLifecycleEventFacet.class);
-        numberEntitiesLoaded.increment();
-    }
-
-    @Override
-    public void recognizePersisting(final ManagedObject entity) {
-        _Xray.recognizePersisting(entity, interactionProviderProvider);
-        CallbackFacet.callCallback(entity, PersistingCallbackFacet.class);
-        postLifecycleEventIfRequired(entity, PersistingLifecycleEventFacet.class);
-    }
-
-    @Override
-    public void recognizeUpdating(final ManagedObject entity) {
-        _Xray.recognizeUpdating(entity, interactionProviderProvider);
-        CallbackFacet.callCallback(entity, UpdatedCallbackFacet.class);
-        postLifecycleEventIfRequired(entity, UpdatedLifecycleEventFacet.class);
-    }
-
-    private final LongAdder numberEntitiesLoaded = new LongAdder();
-    private final LongAdder entityChangeEventCount = new LongAdder();
-    private final AtomicBoolean persistentChangesEncountered = new AtomicBoolean();
-
-}
diff --git a/persistence/jdo/integration/src/main/java/org/apache/isis/persistence/jdo/integration/changetracking/_ChangingEntitiesFactory.java b/persistence/jdo/integration/src/main/java/org/apache/isis/persistence/jdo/integration/changetracking/_ChangingEntitiesFactory.java
deleted file mode 100644
index 95b3c3a9df..0000000000
--- a/persistence/jdo/integration/src/main/java/org/apache/isis/persistence/jdo/integration/changetracking/_ChangingEntitiesFactory.java
+++ /dev/null
@@ -1,143 +0,0 @@
-/*
- *  Licensed to the Apache Software Foundation (ASF) under one
- *  or more contributor license agreements.  See the NOTICE file
- *  distributed with this work for additional information
- *  regarding copyright ownership.  The ASF licenses this file
- *  to you under the Apache License, Version 2.0 (the
- *  "License"); you may not use this file except in compliance
- *  with the License.  You may obtain a copy of the License at
- *
- *        http://www.apache.org/licenses/LICENSE-2.0
- *
- *  Unless required by applicable law or agreed to in writing,
- *  software distributed under the License is distributed on an
- *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- *  KIND, either express or implied.  See the License for the
- *  specific language governing permissions and limitations
- *  under the License.
- */
-package org.apache.isis.persistence.jdo.integration.changetracking;
-
-import java.util.HashMap;
-import java.util.Map;
-import java.util.Optional;
-import java.util.UUID;
-
-import org.apache.isis.applib.annotation.EntityChangeKind;
-import org.apache.isis.applib.jaxb.JavaSqlXMLGregorianCalendarMarshalling;
-import org.apache.isis.applib.services.bookmark.Bookmark;
-import org.apache.isis.applib.services.iactn.Interaction;
-import org.apache.isis.applib.services.publishing.spi.EntityChanges;
-import org.apache.isis.core.metamodel.execution.InteractionInternal;
-import org.apache.isis.schema.chg.v2.ChangesDto;
-import org.apache.isis.schema.chg.v2.ObjectsDto;
-import org.apache.isis.schema.common.v2.OidsDto;
-
-import lombok.val;
-
-final class _ChangingEntitiesFactory {
-
-    public static Optional<EntityChanges> createChangingEntities(
-            final java.sql.Timestamp completedAt,
-            final String userName,
-            final EntityChangeTrackerJdo entityChangeTracker) {
-
-        if(entityChangeTracker.getChangeKindByEnlistedAdapter().isEmpty()) {
-            return Optional.empty();
-        }
-
-        // take a copy of enlisted adapters ... the JDO implementation of the PublishingService
-        // creates further entities which would be enlisted;
-        // taking copy of the map avoids ConcurrentModificationException
-        val changeKindByEnlistedAdapter = new HashMap<>(
-                entityChangeTracker.getChangeKindByEnlistedAdapter());
-
-        val changingEntities = newChangingEntities(
-                completedAt,
-                userName,
-                entityChangeTracker.currentInteraction(),
-                entityChangeTracker.numberEntitiesLoaded(),
-                // side-effect: it locks the result for this transaction,
-                // such that cannot enlist on top of it
-                entityChangeTracker.snapshotPropertyChangeRecords().size(),
-                changeKindByEnlistedAdapter);
-
-        return Optional.of(changingEntities);
-    }
-
-    // -- HELPER
-
-    private static EntityChanges newChangingEntities(
-            final java.sql.Timestamp completedAt,
-            final String userName,
-            final Interaction interaction,
-            final int numberEntitiesLoaded,
-            final int numberEntityPropertiesModified,
-            final Map<Bookmark, EntityChangeKind> changeKindByEnlistedAdapter) {
-
-        val interactionId = interaction.getInteractionId();
-        final int nextEventSequence = ((InteractionInternal) interaction).getThenIncrementTransactionSequence();
-
-        return new _SimpleChangingEntities(
-                    interactionId, nextEventSequence,
-                    userName, completedAt,
-                    numberEntitiesLoaded,
-                    numberEntityPropertiesModified,
-                    ()->newDto(
-                            interactionId, nextEventSequence,
-                            userName, completedAt,
-                            numberEntitiesLoaded,
-                            numberEntityPropertiesModified,
-                            changeKindByEnlistedAdapter));
-    }
-
-    private static ChangesDto newDto(
-            final UUID interactionId, final int transactionSequenceNum,
-            final String userName, final java.sql.Timestamp completedAt,
-            final int numberEntitiesLoaded,
-            final int numberEntityPropertiesModified,
-            final Map<Bookmark, EntityChangeKind> changeKindByEnlistedEntity) {
-
-        val objectsDto = new ObjectsDto();
-        objectsDto.setCreated(new OidsDto());
-        objectsDto.setUpdated(new OidsDto());
-        objectsDto.setDeleted(new OidsDto());
-
-        changeKindByEnlistedEntity.forEach((bookmark, kind)->{
-            val oidDto = bookmark.toOidDto();
-            if(oidDto==null) {
-                return;
-            }
-            switch(kind) {
-            case CREATE:
-                objectsDto.getCreated().getOid().add(oidDto);
-                return;
-            case UPDATE:
-                objectsDto.getUpdated().getOid().add(oidDto);
-                return;
-            case DELETE:
-                objectsDto.getDeleted().getOid().add(oidDto);
-                return;
-            }
-        });
-
-        objectsDto.setLoaded(numberEntitiesLoaded);
-        objectsDto.setPropertiesModified(numberEntityPropertiesModified);
-
-        val changesDto = new ChangesDto();
-
-        changesDto.setMajorVersion("2");
-        changesDto.setMinorVersion("0");
-
-        changesDto.setInteractionId(interactionId.toString());
-        changesDto.setSequence(transactionSequenceNum);
-
-        changesDto.setUsername(userName);
-        changesDto.setCompletedAt(JavaSqlXMLGregorianCalendarMarshalling.toXMLGregorianCalendar(completedAt));
-
-        changesDto.setObjects(objectsDto);
-        return changesDto;
-    }
-
-
-}
diff --git a/persistence/jdo/integration/src/main/java/org/apache/isis/persistence/jdo/integration/changetracking/_SimpleChangingEntities.java b/persistence/jdo/integration/src/main/java/org/apache/isis/persistence/jdo/integration/changetracking/_SimpleChangingEntities.java
deleted file mode 100644
index 18b4896d6a..0000000000
--- a/persistence/jdo/integration/src/main/java/org/apache/isis/persistence/jdo/integration/changetracking/_SimpleChangingEntities.java
+++ /dev/null
@@ -1,121 +0,0 @@
-/*
- *  Licensed to the Apache Software Foundation (ASF) under one
- *  or more contributor license agreements.  See the NOTICE file
- *  distributed with this work for additional information
- *  regarding copyright ownership.  The ASF licenses this file
- *  to you under the Apache License, Version 2.0 (the
- *  "License"); you may not use this file except in compliance
- *  with the License.  You may obtain a copy of the License at
- *
- *        http://www.apache.org/licenses/LICENSE-2.0
- *
- *  Unless required by applicable law or agreed to in writing,
- *  software distributed under the License is distributed on an
- *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- *  KIND, either express or implied.  See the License for the
- *  specific language governing permissions and limitations
- *  under the License.
- */
-package org.apache.isis.persistence.jdo.integration.changetracking;
-
-import java.sql.Timestamp;
-import java.util.UUID;
-import java.util.function.Supplier;
-
-import org.apache.isis.applib.services.publishing.spi.EntityChanges;
-import org.apache.isis.schema.chg.v2.ChangesDto;
-
-import lombok.NonNull;
-import lombok.ToString;
-
-/**
- * Captures which objects were created, updated or deleted in the course of a transaction.
- */
-@ToString
-final class _SimpleChangingEntities implements EntityChanges {
-
-    private UUID transactionUuid;
-    private final int sequence;
-    private final String userName;
-    private final Timestamp completedAt;
-    private final int numberEntitiesLoaded;
-    private final int numberEntityPropertiesModified;
-
-    @ToString.Exclude
-    private final Supplier<ChangesDto> changesDtoSupplier;
-
-    public _SimpleChangingEntities(
-            final @NonNull UUID transactionUuid,
-            final int sequence,
-            final @NonNull String userName,
-            final @NonNull Timestamp completedAt,
-            final int numberEntitiesLoaded,
-            final int numberEntityPropertiesModified,
-            final @NonNull Supplier<ChangesDto> changesDtoSupplier) {
-
-        this.transactionUuid = transactionUuid;
-        this.sequence = sequence;
-        this.userName = userName;
-        this.completedAt = completedAt;
-        this.numberEntitiesLoaded = numberEntitiesLoaded;
-        this.numberEntityPropertiesModified = numberEntityPropertiesModified;
-        this.changesDtoSupplier = changesDtoSupplier;
-    }
-
-    @Override
-    public UUID getInteractionId() {
-        return transactionUuid;
-    }
-
-    @Override
-    public int getSequence() {
-        return sequence;
-    }
-
-    /**
-     * The date/time at which this set of enlisted objects was created
-     * (approx the completion time of the transaction).
-     */
-    @Override
-    public Timestamp getCompletedAt() {
-        return completedAt;
-    }
-
-    @Override
-    public String getUsername() {
-        return userName;
-    }
-
-    private ChangesDto dto;
-
-    @Override
-    public ChangesDto getDto() {
-        return dto != null ? dto : (dto = changesDtoSupplier.get());
-    }
-
-    @Override
-    public int getNumberLoaded() {
-        return numberEntitiesLoaded;
-    }
-
-    @Override
-    public int getNumberCreated() {
-        return getDto().getObjects().getCreated().getOid().size();
-    }
-
-    @Override
-    public int getNumberUpdated() {
-        return getDto().getObjects().getUpdated().getOid().size();
-    }
-
-    @Override
-    public int getNumberDeleted() {
-        return getDto().getObjects().getDeleted().getOid().size();
-    }
-
-    @Override
-    public int getNumberPropertiesModified() {
-        return numberEntityPropertiesModified;
-    }
-
-}
diff --git a/persistence/jdo/integration/src/main/java/org/apache/isis/persistence/jdo/integration/changetracking/_Xray.java b/persistence/jdo/integration/src/main/java/org/apache/isis/persistence/jdo/integration/changetracking/_Xray.java
deleted file mode 100644
index 34c16325dd..0000000000
--- a/persistence/jdo/integration/src/main/java/org/apache/isis/persistence/jdo/integration/changetracking/_Xray.java
+++ /dev/null
@@ -1,145 +0,0 @@
-/*
- *  Licensed to the Apache Software Foundation (ASF) under one
- *  or more contributor license agreements.  See the NOTICE file
- *  distributed with this work for additional information
- *  regarding copyright ownership.  The ASF licenses this file
- *  to you under the Apache License, Version 2.0 (the
- *  "License"); you may not use this file except in compliance
- *  with the License.  You may obtain a copy of the License at
- *
- *        http://www.apache.org/licenses/LICENSE-2.0
- *
- *  Unless required by applicable law or agreed to in writing,
- *  software distributed under the License is distributed on an
- *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- *  KIND, either express or implied.  See the License for the
- *  specific language governing permissions and limitations
- *  under the License.
- */
-package org.apache.isis.persistence.jdo.integration.changetracking;
-
-import java.awt.Color;
-
-import javax.inject.Provider;
-
-import org.apache.isis.applib.services.iactn.InteractionProvider;
-import org.apache.isis.commons.internal.debug._XrayEvent;
-import org.apache.isis.commons.internal.debug.xray.XrayUi;
-import org.apache.isis.core.metamodel.spec.ManagedObject;
-import org.apache.isis.core.metamodel.spec.ManagedObjects;
-import org.apache.isis.core.security.util.XrayUtil;
-
-import lombok.val;
-
-final class _Xray {
-
-    public static void publish(
-            final EntityChangeTrackerJdo entityChangeTrackerDefault,
-            final Provider<InteractionProvider> interactionProviderProvider) {
-
-        if(!XrayUi.isXrayEnabled()) {
-            return;
-        }
-
-        final long propertyChangeRecordCount = entityChangeTrackerDefault.countPotentialPropertyChangeRecords();
-
-        val enteringLabel = String.format("consider %d entity change records for publishing",
-                propertyChangeRecordCount);
-
-        XrayUtil.createSequenceHandle(interactionProviderProvider.get(), "ec-tracker")
-        .ifPresent(handle->{
-
-            handle.submit(sequenceData->{
-
-                sequenceData.alias("ec-tracker", "EntityChange-\nTracker-\n(Default)");
-
-                if(propertyChangeRecordCount==0) {
-                    sequenceData.setConnectionArrowColor(Color.GRAY);
-                    sequenceData.setConnectionLabelColor(Color.GRAY);
-                }
-
-                val callee = handle.getCallees().getFirstOrFail();
-                sequenceData.enter(handle.getCaller(), callee, enteringLabel);
-                //sequenceData.activate(callee);
-            });
-
-        });
-    }
-
-    public static void enlistCreated(
-            final ManagedObject entity,
-            final Provider<InteractionProvider> interactionProviderProvider) {
-        addSequence("enlistCreated", entity, interactionProviderProvider);
-    }
-
-    public static void enlistDeleting(
-            final ManagedObject entity,
-            final Provider<InteractionProvider> interactionProviderProvider) {
-        addSequence("enlistDeleting", entity, interactionProviderProvider);
-    }
-
-    public static void enlistUpdating(
-            final ManagedObject entity,
-            final Provider<InteractionProvider> interactionProviderProvider) {
-        addSequence("enlistUpdating", entity, interactionProviderProvider);
-    }
-
-    public static void recognizeLoaded(
-            final ManagedObject entity,
-            final Provider<InteractionProvider> interactionProviderProvider) {
-        addSequence("recognizeLoaded", entity, interactionProviderProvider);
-    }
-
-    public static void recognizePersisting(
-            final ManagedObject entity,
-            final Provider<InteractionProvider> interactionProviderProvider) {
-        addSequence("recognizePersisting", entity, interactionProviderProvider);
-    }
-
-    public static void recognizeUpdating(
-            final ManagedObject entity,
-            final Provider<InteractionProvider> interactionProviderProvider) {
-        addSequence("recognizeUpdating", entity, interactionProviderProvider);
-    }
-
-    // -- HELPER
-
-    private static void addSequence(
-            final String what,
-            final ManagedObject entity,
-            final Provider<InteractionProvider> interactionProviderProvider) {
-
-        if(!XrayUi.isXrayEnabled()) {
-            return;
-        }
-
-        val enteringLabel = String.format("%s %s",
-                what,
-                ManagedObjects.isNullOrUnspecifiedOrEmpty(entity)
-                    ? "<empty>"
-                    : String.format("%s:\n%s",
-                            entity.getSpecification().getLogicalTypeName(),
-                            "" + entity.getPojo()));
-
-        _XrayEvent.event(enteringLabel);
-
-        XrayUtil.createSequenceHandle(interactionProviderProvider.get(), "ec-tracker")
-        .ifPresent(handle->{
-
-            handle.submit(sequenceData->{
-
-                sequenceData.alias("ec-tracker", "EntityChange-\nTracker-\n(Default)");
-
-                val callee = handle.getCallees().getFirstOrFail();
-                sequenceData.enter(handle.getCaller(), callee, enteringLabel);
-                //sequenceData.activate(callee);
-            });
-
-        });
-
-    }
-
-
-
-
-}
diff --git a/persistence/jpa/integration/pom.xml b/persistence/jpa/integration/pom.xml
index 05a9097765..00f1975201 100644
--- a/persistence/jpa/integration/pom.xml
+++ b/persistence/jpa/integration/pom.xml
@@ -1,13 +1,13 @@
 <?xml version="1.0" encoding="UTF-8"?>
-<!-- Licensed to the Apache Software Foundation (ASF) under one or more contributor 
-	license agreements. See the NOTICE file distributed with this work for additional 
-	information regarding copyright ownership. The ASF licenses this file to 
-	you under the Apache License, Version 2.0 (the "License"); you may not use 
-	this file except in compliance with the License. You may obtain a copy of 
-	the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required 
-	by applicable law or agreed to in writing, software distributed under the 
-	License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS 
-	OF ANY KIND, either express or implied. See the License for the specific 
+<!-- Licensed to the Apache Software Foundation (ASF) under one or more contributor
+	license agreements. See the NOTICE file distributed with this work for additional
+	information regarding copyright ownership. The ASF licenses this file to
+	you under the Apache License, Version 2.0 (the "License"); you may not use
+	this file except in compliance with the License. You may obtain a copy of
+	the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required
+	by applicable law or agreed to in writing, software distributed under the
+	License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS
+	OF ANY KIND, either express or implied. See the License for the specific
 	language governing permissions and limitations under the License. -->
 <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
 	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
@@ -68,16 +68,21 @@
 
 	<dependencies>
 
-		<dependency>
-			<groupId>org.apache.isis.persistence</groupId>
-			<artifactId>isis-persistence-jpa-metamodel</artifactId>
-		</dependency>
-		
 		<dependency>
 			<groupId>org.apache.isis.core</groupId>
 			<artifactId>isis-core-runtime</artifactId>
 		</dependency>
-		
+
+        <dependency>
+            <groupId>org.apache.isis.persistence</groupId>
+            <artifactId>isis-persistence-commons</artifactId>
+        </dependency>
+
+		<dependency>
+			<groupId>org.apache.isis.persistence</groupId>
+			<artifactId>isis-persistence-jpa-metamodel</artifactId>
+		</dependency>
+
 		<dependency>
 			<groupId>org.springframework.data</groupId>
 			<artifactId>spring-data-jpa</artifactId>
@@ -96,14 +101,14 @@
 		</dependency>
 
 		<!-- TESTING -->
-		
+
         <dependency>
             <groupId>org.apache.isis.core</groupId>
             <artifactId>isis-core-internaltestsupport</artifactId>
             <scope>test</scope>
         </dependency>
 
-	</dependencies>
+    </dependencies>
 
 
-</project>
\ No newline at end of file
+</project>
diff --git a/persistence/jpa/integration/src/main/java/org/apache/isis/persistence/jpa/integration/IsisModulePersistenceJpaIntegration.java b/persistence/jpa/integration/src/main/java/org/apache/isis/persistence/jpa/integration/IsisModulePersistenceJpaIntegration.java
index 6989843f23..48b07bccb4 100644
--- a/persistence/jpa/integration/src/main/java/org/apache/isis/persistence/jpa/integration/IsisModulePersistenceJpaIntegration.java
+++ b/persistence/jpa/integration/src/main/java/org/apache/isis/persistence/jpa/integration/IsisModulePersistenceJpaIntegration.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.EntityChangeTrackerJpa;
+import org.apache.isis.persistence.commons.IsisModulePersistenceCommons;
 import org.apache.isis.persistence.jpa.integration.entity.JpaEntityIntegration;
 import org.apache.isis.persistence.jpa.integration.services.JpaSupportServiceUsingSpring;
 import org.apache.isis.persistence.jpa.integration.typeconverters.applib.IsisBookmarkConverter;
@@ -45,6 +45,7 @@ import org.apache.isis.persistence.jpa.metamodel.IsisModulePersistenceJpaMetamod
 @Import({
         // modules
         IsisModuleCoreRuntime.class,
+        IsisModulePersistenceCommons.class,
         IsisModulePersistenceJpaMetamodel.class,
 
         // @Component's
@@ -52,7 +53,6 @@ import org.apache.isis.persistence.jpa.metamodel.IsisModulePersistenceJpaMetamod
 
         // @Service's
         JpaSupportServiceUsingSpring.class,
-        EntityChangeTrackerJpa.class,
 
 })
 @EntityScan(basePackageClasses = {


[isis] 07/12: ISIS-3110: cleans up import

Posted by da...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

danhaywood pushed a commit to branch ISIS-3110
in repository https://gitbox.apache.org/repos/asf/isis.git

commit 8aaca881e3f8986a37e204be9682622c22ff664d
Author: Dan Haywood <da...@haywood-associates.co.uk>
AuthorDate: Thu Aug 4 06:46:54 2022 +0100

    ISIS-3110: cleans up import
---
 .../jpa/IsisModuleExtExecutionOutboxPersistenceJpa.java                | 3 ---
 1 file changed, 3 deletions(-)

diff --git a/extensions/core/executionoutbox/persistence-jpa/src/main/java/org/apache/isis/extensions/executionoutbox/jpa/IsisModuleExtExecutionOutboxPersistenceJpa.java b/extensions/core/executionoutbox/persistence-jpa/src/main/java/org/apache/isis/extensions/executionoutbox/jpa/IsisModuleExtExecutionOutboxPersistenceJpa.java
index 76521823bf..c7929ec451 100644
--- a/extensions/core/executionoutbox/persistence-jpa/src/main/java/org/apache/isis/extensions/executionoutbox/jpa/IsisModuleExtExecutionOutboxPersistenceJpa.java
+++ b/extensions/core/executionoutbox/persistence-jpa/src/main/java/org/apache/isis/extensions/executionoutbox/jpa/IsisModuleExtExecutionOutboxPersistenceJpa.java
@@ -18,13 +18,10 @@
  */
 package org.apache.isis.extensions.executionoutbox.jpa;
 
-import javax.inject.Inject;
-
 import org.springframework.boot.autoconfigure.domain.EntityScan;
 import org.springframework.context.annotation.Configuration;
 import org.springframework.context.annotation.Import;
 
-import org.apache.isis.applib.services.iactnlayer.InteractionService;
 import org.apache.isis.extensions.executionoutbox.applib.IsisModuleExtExecutionOutboxApplib;
 import org.apache.isis.extensions.executionoutbox.jpa.dom.ExecutionOutboxEntry;
 import org.apache.isis.extensions.executionoutbox.jpa.dom.ExecutionOutboxEntryPK;