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/09 09:55:14 UTC
(causeway) 01/01: CAUSEWAY-3989: Command Replay Manager (stub)
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
commit f84a0f689dc7dc152d4638b4e8658deff270c4a7
Author: andi-huber <ah...@apache.org>
AuthorDate: Thu Apr 9 11:55:03 2026 +0200
CAUSEWAY-3989: Command Replay Manager (stub)
---
.../applib/CausewayModuleExtCommandLogApplib.java | 4 ++
.../commandlog/applib/app/CommandLogMenu.java | 18 +++++
.../commandlog/applib/dom/CommandLogEntry.java | 27 +++++---
.../applib/dom/CommandLogEntryRepository.java | 45 ++++++------
.../dom/CommandLogEntryRepositoryAbstract.java | 28 ++++----
.../applib/dom/replay/CommandReplayManager.java | 80 ++++++++++++++++++++++
6 files changed, 160 insertions(+), 42 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 64303bf8c12..e7e71289f54 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
@@ -31,6 +31,7 @@
import org.apache.causeway.extensions.commandlog.applib.dom.mixins.CommandLogEntry_childCommands;
import org.apache.causeway.extensions.commandlog.applib.dom.mixins.CommandLogEntry_openResultObject;
import org.apache.causeway.extensions.commandlog.applib.dom.mixins.CommandLogEntry_siblingCommands;
+import org.apache.causeway.extensions.commandlog.applib.dom.replay.CommandReplayManager;
import org.apache.causeway.extensions.commandlog.applib.fakescheduler.FakeScheduler;
import org.apache.causeway.extensions.commandlog.applib.job.BackgroundCommandsJobControl;
import org.apache.causeway.extensions.commandlog.applib.job.RunBackgroundCommandsJob;
@@ -42,6 +43,9 @@
// @DomainService's
CommandLogMenu.class,
+ // viewmodels
+ CommandReplayManager.class,
+
// mixins
HasInteractionId_commandLogEntry.class,
HasUsername_recentCommandsByUser.class,
diff --git a/extensions/core/commandlog/applib/src/main/java/org/apache/causeway/extensions/commandlog/applib/app/CommandLogMenu.java b/extensions/core/commandlog/applib/src/main/java/org/apache/causeway/extensions/commandlog/applib/app/CommandLogMenu.java
index 8ec1c82c712..b80f5c3a918 100644
--- a/extensions/core/commandlog/applib/src/main/java/org/apache/causeway/extensions/commandlog/applib/app/CommandLogMenu.java
+++ b/extensions/core/commandlog/applib/src/main/java/org/apache/causeway/extensions/commandlog/applib/app/CommandLogMenu.java
@@ -40,6 +40,7 @@
import org.apache.causeway.extensions.commandlog.applib.CausewayModuleExtCommandLogApplib;
import org.apache.causeway.extensions.commandlog.applib.dom.CommandLogEntry;
import org.apache.causeway.extensions.commandlog.applib.dom.CommandLogEntryRepository;
+import org.apache.causeway.extensions.commandlog.applib.dom.replay.CommandReplayManager;
import lombok.RequiredArgsConstructor;
@@ -141,6 +142,23 @@ public class DomainEvent extends ActionDomainEvent<findAll> { }
}
}
+ @Action(
+ commandPublishing = Publishing.DISABLED,
+ domainEvent = replayManager.DomainEvent.class,
+ executionPublishing = Publishing.DISABLED,
+ restrictTo = RestrictTo.PROTOTYPING,
+ semantics = SemanticsOf.SAFE
+ )
+ @ActionLayout(cssClassFa = "solid circle-play", sequence="50")
+ public class replayManager {
+ public class DomainEvent extends ActionDomainEvent<replayManager> { }
+
+ @MemberSupport public CommandReplayManager act() {
+ return new CommandReplayManager(null, commandLogEntryRepository);
+ }
+ }
+
+
private LocalDate now() {
return clockService.getClock().nowAsLocalDate(ZoneId.systemDefault());
}
diff --git a/extensions/core/commandlog/applib/src/main/java/org/apache/causeway/extensions/commandlog/applib/dom/CommandLogEntry.java b/extensions/core/commandlog/applib/src/main/java/org/apache/causeway/extensions/commandlog/applib/dom/CommandLogEntry.java
index 160af93de38..3911666f4ea 100644
--- a/extensions/core/commandlog/applib/src/main/java/org/apache/causeway/extensions/commandlog/applib/dom/CommandLogEntry.java
+++ b/extensions/core/commandlog/applib/src/main/java/org/apache/causeway/extensions/commandlog/applib/dom/CommandLogEntry.java
@@ -26,6 +26,7 @@
import java.util.Arrays;
import java.util.List;
import java.util.Objects;
+import java.util.Optional;
import java.util.UUID;
import java.util.function.Consumer;
@@ -191,8 +192,11 @@ default void init(
// the hierarchy of commands calling other commands is only available on the primary system.
setParentInteractionId(null);
- setStartedAt(JavaSqlXMLGregorianCalendarMarshalling.toTimestamp(commandDto.getTimings().getStartedAt()));
- setCompletedAt(JavaSqlXMLGregorianCalendarMarshalling.toTimestamp(commandDto.getTimings().getCompletedAt()));
+ Optional.ofNullable(commandDto.getTimings())
+ .ifPresent(timings->{
+ setStartedAt(JavaSqlXMLGregorianCalendarMarshalling.toTimestamp(timings.getStartedAt()));
+ setCompletedAt(JavaSqlXMLGregorianCalendarMarshalling.toTimestamp(timings.getCompletedAt()));
+ });
copyOver(commandDto, UserDataKeys.RESULT, value -> this.setResult(Bookmark.parse(value).orElse(null)));
copyOver(commandDto, UserDataKeys.EXCEPTION, this::setException);
@@ -203,14 +207,17 @@ default void init(
static void copyOver(
final CommandDto commandDto,
final String key, final Consumer<String> consume) {
- commandDto.getUserData().getEntry()
- .stream()
- .filter(x -> Objects.equals(x.getKey(), key))
- .map(MapDto.Entry::getValue)
- .filter(Objects::nonNull)
- .filter(x -> x.length() > 0)
- .findFirst()
- .ifPresent(consume);
+ Optional.ofNullable(commandDto.getUserData())
+ .ifPresent(userdata->{
+ userdata.getEntry()
+ .stream()
+ .filter(x -> Objects.equals(x.getKey(), key))
+ .map(MapDto.Entry::getValue)
+ .filter(Objects::nonNull)
+ .filter(x -> x.length() > 0)
+ .findFirst()
+ .ifPresent(consume);
+ });
}
static final DateTimeFormatter DATETIME_FORMATTER =
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 97146dc08df..fef06fb7d07 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
@@ -51,36 +51,41 @@ public NotFoundException(final UUID interactionId) {
}
CommandLogEntry createEntryAndPersist(
- final Command command, final UUID parentInteractionIdIfAny, final ExecuteIn executeIn);
+ Command command, UUID parentInteractionIdIfAny, final ExecuteIn executeIn);
- Optional<CommandLogEntry> findByInteractionId(final UUID interactionId);
+ CommandLogEntry createAsPending(CommandDto commandToReplay, int targetIndex);
- List<CommandLogEntry> findByParent(final CommandLogEntry parent);
+ Optional<CommandLogEntry> findByInteractionId(UUID interactionId);
- List<CommandLogEntry> findByParentInteractionId(final UUID parentInteractionId);
+ List<CommandLogEntry> findByParent(CommandLogEntry parent);
+
+ List<CommandLogEntry> findByParentInteractionId(UUID parentInteractionId);
List<CommandLogEntry> findByFromAndTo(
- final @Nullable LocalDate from,
- final @Nullable LocalDate to);
+ @Nullable LocalDate from,
+ @Nullable LocalDate to);
+ /**
+ * Still running, not completed yet.
+ */
List<CommandLogEntry> findCurrent();
List<CommandLogEntry> findCompleted();
List<CommandLogEntry> findByTargetAndFromAndTo(
- final Bookmark target,
- final @Nullable LocalDate from,
- final @Nullable LocalDate to);
+ Bookmark target,
+ @Nullable LocalDate from,
+ @Nullable LocalDate to);
List<CommandLogEntry> findMostRecent();
- List<CommandLogEntry> findMostRecent(final int limit);
+ List<CommandLogEntry> findMostRecent(int limit);
- List<CommandLogEntry> findRecentByUsername(final String username);
+ List<CommandLogEntry> findRecentByUsername(String username);
- List<CommandLogEntry> findRecentByTarget(final Bookmark target);
+ List<CommandLogEntry> findRecentByTarget(Bookmark target);
- List<CommandLogEntry> findRecentByTargetOrResult(final Bookmark targetOrResult);
+ List<CommandLogEntry> findRecentByTargetOrResult(Bookmark targetOrResult);
/**
* Intended to support the replay of commands on a secondary instance of
@@ -112,7 +117,7 @@ List<CommandLogEntry> findByTargetAndFromAndTo(
* @param batchSize - to restrict the number returned (so that replay
* commands can be batched).
*/
- List<CommandLogEntry> findSince(final UUID interactionId, final Integer batchSize);
+ List<CommandLogEntry> findSince(UUID interactionId, Integer batchSize);
/**
* Returns any persisted commands that have not yet started.
@@ -126,7 +131,7 @@ List<CommandLogEntry> findByTargetAndFromAndTo(
*/
List<CommandLogEntry> findBackgroundAndNotYetStarted();
- List<CommandLogEntry> findRecentBackgroundByTarget(final Bookmark target);
+ List<CommandLogEntry> findRecentBackgroundByTarget(Bookmark target);
/**
* The most recent replayed command previously replicated from primary to
@@ -154,19 +159,19 @@ List<CommandLogEntry> findByTargetAndFromAndTo(
List<CommandLogEntry> findNotYetReplayed();
- CommandLogEntry saveForReplay(final CommandDto dto);
+ CommandLogEntry saveForReplay(CommandDto dto);
- List<CommandLogEntry> saveForReplay(final CommandsDto commandsDto);
+ List<CommandLogEntry> saveForReplay(CommandsDto commandsDto);
- void persist(final CommandLogEntry commandLogEntry);
+ void persist(CommandLogEntry commandLogEntry);
void truncateLog();
// --
List<CommandLogEntry> findCommandsOnPrimaryElseFail(
- final @Nullable UUID interactionId,
- final @Nullable Integer batchSize) throws NotFoundException;
+ @Nullable UUID interactionId,
+ @Nullable Integer batchSize) throws NotFoundException;
/**
* intended for testing purposes only
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 804fbc92179..67371bd75a2 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
@@ -80,6 +80,16 @@ public C createEntryAndPersist(
return c;
}
+ @Override
+ public C createAsPending(final CommandDto commandToReplay, final int targetIndex) {
+ C c = factoryService.detachedEntity(commandLogEntryClass);
+ c.init(commandToReplay, ReplayState.PENDING, targetIndex);
+ c.setParentInteractionId(null); // n/a for replay
+ c.setExecuteIn(null); // to be specified later depending on user action
+ persist(c);
+ return c;
+ }
+
@Override
public Optional<CommandLogEntry> findByInteractionId(final UUID interactionId) {
return _Casts.uncheckedCast(
@@ -257,13 +267,11 @@ public List<CommandLogEntry> findRecentByTargetOrResult(final Bookmark targetOrR
*/
@Override
public List<CommandLogEntry> findSince(final UUID interactionId, final Integer batchSize) {
- if(interactionId == null) {
+ if(interactionId == null)
return findFirst();
- }
final C from = findByInteractionIdElseNull(interactionId);
- if(from == null) {
+ if(from == null)
return Collections.emptyList();
- }
return findSince(from.getTimestamp(), batchSize);
}
@@ -355,11 +363,10 @@ public C saveForReplay(final CommandDto dto) {
if(dto.getMember().getInteractionType() == InteractionType.ACTION_INVOCATION) {
final MapDto userData = dto.getUserData();
- if (userData == null ) {
+ if (userData == null )
throw new IllegalStateException(String.format(
"Can only persist action DTOs with additional userData; got: \n%s",
CommandDtoUtils.dtoMapper().toString(dto)));
- }
}
final C commandJdo = factoryService.detachedEntity(commandLogEntryClass);
@@ -408,9 +415,8 @@ public List<CommandLogEntry> findCommandsOnPrimaryElseFail(
final @Nullable Integer batchSize) throws NotFoundException {
final List<CommandLogEntry> commands = findSince(interactionId, batchSize);
- if(commands == null) {
+ if(commands == null)
throw new NotFoundException(interactionId);
- }
return commands;
}
@@ -460,9 +466,8 @@ private static Timestamp toTimestampStartOfDayWithOffset(
*/
@Override
public List<CommandLogEntry> findAll() {
- if (causewaySystemEnvironment.deploymentType().isProduction()) {
+ if (causewaySystemEnvironment.deploymentType().isProduction())
throw new IllegalStateException("Cannot call 'findAll' in production systems");
- }
return _Casts.uncheckedCast(repositoryService().allInstances(commandLogEntryClass));
}
@@ -471,9 +476,8 @@ public List<CommandLogEntry> findAll() {
*/
@Override
public void removeAll() {
- if (causewaySystemEnvironment.deploymentType().isProduction()) {
+ if (causewaySystemEnvironment.deploymentType().isProduction())
throw new IllegalStateException("Cannot call 'removeAll' in production systems");
- }
repositoryService().removeAll(commandLogEntryClass);
}
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
new file mode 100644
index 00000000000..7d13391054d
--- /dev/null
+++ b/extensions/core/commandlog/applib/src/main/java/org/apache/causeway/extensions/commandlog/applib/dom/replay/CommandReplayManager.java
@@ -0,0 +1,80 @@
+/*
+ * 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.causeway.extensions.commandlog.applib.dom.replay;
+
+import java.util.List;
+
+import jakarta.inject.Named;
+
+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.DomainObjectLayout;
+import org.apache.causeway.applib.annotation.ObjectSupport;
+import org.apache.causeway.applib.annotation.Parameter;
+import org.apache.causeway.applib.util.schema.CommandDtoUtils;
+import org.apache.causeway.applib.value.Blob;
+import org.apache.causeway.applib.value.NamedWithMimeType.CommonMimeType;
+import org.apache.causeway.commons.io.JsonUtils;
+import org.apache.causeway.commons.io.YamlUtils;
+import org.apache.causeway.extensions.commandlog.applib.CausewayModuleExtCommandLogApplib;
+import org.apache.causeway.extensions.commandlog.applib.dom.CommandLogEntryRepository;
+import org.apache.causeway.schema.cmd.v2.CommandDto;
+
+@DomainObjectLayout(cssClassFa = "solid circle-play")
+@Named(CommandReplayManager.LOGICAL_TYPE_NAME)
+public class CommandReplayManager implements ViewModel {
+
+ public static final String LOGICAL_TYPE_NAME = CausewayModuleExtCommandLogApplib.NAMESPACE + ".CommandReplayManager";
+
+ private final CommandLogEntryRepository commandLogEntryRepository;
+
+ public CommandReplayManager(final String memento, final CommandLogEntryRepository commandLogEntryRepository) {
+ this.commandLogEntryRepository = commandLogEntryRepository;
+ }
+
+ @ObjectSupport public String title() {
+ return "Command Replay Manager";
+ }
+
+ @Action
+ @ActionLayout(describedAs = "Imports commands from a zipped yaml, then persists them with replayState=PENDING.")
+ public String importCommands(
+ @Parameter(fileAccept = ".zip")
+ final Blob zippedCommandsYaml) {
+
+ var yamlDs = zippedCommandsYaml.unZip(CommonMimeType.YAML).asDataSource();
+
+ final List<CommandDto> commandDtos = CommandDtoUtils.fromYaml(yamlDs);
+ commandDtos.forEach(commandDto->commandLogEntryRepository.createAsPending(commandDto, 0));
+
+ var yaml = YamlUtils.toStringUtf8(commandDtos,
+ JsonUtils::onlyIncludeNonNull);
+
+ return yaml;
+ }
+
+
+ @Override
+ public String viewModelMemento() {
+ // TODO Auto-generated method stub
+ return null;
+ }
+
+}