StoredWorkflowOperation.java

package org.hammer.audio.workflow.collaboration.store;

import java.time.Instant;
import java.util.Objects;
import java.util.Optional;
import org.hammer.audio.workflow.WorkflowOperation;

/**
 * Immutable accepted operation read back from durable collaboration history.
 *
 * @param sessionId owning collaboration-session identifier
 * @param operationId stable idempotency identifier for the semantic operation
 * @param actorId actor that authored the operation
 * @param operationType stable semantic operation type
 * @param occurredAt operation occurrence timestamp
 * @param sequence durable session event sequence
 * @param revision semantic revision produced by the operation
 * @param payload deterministic serialized identity payload
 * @param bodyVersion version of the reconstructible operation body, or zero for a legacy row
 * @param operationBody complete operation body, or {@code null} for a legacy row
 * @param command durable normal/undo/redo command relation
 */
public record StoredWorkflowOperation(
    String sessionId,
    String operationId,
    String actorId,
    String operationType,
    Instant occurredAt,
    long sequence,
    long revision,
    String payload,
    int bodyVersion,
    String operationBody,
    WorkflowOperationCommandMetadata command) {

  public StoredWorkflowOperation {
    sessionId = requireNotBlank(sessionId, "sessionId");
    operationId = requireNotBlank(operationId, "operationId");
    actorId = requireNotBlank(actorId, "actorId");
    operationType = requireNotBlank(operationType, "operationType");
    Objects.requireNonNull(occurredAt, "occurredAt");
    requirePositive(sequence, "sequence");
    requirePositive(revision, "revision");
    payload = requireNotBlank(payload, "payload");
    if (bodyVersion < 0) {
      throw new IllegalArgumentException("bodyVersion must be >= 0");
    }
    if (bodyVersion == 0) {
      if (operationBody != null && !operationBody.isBlank()) {
        throw new IllegalArgumentException("legacy operation body must be absent");
      }
      operationBody = null;
    } else {
      operationBody = requireNotBlank(operationBody, "operationBody");
    }
    command = Objects.requireNonNull(command, "command");
  }

  /** Returns whether this operation can be reconstructed for semantic undo/redo. */
  public boolean hasOperationBody() {
    return bodyVersion > 0;
  }

  /** Reconstructs the operation when a versioned body is available. */
  public Optional<WorkflowOperation> operation() {
    return hasOperationBody()
        ? Optional.of(WorkflowOperationBodyCodec.decode(bodyVersion, operationBody))
        : Optional.empty();
  }

  private static String requireNotBlank(String value, String name) {
    Objects.requireNonNull(value, name);
    if (value.isBlank()) {
      throw new IllegalArgumentException(name + " must not be blank");
    }
    return value;
  }

  private static void requirePositive(long value, String name) {
    if (value <= 0) {
      throw new IllegalArgumentException(name + " must be > 0");
    }
  }
}