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