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 <[email protected]> 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 {
