package com.xfestudio.xfeservermanager.core.rollback;
import java.time.Instant;
import java.util.List;
import java.util.Objects;
import java.util.UUID;
/** Persistable execution checkpoint. Cursor points at the next plan item. */
public record RollbackOperation(
UUID operationId,
RollbackPlan plan,
RollbackStatus status,
int cursor,
List<UUID> appliedEventIds,
List<RollbackFailure> failures,
UUID inFlightEventId,
Instant updatedAt
) {
public RollbackOperation(UUID operationId, RollbackPlan plan, RollbackStatus status, int cursor,
List<UUID> appliedEventIds, List<RollbackFailure> failures, Instant updatedAt) {
this(operationId, plan, status, cursor, appliedEventIds, failures, null, updatedAt);
}
public RollbackOperation {
Objects.requireNonNull(operationId, "operationId");
Objects.requireNonNull(plan, "plan");
Objects.requireNonNull(status, "status");
if (cursor < 0 || cursor > plan.items().size()) throw new IllegalArgumentException("cursor out of range");
appliedEventIds = List.copyOf(appliedEventIds);
if (new java.util.HashSet<>(appliedEventIds).size() != appliedEventIds.size()) {
throw new IllegalArgumentException("applied event ids must be unique");
}
var planEventIds = plan.items().stream().map(item -> item.entry().change().eventId())
.collect(java.util.stream.Collectors.toSet());
if (!planEventIds.containsAll(appliedEventIds)) {
throw new IllegalArgumentException("applied event ids must belong to the plan");
}
failures = List.copyOf(failures);
if (status == RollbackStatus.PREVIEWED && (cursor != 0 || !appliedEventIds.isEmpty())) {
throw new IllegalArgumentException("previewed operations cannot contain execution progress");
}
if (status == RollbackStatus.COMPLETED && cursor != plan.items().size()) {
throw new IllegalArgumentException("completed operations must reach the end of the plan");
}
if (inFlightEventId != null) {
if (status != RollbackStatus.RUNNING && status != RollbackStatus.PAUSED) {
throw new IllegalArgumentException("only active operations can have an in-flight event");
}
if (cursor >= plan.items().size()
|| !plan.items().get(cursor).entry().change().eventId().equals(inFlightEventId)) {
throw new IllegalArgumentException("in-flight event must identify the item at the cursor");
}
if (appliedEventIds.contains(inFlightEventId)) {
throw new IllegalArgumentException("in-flight event cannot already be applied");
}
}
Objects.requireNonNull(updatedAt, "updatedAt");
}
public double progress() {
return plan.items().isEmpty() ? 1.0 : (double) cursor / plan.items().size();
}
}
package com.xfestudio.xfeservermanager.core.rollback;
import java.time.Instant;
import java.util.List;
import java.util.Objects;
import java.util.UUID;
/** Persistable execution checkpoint. Cursor points at the next plan item. */
public record RollbackOperation(
UUID operationId,
RollbackPlan plan,
RollbackStatus status,
int cursor,
List<UUID> appliedEventIds,
List<RollbackFailure> failures,
UUID inFlightEventId,
Instant updatedAt
) {
public RollbackOperation(UUID operationId, RollbackPlan plan, RollbackStatus status, int cursor,
List<UUID> appliedEventIds, List<RollbackFailure> failures, Instant updatedAt) {
this(operationId, plan, status, cursor, appliedEventIds, failures, null, updatedAt);
}
public RollbackOperation {
Objects.requireNonNull(operationId, "operationId");
Objects.requireNonNull(plan, "plan");
Objects.requireNonNull(status, "status");
if (cursor < 0 || cursor > plan.items().size()) throw new IllegalArgumentException("cursor out of range");
appliedEventIds = List.copyOf(appliedEventIds);
if (new java.util.HashSet<>(appliedEventIds).size() != appliedEventIds.size()) {
throw new IllegalArgumentException("applied event ids must be unique");
}
var planEventIds = plan.items().stream().map(item -> item.entry().change().eventId())
.collect(java.util.stream.Collectors.toSet());
if (!planEventIds.containsAll(appliedEventIds)) {
throw new IllegalArgumentException("applied event ids must belong to the plan");
}
failures = List.copyOf(failures);
if (status == RollbackStatus.PREVIEWED && (cursor != 0 || !appliedEventIds.isEmpty())) {
throw new IllegalArgumentException("previewed operations cannot contain execution progress");
}
if (status == RollbackStatus.COMPLETED && cursor != plan.items().size()) {
throw new IllegalArgumentException("completed operations must reach the end of the plan");
}
if (inFlightEventId != null) {
if (status != RollbackStatus.RUNNING && status != RollbackStatus.PAUSED) {
throw new IllegalArgumentException("only active operations can have an in-flight event");
}
if (cursor >= plan.items().size()
|| !plan.items().get(cursor).entry().change().eventId().equals(inFlightEventId)) {
throw new IllegalArgumentException("in-flight event must identify the item at the cursor");
}
if (appliedEventIds.contains(inFlightEventId)) {
throw new IllegalArgumentException("in-flight event cannot already be applied");
}
}
Objects.requireNonNull(updatedAt, "updatedAt");
}
public double progress() {
return plan.items().isEmpty() ? 1.0 : (double) cursor / plan.items().size();
}
}