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/03 12:15:42 UTC
[isis] 02/02: ISIS-3110: introduces EntityChangeTrackerJpa, mirroring JDO impl, but leveraging the PropertyChangeRecords already provided to us
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 f427fbea15a081018034cbfbc6a54e4d2e94e783
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 {