You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@causeway.apache.org by ah...@apache.org on 2026/05/12 11:11:22 UTC
(causeway) branch main updated: CAUSEWAY-3998: replay refinement (forward porting)
This is an automated email from the ASF dual-hosted git repository.
andi-huber pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/causeway.git
The following commit(s) were added to refs/heads/main by this push:
new 4341a4e6286 CAUSEWAY-3998: replay refinement (forward porting)
4341a4e6286 is described below
commit 4341a4e6286ce47a4d9f0b4c967af7d24474542f
Author: andi-huber <ah...@apache.org>
AuthorDate: Tue May 12 13:11:12 2026 +0200
CAUSEWAY-3998: replay refinement (forward porting)
---
.../applib/CausewayModuleExtCommandLogApplib.java | 4 +-
.../applib/dom/replay/CommandExportManager.java | 36 +++---
.../CommandExportManager.layout.fallback.xml | 6 +-
.../applib/dom/replay/CommandReplayManager.java | 124 +++++++++++++++------
.../CommandReplayManager.layout.fallback.xml | 6 +-
.../applib/dom/replay/ReplayableCommand.java | 25 +++++
.../replay/ReplayableCommand.layout.fallback.xml | 1 +
7 files changed, 142 insertions(+), 60 deletions(-)
diff --git a/extensions/core/commandlog/applib/src/main/java/org/apache/causeway/extensions/commandlog/applib/CausewayModuleExtCommandLogApplib.java b/extensions/core/commandlog/applib/src/main/java/org/apache/causeway/extensions/commandlog/applib/CausewayModuleExtCommandLogApplib.java
index 8cdf6f7877e..76435d8babd 100644
--- a/extensions/core/commandlog/applib/src/main/java/org/apache/causeway/extensions/commandlog/applib/CausewayModuleExtCommandLogApplib.java
+++ b/extensions/core/commandlog/applib/src/main/java/org/apache/causeway/extensions/commandlog/applib/CausewayModuleExtCommandLogApplib.java
@@ -72,12 +72,12 @@
ReplayableCommand_replayOrRetry.class,
ReplayableCommand_excludeFromReplay.class,
ReplayableCommand_delete.class,
- CommandExportManager.changeSince.class,
+ CommandExportManager.changeBaseline.class,
CommandExportManager.previousHour.class,
CommandExportManager.nextHour.class,
CommandExportManager.exportSelected.class,
CommandExportManager.makeSelectedExportable.class,
- CommandReplayManager.changeSince.class,
+ CommandReplayManager.changeBaseline.class,
CommandExportManager.previousHour.class,
CommandReplayManager.nextHour.class,
CommandReplayManager.importCommands.class,
diff --git a/extensions/core/commandlog/applib/src/main/java/org/apache/causeway/extensions/commandlog/applib/dom/replay/CommandExportManager.java b/extensions/core/commandlog/applib/src/main/java/org/apache/causeway/extensions/commandlog/applib/dom/replay/CommandExportManager.java
index 9b9e8a30b90..275aee2c82a 100644
--- a/extensions/core/commandlog/applib/src/main/java/org/apache/causeway/extensions/commandlog/applib/dom/replay/CommandExportManager.java
+++ b/extensions/core/commandlog/applib/src/main/java/org/apache/causeway/extensions/commandlog/applib/dom/replay/CommandExportManager.java
@@ -59,8 +59,8 @@
@Named(CommandExportManager.LOGICAL_TYPE_NAME)
public record CommandExportManager(
@Property
- @PropertyLayout(describedAs = "Only commands since this timestamp are available for export")
- java.sql.Timestamp since,
+ @PropertyLayout(describedAs = "Only commands after this timestamp are available for export")
+ java.sql.Timestamp baseline,
ReplayContext replayContext) implements ViewModel {
public static final String LOGICAL_TYPE_NAME = CausewayModuleExtCommandLogApplib.NAMESPACE + ".CommandExportManager";
@@ -86,14 +86,14 @@ public CommandExportManager(
domainEvent = previousHour.DomainEvent.class,
executionPublishing = Publishing.DISABLED)
@ActionLayout(
- associateWith = "since", sequence = "1",
+ associateWith = "baseline", sequence = "1",
named = "Previous",
position = ActionLayout.Position.PANEL,
describedAs = "Move back one hour")
public class previousHour {
public class DomainEvent extends ActionDomainEvent<previousHour> { }
@MemberSupport public CommandExportManager act() {
- return new CommandExportManager(addSeconds(since, -3600), replayContext);
+ return new CommandExportManager(addSeconds(baseline, -3600), replayContext);
}
}
@@ -103,14 +103,14 @@ public class DomainEvent extends ActionDomainEvent<previousHour> { }
domainEvent = nextHour.DomainEvent.class,
executionPublishing = Publishing.DISABLED)
@ActionLayout(
- associateWith = "since", sequence = "3",
+ associateWith = "baseline", sequence = "3",
named = "Next",
position = ActionLayout.Position.PANEL,
describedAs = "Move forward one hour")
public class nextHour {
public class DomainEvent extends ActionDomainEvent<nextHour> { }
@MemberSupport public CommandExportManager act() {
- return new CommandExportManager(addSeconds(since, +3600), replayContext);
+ return new CommandExportManager(addSeconds(baseline, +3600), replayContext);
}
}
@@ -118,19 +118,19 @@ public class DomainEvent extends ActionDomainEvent<nextHour> { }
restrictTo = RestrictTo.PROTOTYPING,
semantics = SemanticsOf.SAFE,
commandPublishing = Publishing.DISABLED,
- domainEvent = changeSince.DomainEvent.class,
+ domainEvent = changeBaseline.DomainEvent.class,
executionPublishing = Publishing.DISABLED)
@ActionLayout(
- associateWith = "since", sequence = "2",
+ associateWith = "baseline", sequence = "2",
named = "Change",
position = ActionLayout.Position.PANEL)
- public class changeSince {
+ public class changeBaseline {
public class DomainEvent extends ActionDomainEvent<nextHour> { }
- @MemberSupport public CommandExportManager act(final java.sql.Timestamp since) {
- return new CommandExportManager(since, replayContext);
+ @MemberSupport public CommandExportManager act(final java.sql.Timestamp baseline) {
+ return new CommandExportManager(baseline, replayContext);
}
- @MemberSupport public java.sql.Timestamp defaultSince() {
- return CommandExportManager.this.since;
+ @MemberSupport public java.sql.Timestamp defaultBaseline() {
+ return CommandExportManager.this.baseline;
}
}
@@ -140,7 +140,7 @@ public class DomainEvent extends ActionDomainEvent<nextHour> { }
@CollectionLayout(
describedAs = "Commands that can be exported")
public List<ReplayableCommand> getNotYetExported() {
- return commandLogEntryRepository().findForegroundSinceTimestampAndCanBeExported(since).stream()
+ return commandLogEntryRepository().findForegroundSinceTimestampAndCanBeExported(baseline).stream()
.map(entry->new ReplayableCommand(
entry.getInteractionId(),
replayContext))
@@ -231,7 +231,7 @@ public class DomainEvent extends ActionDomainEvent<exportSelected> { }
@Collection
@CollectionLayout(describedAs = "Commands that have been exported")
public List<ReplayableCommand> getExported() {
- return commandLogEntryRepository().findForegroundSinceTimestampAndHasBeenExported(since).stream()
+ return commandLogEntryRepository().findForegroundSinceTimestampAndHasBeenExported(baseline).stream()
.map(entry->new ReplayableCommand(
entry.getInteractionId(),
replayContext))
@@ -271,7 +271,7 @@ public class DomainEvent extends ActionDomainEvent<makeSelectedExportable> { }
@Override
public String viewModelMemento() {
- return TimestampMarshallUtil.toString(this.since);
+ return TimestampMarshallUtil.toString(this.baseline);
}
// -- HELPER
@@ -280,7 +280,7 @@ private CommandLogEntryRepository commandLogEntryRepository() {
return replayContext.commandLogEntryRepository();
}
- private static Timestamp addSeconds(final Timestamp since, final int secondsToAdd) {
- return Timestamp.from(since.toInstant().plusSeconds(secondsToAdd));
+ private static Timestamp addSeconds(final Timestamp ts, final int secondsToAdd) {
+ return Timestamp.from(ts.toInstant().plusSeconds(secondsToAdd));
}
}
diff --git a/extensions/core/commandlog/applib/src/main/java/org/apache/causeway/extensions/commandlog/applib/dom/replay/CommandExportManager.layout.fallback.xml b/extensions/core/commandlog/applib/src/main/java/org/apache/causeway/extensions/commandlog/applib/dom/replay/CommandExportManager.layout.fallback.xml
index ba0d2da0ef7..272902d81be 100644
--- a/extensions/core/commandlog/applib/src/main/java/org/apache/causeway/extensions/commandlog/applib/dom/replay/CommandExportManager.layout.fallback.xml
+++ b/extensions/core/commandlog/applib/src/main/java/org/apache/causeway/extensions/commandlog/applib/dom/replay/CommandExportManager.layout.fallback.xml
@@ -4,11 +4,11 @@
<cpt:domainObject/>
</bs:col>
<bs:col span="3">
- <cpt:fieldSet name=" " id="since">
+ <cpt:fieldSet name=" " id="baseline">
<cpt:action id="previousHour"/>
- <cpt:action id="changeSince"/>
+ <cpt:action id="changeBaseline"/>
<cpt:action id="nextHour"/>
- <cpt:property id="since"/>
+ <cpt:property id="baseline"/>
</cpt:fieldSet>
</bs:col>
<bs:col span="3">
diff --git a/extensions/core/commandlog/applib/src/main/java/org/apache/causeway/extensions/commandlog/applib/dom/replay/CommandReplayManager.java b/extensions/core/commandlog/applib/src/main/java/org/apache/causeway/extensions/commandlog/applib/dom/replay/CommandReplayManager.java
index e6522bf1d54..20834510232 100644
--- a/extensions/core/commandlog/applib/src/main/java/org/apache/causeway/extensions/commandlog/applib/dom/replay/CommandReplayManager.java
+++ b/extensions/core/commandlog/applib/src/main/java/org/apache/causeway/extensions/commandlog/applib/dom/replay/CommandReplayManager.java
@@ -20,7 +20,9 @@
import java.sql.Timestamp;
import java.util.List;
+import java.util.Optional;
import java.util.stream.Collectors;
+import java.util.stream.Stream;
import jakarta.inject.Inject;
import jakarta.inject.Named;
@@ -36,11 +38,13 @@
import org.apache.causeway.applib.annotation.MemberSupport;
import org.apache.causeway.applib.annotation.ObjectSupport;
import org.apache.causeway.applib.annotation.Parameter;
+import org.apache.causeway.applib.annotation.ParameterLayout;
import org.apache.causeway.applib.annotation.Property;
import org.apache.causeway.applib.annotation.PropertyLayout;
import org.apache.causeway.applib.annotation.Publishing;
import org.apache.causeway.applib.annotation.RestrictTo;
import org.apache.causeway.applib.annotation.SemanticsOf;
+import org.apache.causeway.applib.jaxb.JavaSqlXMLGregorianCalendarMarshalling;
import org.apache.causeway.applib.util.schema.CommandDtoUtils;
import org.apache.causeway.applib.value.Blob;
import org.apache.causeway.extensions.commandlog.applib.CausewayModuleExtCommandLogApplib;
@@ -54,8 +58,8 @@
@Named(CommandReplayManager.LOGICAL_TYPE_NAME)
public record CommandReplayManager(
@Property
- @PropertyLayout(describedAs = "Only commands since this timestamp are available for export")
- java.sql.Timestamp since,
+ @PropertyLayout(describedAs = "Only commands after this baseline are listed for replay")
+ java.sql.Timestamp baseline,
ReplayContext replayContext) implements ViewModel {
public static final String LOGICAL_TYPE_NAME = CausewayModuleExtCommandLogApplib.NAMESPACE + ".CommandReplayManager";
@@ -81,14 +85,14 @@ public CommandReplayManager(
domainEvent = previousHour.DomainEvent.class,
executionPublishing = Publishing.DISABLED)
@ActionLayout(
- associateWith = "since", sequence = "1",
+ associateWith = "baseline", sequence = "1",
named = "Previous",
position = ActionLayout.Position.PANEL,
describedAs = "Move back one hour")
public class previousHour {
public class DomainEvent extends ActionDomainEvent<previousHour> { }
@MemberSupport public CommandReplayManager act() {
- return new CommandReplayManager(addSeconds(since, -3600), replayContext);
+ return new CommandReplayManager(addSeconds(baseline, -3600), replayContext);
}
}
@@ -98,14 +102,14 @@ public class DomainEvent extends ActionDomainEvent<previousHour> { }
domainEvent = nextHour.DomainEvent.class,
executionPublishing = Publishing.DISABLED)
@ActionLayout(
- associateWith = "since", sequence = "3",
+ associateWith = "baseline", sequence = "3",
named = "Next",
position = ActionLayout.Position.PANEL,
describedAs = "Move forward one hour")
public class nextHour {
public class DomainEvent extends ActionDomainEvent<nextHour> { }
@MemberSupport public CommandReplayManager act() {
- return new CommandReplayManager(addSeconds(since, +3600), replayContext);
+ return new CommandReplayManager(addSeconds(baseline, +3600), replayContext);
}
}
@@ -113,21 +117,21 @@ public class DomainEvent extends ActionDomainEvent<nextHour> { }
restrictTo = RestrictTo.PROTOTYPING,
semantics = SemanticsOf.SAFE,
commandPublishing = Publishing.DISABLED,
- domainEvent = changeSince.DomainEvent.class,
+ domainEvent = changeBaseline.DomainEvent.class,
executionPublishing = Publishing.DISABLED
)
@ActionLayout(
- associateWith = "since", sequence = "2",
+ associateWith = "baseline", sequence = "2",
named = "Change",
position = ActionLayout.Position.PANEL
)
- public class changeSince {
+ public class changeBaseline {
public class DomainEvent extends ActionDomainEvent<nextHour> { }
@MemberSupport public CommandReplayManager act(final java.sql.Timestamp since) {
return new CommandReplayManager(since, replayContext);
}
- @MemberSupport public java.sql.Timestamp defaultSince() {
- return CommandReplayManager.this.since;
+ @MemberSupport public java.sql.Timestamp defaultBaseline() {
+ return CommandReplayManager.this.baseline;
}
}
@@ -138,21 +142,39 @@ public class DomainEvent extends ActionDomainEvent<nextHour> { }
domainEvent = importCommands.DomainEvent.class,
executionPublishing = Publishing.DISABLED)
@ActionLayout(
- sequence = "0.1",
+ sequence = "1.1",
associateWith = "pendingOrFailed",
- cssClass = "btn-primary",
+ cssClass = "btn-secondary",
describedAs = "Imports commands from yaml format, then persists them with replayState=PENDING.")
public class importCommands {
public class DomainEvent extends ActionDomainEvent<importCommands> { }
- public CommandReplayManager act(
+ @MemberSupport public CommandReplayManager act(
@Parameter(fileAccept = ".yml,.yaml")
- final Blob commandsYaml) {
+ final Blob commandsYaml,
+ @ParameterLayout(describedAs = "Change the baseline to the timestamp of the oldest, so that they are listed at top")
+ final boolean moveBaselineToOldest) {
var yamlDs = commandsYaml.asDataSource();
final List<CommandDto> commandDtos = CommandDtoUtils.fromYaml(yamlDs);
commandDtos.forEach(commandLogEntryRepository()::saveForReplay);
- return CommandReplayManager.this;
+ return moveBaselineToOldest
+ ? oldestTimestamp(commandDtos)
+ .map(timestamp -> new CommandReplayManager(timestamp, replayContext))
+ .orElse(CommandReplayManager.this)
+ : CommandReplayManager.this;
+ }
+
+ @MemberSupport public boolean defaultMoveBaselineToOldest() {
+ return true;
+ }
+
+ private Optional<Timestamp> oldestTimestamp(final List<CommandDto> commandDtos) {
+ return commandDtos.stream()
+ .map(CommandDto::getTimestamp)
+ .map(JavaSqlXMLGregorianCalendarMarshalling::toTimestamp)
+ .sorted()
+ .findFirst();
}
}
@@ -160,16 +182,10 @@ public CommandReplayManager act(
@Collection
@CollectionLayout(
- describedAs = "Imported Commands that can be either replayed (replayState=PENDING) "
- + "or retried (when replayState=FAILED)")
+ describedAs = "Imported Commands that can be either replayed (if PENDING) or retried (if FAILED)")
public List<ReplayableCommand> getPendingOrFailed() {
- return commandLogEntryRepository()
- .findForegroundSinceTimestampAndWithReplayPendingOrFailed(since)
- .stream()
- .map(entry->new ReplayableCommand(
- entry.getInteractionId(),
- replayContext))
- .collect(Collectors.toList());
+ return streamPendingOrFailed()
+ .toList();
}
@Action(
@@ -180,9 +196,9 @@ public List<ReplayableCommand> getPendingOrFailed() {
domainEvent = replayOrRetrySelected.DomainEvent.class,
executionPublishing = Publishing.DISABLED)
@ActionLayout(
- associateWith = "pendingOrFailed", sequence = "1.1",
- cssClassFa = "solid circle-play",
+ associateWith = "pendingOrFailed", sequence = "1.2",
cssClass = "btn-primary",
+ cssClassFa = "solid circle-play",
describedAs = "Executes the list of commands in sequence, after having sorted them by their timestamp. "
+ "If any of the given commands fails, "
+ "its surrounding transaction is rolled back, but any successful commands so far are marked OK). "
@@ -208,6 +224,34 @@ public class DomainEvent extends ActionDomainEvent<replayOrRetrySelected> { }
}
}
+ @Action(
+ restrictTo = RestrictTo.PROTOTYPING,
+ choicesFrom = "pendingOrFailed",
+ semantics = SemanticsOf.NON_IDEMPOTENT,
+ commandPublishing = Publishing.DISABLED,
+ domainEvent = replayOrRetrySelected.DomainEvent.class,
+ executionPublishing = Publishing.DISABLED
+ )
+ @ActionLayout(
+ associateWith = "pendingOrFailed", sequence = "1.3",
+ cssClassFa = "solid circle-play",
+ cssClass = "btn-primary",
+ describedAs = "Executes the oldest command.")
+ public class replayOrRetryNext {
+ public class DomainEvent extends ActionDomainEvent<replayOrRetrySelected> { }
+ @MemberSupport public CommandReplayManager act() {
+ var nextIfAny = streamPendingOrFailed().findFirst();
+ // should always be present, due to our guard
+ nextIfAny.ifPresent(ReplayableCommand::tryReplayOrRetry);
+ return CommandReplayManager.this;
+ }
+
+ @MemberSupport
+ public String disableAct() {
+ return sizePendingOrFailed() == 0 ? "No commands in collection" : null;
+ }
+ }
+
@Action(
restrictTo = RestrictTo.PROTOTYPING,
choicesFrom = "pendingOrFailed",
@@ -216,7 +260,8 @@ public class DomainEvent extends ActionDomainEvent<replayOrRetrySelected> { }
domainEvent = excludeSelectedFromReplay.DomainEvent.class,
executionPublishing = Publishing.DISABLED)
@ActionLayout(
- associateWith = "pendingOrFailed", sequence = "1.2",
+ associateWith = "pendingOrFailed", sequence = "1.4",
+ cssClass = "btn-secondary",
describedAs = "Marks selected Commands to be EXCLUDED from replay")
public class excludeSelectedFromReplay {
public class DomainEvent extends ActionDomainEvent<excludeSelectedFromReplay> {}
@@ -226,7 +271,7 @@ public class DomainEvent extends ActionDomainEvent<excludeSelectedFromReplay> {}
return CommandReplayManager.this;
}
@MemberSupport public String disableAct() {
- return getPendingOrFailed().isEmpty() ? "No commands in collection" : null;
+ return sizePendingOrFailed() == 0 ? "No commands in collection" : null;
}
@MemberSupport public String validateSelected(final List<ReplayableCommand> selected) {
return selected != null
@@ -244,7 +289,8 @@ public class DomainEvent extends ActionDomainEvent<excludeSelectedFromReplay> {}
domainEvent = deleteSelectedPendingOrFailed.DomainEvent.class,
executionPublishing = Publishing.DISABLED)
@ActionLayout(
- associateWith = "pendingOrFailed", sequence = "1.3",
+ associateWith = "pendingOrFailed", sequence = "1.4",
+ cssClass = "btn-danger",
named = "Delete Selected",
describedAs = "Deletes selected Commands (cannot be undone)")
public class deleteSelectedPendingOrFailed {
@@ -272,7 +318,7 @@ public class DomainEvent extends ActionDomainEvent<deleteSelectedPendingOrFailed
describedAs = "Imported Commands that were either replayed with success (replayState=OK) "
+ "or marked to be excluded from replay (replayState=EXCLUDE)")
public List<ReplayableCommand> getSucceededOrExcluded() {
- return commandLogEntryRepository().findSinceAndWithReplayOkOrExcluded(since).stream()
+ return commandLogEntryRepository().findSinceAndWithReplayOkOrExcluded(baseline).stream()
.map(entry->new ReplayableCommand(
entry.getInteractionId(),
replayContext))
@@ -311,7 +357,7 @@ public class DomainEvent extends ActionDomainEvent<deleteSelectedSucceededOrExcl
@Override
public String viewModelMemento() {
- return TimestampMarshallUtil.toString(this.since);
+ return TimestampMarshallUtil.toString(this.baseline);
}
// -- HELPER
@@ -320,7 +366,17 @@ private CommandLogEntryRepository commandLogEntryRepository() {
return replayContext.commandLogEntryRepository();
}
- private static Timestamp addSeconds(final Timestamp since, final int secondsToAdd) {
- return Timestamp.from(since.toInstant().plusSeconds(secondsToAdd));
+ private static Timestamp addSeconds(final Timestamp ts, final int secondsToAdd) {
+ return Timestamp.from(ts.toInstant().plusSeconds(secondsToAdd));
+ }
+
+ private Stream<ReplayableCommand> streamPendingOrFailed() {
+ return commandLogEntryRepository()
+ .findForegroundSinceTimestampAndWithReplayPendingOrFailed(baseline).stream()
+ .map(entry -> new ReplayableCommand(entry.getInteractionId(), replayContext));
+ }
+
+ private long sizePendingOrFailed() {
+ return streamPendingOrFailed().count();
}
}
diff --git a/extensions/core/commandlog/applib/src/main/java/org/apache/causeway/extensions/commandlog/applib/dom/replay/CommandReplayManager.layout.fallback.xml b/extensions/core/commandlog/applib/src/main/java/org/apache/causeway/extensions/commandlog/applib/dom/replay/CommandReplayManager.layout.fallback.xml
index c7aed354e9f..f10b0445aa8 100644
--- a/extensions/core/commandlog/applib/src/main/java/org/apache/causeway/extensions/commandlog/applib/dom/replay/CommandReplayManager.layout.fallback.xml
+++ b/extensions/core/commandlog/applib/src/main/java/org/apache/causeway/extensions/commandlog/applib/dom/replay/CommandReplayManager.layout.fallback.xml
@@ -4,11 +4,11 @@
<cpt:domainObject/>
</bs:col>
<bs:col span="3">
- <cpt:fieldSet name=" " id="since">
+ <cpt:fieldSet name=" " id="baseline">
<cpt:action id="previousHour"/>
- <cpt:action id="changeSince"/>
+ <cpt:action id="changeBaseline"/>
<cpt:action id="nextHour"/>
- <cpt:property id="since"/>
+ <cpt:property id="baseline"/>
</cpt:fieldSet>
</bs:col>
<bs:col span="3">
diff --git a/extensions/core/commandlog/applib/src/main/java/org/apache/causeway/extensions/commandlog/applib/dom/replay/ReplayableCommand.java b/extensions/core/commandlog/applib/src/main/java/org/apache/causeway/extensions/commandlog/applib/dom/replay/ReplayableCommand.java
index 04e3782ea37..82e249c5948 100644
--- a/extensions/core/commandlog/applib/src/main/java/org/apache/causeway/extensions/commandlog/applib/dom/replay/ReplayableCommand.java
+++ b/extensions/core/commandlog/applib/src/main/java/org/apache/causeway/extensions/commandlog/applib/dom/replay/ReplayableCommand.java
@@ -31,18 +31,23 @@
import org.springframework.transaction.annotation.Propagation;
import org.apache.causeway.applib.ViewModel;
+import org.apache.causeway.applib.annotation.Action;
+import org.apache.causeway.applib.annotation.ActionLayout;
import org.apache.causeway.applib.annotation.DomainObject;
import org.apache.causeway.applib.annotation.DomainObjectLayout;
import org.apache.causeway.applib.annotation.Introspection;
import org.apache.causeway.applib.annotation.LabelPosition;
+import org.apache.causeway.applib.annotation.MemberSupport;
import org.apache.causeway.applib.annotation.ObjectSupport;
import org.apache.causeway.applib.annotation.Programmatic;
import org.apache.causeway.applib.annotation.Property;
import org.apache.causeway.applib.annotation.PropertyLayout;
+import org.apache.causeway.applib.annotation.SemanticsOf;
import org.apache.causeway.applib.annotation.Where;
import org.apache.causeway.applib.fa.FontAwesomeLayers;
import org.apache.causeway.applib.jaxb.JavaTimeXMLGregorianCalendarMarshalling;
import org.apache.causeway.applib.services.bookmark.Bookmark;
+import org.apache.causeway.applib.services.bookmark.BookmarkService;
import org.apache.causeway.applib.services.command.CommandExecutorService.InteractionContextPolicy;
import org.apache.causeway.commons.functional.Try;
import org.apache.causeway.commons.internal.base._Refs.ObjectReference;
@@ -193,6 +198,26 @@ public String getTargetId() {
.orElse(null);
}
+ @Action(semantics = SemanticsOf.SAFE)
+ @ActionLayout(
+ cssClassFa = "fa-bullseye"
+ )
+ public class openTarget {
+ @Inject BookmarkService bookmarkService;
+
+ @MemberSupport public Object act() {
+ return commandLogEntry()
+ .map(CommandLogEntry::getTarget)
+ .flatMap(bookmark -> bookmarkService.lookup(bookmark))
+ .orElse(null);
+ }
+ @MemberSupport public String disableAct() {
+ return commandLogEntry().isPresent()
+ ? null
+ : "Unknown target";
+ }
+ }
+
@Property
@PropertyLayout(
sequence = "3.1",
diff --git a/extensions/core/commandlog/applib/src/main/java/org/apache/causeway/extensions/commandlog/applib/dom/replay/ReplayableCommand.layout.fallback.xml b/extensions/core/commandlog/applib/src/main/java/org/apache/causeway/extensions/commandlog/applib/dom/replay/ReplayableCommand.layout.fallback.xml
index 331644bba1a..4b3f8a8658d 100644
--- a/extensions/core/commandlog/applib/src/main/java/org/apache/causeway/extensions/commandlog/applib/dom/replay/ReplayableCommand.layout.fallback.xml
+++ b/extensions/core/commandlog/applib/src/main/java/org/apache/causeway/extensions/commandlog/applib/dom/replay/ReplayableCommand.layout.fallback.xml
@@ -37,6 +37,7 @@
</bs:tab>
</bs:tabGroup>
<cpt:fieldSet name="Target" id="target">
+ <cpt:action id="openTarget" position="PANEL"/>
<cpt:property id="targetType"/>
<cpt:property id="targetId"/>
<cpt:property id="member"/>