in core/metamodel/src/main/java/org/apache/causeway/core/metamodel/object/ManagedObjectViewmodel.java [43:208]
record ManagedObjectViewmodel(
@NonNull ObjectSpecification objSpec,
@NonNull TransientObjectRef<Object> pojoRef,
@NonNull TransientObjectRef<TransactionId> txIdDuringWhichRefreshed,
@NonNull _Lazy<Bookmark> bookmarkLazy)
implements
ManagedObject,
Bookmarkable.BookmarkRefreshable {
ManagedObjectViewmodel(
final ObjectSpecification objSpec,
final Object pojo,
final Optional<Bookmark> bookmarkIfKnown) {
this(
objSpec,
new TransientObjectRef<>(pojo),
new TransientObjectRef<TransactionId>(null),
null);
bookmarkIfKnown.ifPresent(bookmarkLazy::set);
}
// canonical constructor
ManagedObjectViewmodel(
final ObjectSpecification objSpec,
final TransientObjectRef<Object> pojoRef,
final TransientObjectRef<TransactionId> txIdDuringWhichRefreshed,
final _Lazy<Bookmark> bookmarkLazy) {
_Assert.assertTrue(objSpec.isViewModel());
specialization().assertCompliance(objSpec, pojoRef.getObject());
this.objSpec = objSpec;
this.pojoRef = pojoRef;
this.txIdDuringWhichRefreshed = txIdDuringWhichRefreshed;
this.bookmarkLazy = _Lazy.threadSafe(()->objSpec.viewmodelFacetElseFail().serializeToBookmark(this));
}
@Override
public Optional<ObjectMemento> getMemento() {
return ObjectMemento.singular(this);
}
@Override
public String getTitle() {
return _InternalTitleUtil.titleString(
TitleRenderRequest.forObject(this));
}
@Override
public Specialization specialization() {
return ManagedObject.Specialization.VIEWMODEL;
}
@Override
public Object getPojo() {
return pojoRef.getObject();
}
@Override
public final Optional<Bookmark> getBookmark() {
return Optional.of(bookmarkLazy.get());
}
@Override
public final boolean isBookmarkMemoized() {
return bookmarkLazy.isMemoized();
}
@Override
public void invalidateBookmark() {
bookmarkLazy.clear();
}
// -- REFRESH OPTIMIZATION
/**
* If the underlying domain object is a viewmodel, refreshes any referenced entities.
* (Acts as a no-op otherwise.)
* @apiNote usually should be sufficient to refresh once per interaction.
*/
final void refreshViewmodel(final @Nullable Supplier<Bookmark> bookmarkSupplier) {
var shouldRefresh = getTransactionService().currentTransactionId()
.map(this::shouldRefresh)
.orElse(true); // if there is no current transaction, refresh regardless; unexpected state, might fail later
if(!shouldRefresh) return;
if(isBookmarkMemoized()) {
reloadViewmodelFromMemoizedBookmark();
} else {
var bookmark = bookmarkSupplier!=null
? bookmarkSupplier.get()
: null;
if(bookmark!=null) {
reloadViewmodelFromBookmark(bookmark);
}
}
}
// -- OBJECT CONTRACT
@Override
public final boolean equals(final Object obj) {
return obj instanceof ManagedObjectViewmodel other
? Objects.equals(this.objSpec().logicalTypeName(), other.objSpec().logicalTypeName())
&& Objects.equals(this.getPojo(), other.getPojo())
: false;
}
@Override
public final int hashCode() {
return Objects.hash(objSpec().logicalTypeName(), getPojo());
}
@Override
public final String toString() {
return "ManagedObjectViewmodel[logicalTypeName=%s]".formatted(objSpec().logicalTypeName());
}
// -- HELPER
private void replaceBookmark(final UnaryOperator<Bookmark> replacer) {
final Bookmark old = bookmarkLazy.isMemoized()
? bookmarkLazy.get()
: null;
bookmarkLazy.clear();
bookmarkLazy.set(replacer.apply(old));
}
private boolean shouldRefresh(final @NonNull TransactionId transactionId) {
// if already refreshed within current transaction, skip
if(Objects.equals(this.txIdDuringWhichRefreshed.getObject(), transactionId)) return false;
this.txIdDuringWhichRefreshed.update(__->transactionId);
return true;
}
/**
* Reload current viewmodel object from memoized bookmark, otherwise does nothing.
*/
private void reloadViewmodelFromMemoizedBookmark() {
var bookmark = getBookmark().get();
var viewModelClass = getCorrespondingClass();
var recreatedViewmodel =
getFactoryService().viewModel(viewModelClass, bookmark);
_XrayEvent.event("Viewmodel '%s' recreated from memoized bookmark.", viewModelClass.getName());
replacePojo(old->recreatedViewmodel);
}
private void reloadViewmodelFromBookmark(final @NonNull Bookmark bookmark) {
var viewModelClass = getCorrespondingClass();
var recreatedViewmodel =
getFactoryService().viewModel(viewModelClass, bookmark);
_XrayEvent.event("Viewmodel '%s' recreated from provided bookmark.", viewModelClass.getName());
replacePojo(old->recreatedViewmodel);
replaceBookmark(old->bookmark);
}
private void replacePojo(final UnaryOperator<Object> replacer) {
pojoRef.update(pojo->specialization().assertCompliance(objSpec, replacer.apply(pojo)));
}
}