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/04/15 05:40:35 UTC

(causeway) branch 3989-cmd-io updated: CAUSEWAY-3989: Command Replay Manager w/ 2 tables (PENDING/FAILED and OK/EXCLUDE)

This is an automated email from the ASF dual-hosted git repository.

ahuber pushed a commit to branch 3989-cmd-io
in repository https://gitbox.apache.org/repos/asf/causeway.git


The following commit(s) were added to refs/heads/3989-cmd-io by this push:
     new 08ddf31157d CAUSEWAY-3989: Command Replay Manager w/ 2 tables (PENDING/FAILED and OK/EXCLUDE)
08ddf31157d is described below

commit 08ddf31157dedea1213a8b0de6b4344832b48271
Author: andi-huber <ah...@apache.org>
AuthorDate: Wed Apr 15 07:40:27 2026 +0200

    CAUSEWAY-3989: Command Replay Manager w/ 2 tables (PENDING/FAILED and
    OK/EXCLUDE)
    
    first allows replay or retry
    second collects those that are done
---
 .../applib/dom/CommandLogEntryRepository.java      |  9 +++-
 .../dom/CommandLogEntryRepositoryAbstract.java     | 19 ++++++--
 .../applib/dom/replay/CommandReplayManager.java    | 56 ++++++++++++++++------
 .../dom/replay/CommandReplayManager.layout.xml     |  7 ++-
 .../applib/dom/replay/ReplayableCommand.java       | 18 ++++++-
 .../integtest/CommandLog_IntegTestAbstract.java    |  4 +-
 .../commandlog/jpa/dom/CommandLogEntry.java        |  9 ++--
 7 files changed, 94 insertions(+), 28 deletions(-)

diff --git a/extensions/core/commandlog/applib/src/main/java/org/apache/causeway/extensions/commandlog/applib/dom/CommandLogEntryRepository.java b/extensions/core/commandlog/applib/src/main/java/org/apache/causeway/extensions/commandlog/applib/dom/CommandLogEntryRepository.java
index 7b8e1f913c9..d067f446e76 100644
--- a/extensions/core/commandlog/applib/src/main/java/org/apache/causeway/extensions/commandlog/applib/dom/CommandLogEntryRepository.java
+++ b/extensions/core/commandlog/applib/src/main/java/org/apache/causeway/extensions/commandlog/applib/dom/CommandLogEntryRepository.java
@@ -158,7 +158,14 @@ List<CommandLogEntry> findByTargetAndFromAndTo(
      */
     Optional<CommandLogEntry> findMostRecentCompleted();
 
-    List<CommandLogEntry> findNotYetReplayed();
+    /**
+     * Command Replay feature: Can replay or retry.
+     */
+    List<CommandLogEntry> findReplayPendingOrFailed();
+    /**
+     * Command Replay feature: Cannot replay or retry.
+     */
+    List<CommandLogEntry> findReplaySucceededOrExcluded();
 
     CommandLogEntry saveForReplay(CommandDto dto);
 
diff --git a/extensions/core/commandlog/applib/src/main/java/org/apache/causeway/extensions/commandlog/applib/dom/CommandLogEntryRepositoryAbstract.java b/extensions/core/commandlog/applib/src/main/java/org/apache/causeway/extensions/commandlog/applib/dom/CommandLogEntryRepositoryAbstract.java
index 3b0d840a2a0..bacaa3bfda6 100644
--- a/extensions/core/commandlog/applib/src/main/java/org/apache/causeway/extensions/commandlog/applib/dom/CommandLogEntryRepositoryAbstract.java
+++ b/extensions/core/commandlog/applib/src/main/java/org/apache/causeway/extensions/commandlog/applib/dom/CommandLogEntryRepositoryAbstract.java
@@ -333,14 +333,25 @@ public Optional<CommandLogEntry> findMostRecentCompleted() {
                     Query.named(commandLogEntryClass, CommandLogEntry.Nq.FIND_MOST_RECENT_COMPLETED))
         );
     }
-
     @Override
-    public List<CommandLogEntry> findNotYetReplayed() {
+    public List<CommandLogEntry> findReplayPendingOrFailed() {
+        return _Casts.uncheckedCast(
+                repositoryService().allMatches(
+                    Query.named(commandLogEntryClass, CommandLogEntry.Nq.FIND_BY_REPLAY_STATE)
+                        .withParameter("replayState1", ReplayState.PENDING)
+                        .withParameter("replayState2", ReplayState.FAILED))
+        );
+    }
+    /**
+     * Command Replay feature: Cannot replay or retry.
+     */
+    @Override
+    public List<CommandLogEntry> findReplaySucceededOrExcluded() {
         return _Casts.uncheckedCast(
                 repositoryService().allMatches(
                     Query.named(commandLogEntryClass, CommandLogEntry.Nq.FIND_BY_REPLAY_STATE)
-                        .withParameter("replayState", ReplayState.PENDING)
-                        .withLimit(10))
+                        .withParameter("replayState1", ReplayState.OK)
+                        .withParameter("replayState2", ReplayState.EXCLUDED))
         );
     }
 
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 efe179ca14c..72303f3a217 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
@@ -39,7 +39,6 @@
 import org.apache.causeway.applib.value.NamedWithMimeType.CommonMimeType;
 import org.apache.causeway.extensions.commandlog.applib.CausewayModuleExtCommandLogApplib;
 import org.apache.causeway.extensions.commandlog.applib.dom.CommandLogEntryRepository;
-import org.apache.causeway.extensions.commandlog.applib.dom.ReplayState;
 import org.apache.causeway.schema.cmd.v2.CommandDto;
 
 @DomainObject(introspection = Introspection.ANNOTATION_REQUIRED)
@@ -76,39 +75,66 @@ public CommandReplayManager importCommands(
         return this;
     }
 
-    // -- NOT YET REPLAYED
+    // -- PENDING OR FAILED
 
     @Collection
-    public List<ReplayableCommand> getNotYetReplayed() {
-        return commandLogEntryRepository().findNotYetReplayed().stream()
+    public List<ReplayableCommand> getPendingOrFailed() {
+        return commandLogEntryRepository().findReplayPendingOrFailed().stream()
             .map(entry->new ReplayableCommand(
                     replayContext.bookmarkService().bookmarkFor(entry).get().identifier(),
                     replayContext))
             .toList();
     }
 
-    @Action(choicesFrom = "notYetReplayed")
-    @ActionLayout(associateWith = "notYetReplayed")
-    public CommandReplayManager replaySelected(final List<ReplayableCommand> selected) {
+    @Action(choicesFrom = "pendingOrFailed")
+    @ActionLayout(associateWith = "pendingOrFailed",
+        sequence = "1.1",
+        cssClassFa = "solid circle-play",
+        cssClass = "btn-primary")
+    public CommandReplayManager replayOrRetrySelected(final List<ReplayableCommand> selected) {
         selected.stream()
-            .filter(c->c.getReplayState() == ReplayState.PENDING)
-            .forEach(ReplayableCommand::replay);
+            .forEach(ReplayableCommand::replay); // filtered on its own responsibility
         return this;
     }
 
-    @Action(choicesFrom = "notYetReplayed")
-    @ActionLayout(associateWith = "notYetReplayed",
+    @Action(choicesFrom = "pendingOrFailed")
+    @ActionLayout(associateWith = "pendingOrFailed", sequence = "1.2",
             describedAs = "Marks selected Commands to be EXCLUDED from replay.")
     public CommandReplayManager excludeSelectedFromReplay(final List<ReplayableCommand> selected) {
         selected.stream()
-            .filter(c->c.getReplayState() == ReplayState.PENDING)
-            .forEach(ReplayableCommand::excludeFromReplay);
+            .forEach(ReplayableCommand::excludeFromReplay); // filtered on its own responsibility
         return this;
     }
 
-    // -- FAILED //TODO
+    @Action(choicesFrom = "pendingOrFailed")
+    @ActionLayout(associateWith = "pendingOrFailed", sequence = "1.3",
+            describedAs = "Deletes selected Commands.")
+    public CommandReplayManager deleteSelected(final List<ReplayableCommand> selected) {
+        selected.stream()
+            .forEach(ReplayableCommand::delete); // filtered on its own responsibility
+        return this;
+    }
+
+    // -- OK OR EXCLUDE
+
+    @Collection
+    public List<ReplayableCommand> getSucceededOrExcluded() {
+        return commandLogEntryRepository().findReplaySucceededOrExcluded().stream()
+            .map(entry->new ReplayableCommand(
+                    replayContext.bookmarkService().bookmarkFor(entry).get().identifier(),
+                    replayContext))
+            .toList();
+    }
 
-    // -- OK //TODO
+    @Action(choicesFrom = "succeededOrExcluded")
+    @ActionLayout(associateWith = "succeededOrExcluded",
+            named = "Delete Selected",
+            describedAs = "Deletes selected Commands.")
+    public CommandReplayManager deleteSelected2(final List<ReplayableCommand> selected) {
+        selected.stream()
+            .forEach(ReplayableCommand::delete); // filtered on its own responsibility
+        return this;
+    }
 
     // -- VM STATE
 
diff --git a/extensions/core/commandlog/applib/src/main/java/org/apache/causeway/extensions/commandlog/applib/dom/replay/CommandReplayManager.layout.xml b/extensions/core/commandlog/applib/src/main/java/org/apache/causeway/extensions/commandlog/applib/dom/replay/CommandReplayManager.layout.xml
index 1f09f254ecc..30489ca2158 100644
--- a/extensions/core/commandlog/applib/src/main/java/org/apache/causeway/extensions/commandlog/applib/dom/replay/CommandReplayManager.layout.xml
+++ b/extensions/core/commandlog/applib/src/main/java/org/apache/causeway/extensions/commandlog/applib/dom/replay/CommandReplayManager.layout.xml
@@ -33,7 +33,12 @@
         <bs:col span="12">
             <bs:row>
                 <bs:col span="12" size="MD">
-                    <cpt:collection defaultView="table" paged="15" id="notYetReplayed"/>
+                    <cpt:collection defaultView="table" paged="15" id="pendingOrFailed"/>
+                </bs:col>
+            </bs:row>
+            <bs:row>
+                <bs:col span="12" size="MD">
+                    <cpt:collection defaultView="table" paged="15" id="succeededOrExcluded"/>
                 </bs:col>
             </bs:row>
         </bs:col>
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 63af5ad1955..4550c7e8b6e 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
@@ -203,6 +203,7 @@ public ReplayableCommand replay() {
         if(disableReplay()!=null)
             return this; // safe guard when called programmatically
         commandLogEntry()
+            .filter(ReplayableCommand::canReplayOrRetryOrMarkForExclusion)
             .ifPresent(commandLogEntry->{
                 commandLogEntry.setExecuteIn(ExecuteIn.FOREGROUND);
                 replayContext.commandExecutorService().executeCommand(commandLogEntry.getCommandDto());
@@ -226,7 +227,7 @@ public ReplayableCommand excludeFromReplay() {
         if(disableExcludeFromReplay()!=null)
             return this; // safe guard when called programmatically
         commandLogEntry()
-            .filter(commandLogEntry->commandLogEntry.getReplayState() == ReplayState.PENDING)
+            .filter(ReplayableCommand::canReplayOrRetryOrMarkForExclusion)
             .ifPresent(commandLogEntry->commandLogEntry.setReplayState(ReplayState.EXCLUDED));
         return this;
     }
@@ -238,6 +239,16 @@ public ReplayableCommand excludeFromReplay() {
                 : "Cannot mark for exclusion, if not PENDING nor FAILED";
     }
 
+    @Action
+    @ActionLayout(
+            //hidden = Where.NOWHERE, // show in tables //TODO NPE bug
+            describedAs = "Marks Command to be EXCLUDED from replay.")
+    public ReplayableCommand delete() {
+        commandLogEntry()
+            .ifPresent(commandLogEntry->commandLogEntry.setReplayState(ReplayState.EXCLUDED));
+        return this;
+    }
+
     // -- VM STATE
 
     @Override
@@ -268,4 +279,9 @@ private Bookmark commandLogEntryBookmark() {
                     commandLogEntryId));
     }
 
+    private static boolean canReplayOrRetryOrMarkForExclusion(final CommandLogEntry commandLogEntry) {
+        return commandLogEntry.getReplayState() == ReplayState.PENDING
+                || commandLogEntry.getReplayState() == ReplayState.FAILED;
+    }
+
 }
diff --git a/extensions/core/commandlog/applib/src/test/java/org/apache/causeway/extensions/commandlog/applib/integtest/CommandLog_IntegTestAbstract.java b/extensions/core/commandlog/applib/src/test/java/org/apache/causeway/extensions/commandlog/applib/integtest/CommandLog_IntegTestAbstract.java
index 8909aa77aac..1c94cd85468 100644
--- a/extensions/core/commandlog/applib/src/test/java/org/apache/causeway/extensions/commandlog/applib/integtest/CommandLog_IntegTestAbstract.java
+++ b/extensions/core/commandlog/applib/src/test/java/org/apache/causeway/extensions/commandlog/applib/integtest/CommandLog_IntegTestAbstract.java
@@ -361,7 +361,7 @@ void test_all_the_repository_methods() {
         var to = from.plusDays(1);
 
         // when
-        List<? extends CommandLogEntry> notYetReplayed = commandLogEntryRepository.findNotYetReplayed();
+        List<? extends CommandLogEntry> notYetReplayed = commandLogEntryRepository.findReplayPendingOrFailed();
 
         // then
         Assertions.assertThat(notYetReplayed).isEmpty();
@@ -374,7 +374,7 @@ void test_all_the_repository_methods() {
             commandTarget1User1.setReplayState(ReplayState.PENDING);
 
             // when
-            List<? extends CommandLogEntry> notYetReplayed2 = commandLogEntryRepository.findNotYetReplayed();
+            List<? extends CommandLogEntry> notYetReplayed2 = commandLogEntryRepository.findReplayPendingOrFailed();
 
             // then
             Assertions.assertThat(notYetReplayed2).hasSize(1);
diff --git a/extensions/core/commandlog/persistence-jpa/src/main/java/org/apache/causeway/extensions/commandlog/jpa/dom/CommandLogEntry.java b/extensions/core/commandlog/persistence-jpa/src/main/java/org/apache/causeway/extensions/commandlog/jpa/dom/CommandLogEntry.java
index a760611bc0e..0ac083a3198 100644
--- a/extensions/core/commandlog/persistence-jpa/src/main/java/org/apache/causeway/extensions/commandlog/jpa/dom/CommandLogEntry.java
+++ b/extensions/core/commandlog/persistence-jpa/src/main/java/org/apache/causeway/extensions/commandlog/jpa/dom/CommandLogEntry.java
@@ -198,7 +198,8 @@
             name  = Nq.FIND_MOST_RECENT_REPLAYED,
             query = "SELECT cl "
                   + "  FROM CommandLogEntry cl "
-                  + " WHERE (cl.replayState = org.apache.causeway.extensions.commandlog.applib.dom.ReplayState.OK OR cl.replayState = org.apache.causeway.extensions.commandlog.applib.dom.ReplayState.FAILED) "
+                  + " WHERE (cl.replayState = org.apache.causeway.extensions.commandlog.applib.dom.ReplayState.OK"
+                  + " OR cl.replayState = org.apache.causeway.extensions.commandlog.applib.dom.ReplayState.FAILED) "
                   + " ORDER BY cl.timestamp DESC"), // programmatic LIMIT 1
     @NamedQuery(
             name  = Nq.FIND_MOST_RECENT_COMPLETED,
@@ -210,9 +211,9 @@
     @NamedQuery(
             name  = Nq.FIND_BY_REPLAY_STATE,
             query = "SELECT cl "
-                  + "  FROM CommandLogEntry cl "
-                  + " WHERE cl.replayState = :replayState "
-                  + " ORDER BY cl.timestamp ASC"), // programmatic LIMIT 10
+                  + " FROM CommandLogEntry cl "
+                  + " WHERE (cl.replayState = :replayState1 OR cl.replayState = :replayState2) "
+                  + " ORDER BY cl.timestamp ASC"),
 })
 @Named(CommandLogEntry.LOGICAL_TYPE_NAME)
 @DomainObject(