Skip to content

Commit f84a0f6

Browse files
committed
CAUSEWAY-3989: Command Replay Manager (stub)
1 parent a4360e8 commit f84a0f6

6 files changed

Lines changed: 160 additions & 42 deletions

File tree

extensions/core/commandlog/applib/src/main/java/org/apache/causeway/extensions/commandlog/applib/CausewayModuleExtCommandLogApplib.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
import org.apache.causeway.extensions.commandlog.applib.dom.mixins.CommandLogEntry_childCommands;
3232
import org.apache.causeway.extensions.commandlog.applib.dom.mixins.CommandLogEntry_openResultObject;
3333
import org.apache.causeway.extensions.commandlog.applib.dom.mixins.CommandLogEntry_siblingCommands;
34+
import org.apache.causeway.extensions.commandlog.applib.dom.replay.CommandReplayManager;
3435
import org.apache.causeway.extensions.commandlog.applib.fakescheduler.FakeScheduler;
3536
import org.apache.causeway.extensions.commandlog.applib.job.BackgroundCommandsJobControl;
3637
import org.apache.causeway.extensions.commandlog.applib.job.RunBackgroundCommandsJob;
@@ -42,6 +43,9 @@
4243
// @DomainService's
4344
CommandLogMenu.class,
4445

46+
// viewmodels
47+
CommandReplayManager.class,
48+
4549
// mixins
4650
HasInteractionId_commandLogEntry.class,
4751
HasUsername_recentCommandsByUser.class,

extensions/core/commandlog/applib/src/main/java/org/apache/causeway/extensions/commandlog/applib/app/CommandLogMenu.java

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@
4040
import org.apache.causeway.extensions.commandlog.applib.CausewayModuleExtCommandLogApplib;
4141
import org.apache.causeway.extensions.commandlog.applib.dom.CommandLogEntry;
4242
import org.apache.causeway.extensions.commandlog.applib.dom.CommandLogEntryRepository;
43+
import org.apache.causeway.extensions.commandlog.applib.dom.replay.CommandReplayManager;
4344

4445
import lombok.RequiredArgsConstructor;
4546

@@ -141,6 +142,23 @@ public class DomainEvent extends ActionDomainEvent<findAll> { }
141142
}
142143
}
143144

145+
@Action(
146+
commandPublishing = Publishing.DISABLED,
147+
domainEvent = replayManager.DomainEvent.class,
148+
executionPublishing = Publishing.DISABLED,
149+
restrictTo = RestrictTo.PROTOTYPING,
150+
semantics = SemanticsOf.SAFE
151+
)
152+
@ActionLayout(cssClassFa = "solid circle-play", sequence="50")
153+
public class replayManager {
154+
public class DomainEvent extends ActionDomainEvent<replayManager> { }
155+
156+
@MemberSupport public CommandReplayManager act() {
157+
return new CommandReplayManager(null, commandLogEntryRepository);
158+
}
159+
}
160+
161+
144162
private LocalDate now() {
145163
return clockService.getClock().nowAsLocalDate(ZoneId.systemDefault());
146164
}

extensions/core/commandlog/applib/src/main/java/org/apache/causeway/extensions/commandlog/applib/dom/CommandLogEntry.java

Lines changed: 17 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
import java.util.Arrays;
2727
import java.util.List;
2828
import java.util.Objects;
29+
import java.util.Optional;
2930
import java.util.UUID;
3031
import java.util.function.Consumer;
3132

@@ -191,8 +192,11 @@ default void init(
191192
// the hierarchy of commands calling other commands is only available on the primary system.
192193
setParentInteractionId(null);
193194

194-
setStartedAt(JavaSqlXMLGregorianCalendarMarshalling.toTimestamp(commandDto.getTimings().getStartedAt()));
195-
setCompletedAt(JavaSqlXMLGregorianCalendarMarshalling.toTimestamp(commandDto.getTimings().getCompletedAt()));
195+
Optional.ofNullable(commandDto.getTimings())
196+
.ifPresent(timings->{
197+
setStartedAt(JavaSqlXMLGregorianCalendarMarshalling.toTimestamp(timings.getStartedAt()));
198+
setCompletedAt(JavaSqlXMLGregorianCalendarMarshalling.toTimestamp(timings.getCompletedAt()));
199+
});
196200

197201
copyOver(commandDto, UserDataKeys.RESULT, value -> this.setResult(Bookmark.parse(value).orElse(null)));
198202
copyOver(commandDto, UserDataKeys.EXCEPTION, this::setException);
@@ -203,14 +207,17 @@ default void init(
203207
static void copyOver(
204208
final CommandDto commandDto,
205209
final String key, final Consumer<String> consume) {
206-
commandDto.getUserData().getEntry()
207-
.stream()
208-
.filter(x -> Objects.equals(x.getKey(), key))
209-
.map(MapDto.Entry::getValue)
210-
.filter(Objects::nonNull)
211-
.filter(x -> x.length() > 0)
212-
.findFirst()
213-
.ifPresent(consume);
210+
Optional.ofNullable(commandDto.getUserData())
211+
.ifPresent(userdata->{
212+
userdata.getEntry()
213+
.stream()
214+
.filter(x -> Objects.equals(x.getKey(), key))
215+
.map(MapDto.Entry::getValue)
216+
.filter(Objects::nonNull)
217+
.filter(x -> x.length() > 0)
218+
.findFirst()
219+
.ifPresent(consume);
220+
});
214221
}
215222

216223
static final DateTimeFormatter DATETIME_FORMATTER =

extensions/core/commandlog/applib/src/main/java/org/apache/causeway/extensions/commandlog/applib/dom/CommandLogEntryRepository.java

Lines changed: 25 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -51,36 +51,41 @@ public NotFoundException(final UUID interactionId) {
5151
}
5252

5353
CommandLogEntry createEntryAndPersist(
54-
final Command command, final UUID parentInteractionIdIfAny, final ExecuteIn executeIn);
54+
Command command, UUID parentInteractionIdIfAny, final ExecuteIn executeIn);
5555

56-
Optional<CommandLogEntry> findByInteractionId(final UUID interactionId);
56+
CommandLogEntry createAsPending(CommandDto commandToReplay, int targetIndex);
5757

58-
List<CommandLogEntry> findByParent(final CommandLogEntry parent);
58+
Optional<CommandLogEntry> findByInteractionId(UUID interactionId);
5959

60-
List<CommandLogEntry> findByParentInteractionId(final UUID parentInteractionId);
60+
List<CommandLogEntry> findByParent(CommandLogEntry parent);
61+
62+
List<CommandLogEntry> findByParentInteractionId(UUID parentInteractionId);
6163

6264
List<CommandLogEntry> findByFromAndTo(
63-
final @Nullable LocalDate from,
64-
final @Nullable LocalDate to);
65+
@Nullable LocalDate from,
66+
@Nullable LocalDate to);
6567

68+
/**
69+
* Still running, not completed yet.
70+
*/
6671
List<CommandLogEntry> findCurrent();
6772

6873
List<CommandLogEntry> findCompleted();
6974

7075
List<CommandLogEntry> findByTargetAndFromAndTo(
71-
final Bookmark target,
72-
final @Nullable LocalDate from,
73-
final @Nullable LocalDate to);
76+
Bookmark target,
77+
@Nullable LocalDate from,
78+
@Nullable LocalDate to);
7479

7580
List<CommandLogEntry> findMostRecent();
7681

77-
List<CommandLogEntry> findMostRecent(final int limit);
82+
List<CommandLogEntry> findMostRecent(int limit);
7883

79-
List<CommandLogEntry> findRecentByUsername(final String username);
84+
List<CommandLogEntry> findRecentByUsername(String username);
8085

81-
List<CommandLogEntry> findRecentByTarget(final Bookmark target);
86+
List<CommandLogEntry> findRecentByTarget(Bookmark target);
8287

83-
List<CommandLogEntry> findRecentByTargetOrResult(final Bookmark targetOrResult);
88+
List<CommandLogEntry> findRecentByTargetOrResult(Bookmark targetOrResult);
8489

8590
/**
8691
* Intended to support the replay of commands on a secondary instance of
@@ -112,7 +117,7 @@ List<CommandLogEntry> findByTargetAndFromAndTo(
112117
* @param batchSize - to restrict the number returned (so that replay
113118
* commands can be batched).
114119
*/
115-
List<CommandLogEntry> findSince(final UUID interactionId, final Integer batchSize);
120+
List<CommandLogEntry> findSince(UUID interactionId, Integer batchSize);
116121

117122
/**
118123
* Returns any persisted commands that have not yet started.
@@ -126,7 +131,7 @@ List<CommandLogEntry> findByTargetAndFromAndTo(
126131
*/
127132
List<CommandLogEntry> findBackgroundAndNotYetStarted();
128133

129-
List<CommandLogEntry> findRecentBackgroundByTarget(final Bookmark target);
134+
List<CommandLogEntry> findRecentBackgroundByTarget(Bookmark target);
130135

131136
/**
132137
* The most recent replayed command previously replicated from primary to
@@ -154,19 +159,19 @@ List<CommandLogEntry> findByTargetAndFromAndTo(
154159

155160
List<CommandLogEntry> findNotYetReplayed();
156161

157-
CommandLogEntry saveForReplay(final CommandDto dto);
162+
CommandLogEntry saveForReplay(CommandDto dto);
158163

159-
List<CommandLogEntry> saveForReplay(final CommandsDto commandsDto);
164+
List<CommandLogEntry> saveForReplay(CommandsDto commandsDto);
160165

161-
void persist(final CommandLogEntry commandLogEntry);
166+
void persist(CommandLogEntry commandLogEntry);
162167

163168
void truncateLog();
164169

165170
// --
166171

167172
List<CommandLogEntry> findCommandsOnPrimaryElseFail(
168-
final @Nullable UUID interactionId,
169-
final @Nullable Integer batchSize) throws NotFoundException;
173+
@Nullable UUID interactionId,
174+
@Nullable Integer batchSize) throws NotFoundException;
170175

171176
/**
172177
* intended for testing purposes only

extensions/core/commandlog/applib/src/main/java/org/apache/causeway/extensions/commandlog/applib/dom/CommandLogEntryRepositoryAbstract.java

Lines changed: 16 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,16 @@ public C createEntryAndPersist(
8080
return c;
8181
}
8282

83+
@Override
84+
public C createAsPending(final CommandDto commandToReplay, final int targetIndex) {
85+
C c = factoryService.detachedEntity(commandLogEntryClass);
86+
c.init(commandToReplay, ReplayState.PENDING, targetIndex);
87+
c.setParentInteractionId(null); // n/a for replay
88+
c.setExecuteIn(null); // to be specified later depending on user action
89+
persist(c);
90+
return c;
91+
}
92+
8393
@Override
8494
public Optional<CommandLogEntry> findByInteractionId(final UUID interactionId) {
8595
return _Casts.uncheckedCast(
@@ -257,13 +267,11 @@ public List<CommandLogEntry> findRecentByTargetOrResult(final Bookmark targetOrR
257267
*/
258268
@Override
259269
public List<CommandLogEntry> findSince(final UUID interactionId, final Integer batchSize) {
260-
if(interactionId == null) {
270+
if(interactionId == null)
261271
return findFirst();
262-
}
263272
final C from = findByInteractionIdElseNull(interactionId);
264-
if(from == null) {
273+
if(from == null)
265274
return Collections.emptyList();
266-
}
267275
return findSince(from.getTimestamp(), batchSize);
268276
}
269277

@@ -355,11 +363,10 @@ public C saveForReplay(final CommandDto dto) {
355363

356364
if(dto.getMember().getInteractionType() == InteractionType.ACTION_INVOCATION) {
357365
final MapDto userData = dto.getUserData();
358-
if (userData == null ) {
366+
if (userData == null )
359367
throw new IllegalStateException(String.format(
360368
"Can only persist action DTOs with additional userData; got: \n%s",
361369
CommandDtoUtils.dtoMapper().toString(dto)));
362-
}
363370
}
364371

365372
final C commandJdo = factoryService.detachedEntity(commandLogEntryClass);
@@ -408,9 +415,8 @@ public List<CommandLogEntry> findCommandsOnPrimaryElseFail(
408415
final @Nullable Integer batchSize) throws NotFoundException {
409416

410417
final List<CommandLogEntry> commands = findSince(interactionId, batchSize);
411-
if(commands == null) {
418+
if(commands == null)
412419
throw new NotFoundException(interactionId);
413-
}
414420
return commands;
415421
}
416422

@@ -460,9 +466,8 @@ private static Timestamp toTimestampStartOfDayWithOffset(
460466
*/
461467
@Override
462468
public List<CommandLogEntry> findAll() {
463-
if (causewaySystemEnvironment.deploymentType().isProduction()) {
469+
if (causewaySystemEnvironment.deploymentType().isProduction())
464470
throw new IllegalStateException("Cannot call 'findAll' in production systems");
465-
}
466471
return _Casts.uncheckedCast(repositoryService().allInstances(commandLogEntryClass));
467472
}
468473

@@ -471,9 +476,8 @@ public List<CommandLogEntry> findAll() {
471476
*/
472477
@Override
473478
public void removeAll() {
474-
if (causewaySystemEnvironment.deploymentType().isProduction()) {
479+
if (causewaySystemEnvironment.deploymentType().isProduction())
475480
throw new IllegalStateException("Cannot call 'removeAll' in production systems");
476-
}
477481
repositoryService().removeAll(commandLogEntryClass);
478482
}
479483

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one
3+
* or more contributor license agreements. See the NOTICE file
4+
* distributed with this work for additional information
5+
* regarding copyright ownership. The ASF licenses this file
6+
* to you under the Apache License, Version 2.0 (the
7+
* "License"); you may not use this file except in compliance
8+
* with the License. You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing,
13+
* software distributed under the License is distributed on an
14+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
* KIND, either express or implied. See the License for the
16+
* specific language governing permissions and limitations
17+
* under the License.
18+
*/
19+
package org.apache.causeway.extensions.commandlog.applib.dom.replay;
20+
21+
import java.util.List;
22+
23+
import jakarta.inject.Named;
24+
25+
import org.apache.causeway.applib.ViewModel;
26+
import org.apache.causeway.applib.annotation.Action;
27+
import org.apache.causeway.applib.annotation.ActionLayout;
28+
import org.apache.causeway.applib.annotation.DomainObjectLayout;
29+
import org.apache.causeway.applib.annotation.ObjectSupport;
30+
import org.apache.causeway.applib.annotation.Parameter;
31+
import org.apache.causeway.applib.util.schema.CommandDtoUtils;
32+
import org.apache.causeway.applib.value.Blob;
33+
import org.apache.causeway.applib.value.NamedWithMimeType.CommonMimeType;
34+
import org.apache.causeway.commons.io.JsonUtils;
35+
import org.apache.causeway.commons.io.YamlUtils;
36+
import org.apache.causeway.extensions.commandlog.applib.CausewayModuleExtCommandLogApplib;
37+
import org.apache.causeway.extensions.commandlog.applib.dom.CommandLogEntryRepository;
38+
import org.apache.causeway.schema.cmd.v2.CommandDto;
39+
40+
@DomainObjectLayout(cssClassFa = "solid circle-play")
41+
@Named(CommandReplayManager.LOGICAL_TYPE_NAME)
42+
public class CommandReplayManager implements ViewModel {
43+
44+
public static final String LOGICAL_TYPE_NAME = CausewayModuleExtCommandLogApplib.NAMESPACE + ".CommandReplayManager";
45+
46+
private final CommandLogEntryRepository commandLogEntryRepository;
47+
48+
public CommandReplayManager(final String memento, final CommandLogEntryRepository commandLogEntryRepository) {
49+
this.commandLogEntryRepository = commandLogEntryRepository;
50+
}
51+
52+
@ObjectSupport public String title() {
53+
return "Command Replay Manager";
54+
}
55+
56+
@Action
57+
@ActionLayout(describedAs = "Imports commands from a zipped yaml, then persists them with replayState=PENDING.")
58+
public String importCommands(
59+
@Parameter(fileAccept = ".zip")
60+
final Blob zippedCommandsYaml) {
61+
62+
var yamlDs = zippedCommandsYaml.unZip(CommonMimeType.YAML).asDataSource();
63+
64+
final List<CommandDto> commandDtos = CommandDtoUtils.fromYaml(yamlDs);
65+
commandDtos.forEach(commandDto->commandLogEntryRepository.createAsPending(commandDto, 0));
66+
67+
var yaml = YamlUtils.toStringUtf8(commandDtos,
68+
JsonUtils::onlyIncludeNonNull);
69+
70+
return yaml;
71+
}
72+
73+
74+
@Override
75+
public String viewModelMemento() {
76+
// TODO Auto-generated method stub
77+
return null;
78+
}
79+
80+
}

0 commit comments

Comments
 (0)