WorkflowHistoryDescriptor.java

package org.hammer.audio.workflow.collaboration;

import java.time.Instant;
import java.util.List;
import java.util.Objects;

/**
 * Immutable read-only descriptor for one accepted semantic workflow operation.
 *
 * @param operationId stable operation identifier
 * @param operationType semantic operation type
 * @param actorId actor that authored the operation
 * @param occurredAt operation occurrence timestamp
 * @param revision semantic revision produced by the operation
 * @param sequence durable event sequence assigned to the operation
 * @param commandKind normal, undo or redo command category
 * @param commandId stable idempotency identity of the command
 * @param targetOperationId target operation for undo or redo, otherwise {@code null}
 * @param affectedObjectIds semantic objects affected by the operation
 * @param reconstructible whether a complete operation body is available
 * @param activeUndoTarget whether the operation is currently an active undo target
 * @param activeRedoTarget whether the operation is currently an active redo target
 */
public record WorkflowHistoryDescriptor(
    String operationId,
    String operationType,
    String actorId,
    Instant occurredAt,
    long revision,
    long sequence,
    CommandKind commandKind,
    String commandId,
    String targetOperationId,
    List<String> affectedObjectIds,
    boolean reconstructible,
    boolean activeUndoTarget,
    boolean activeRedoTarget) {

  /** Stable public command categories independent of persistence implementation types. */
  public enum CommandKind {
    NORMAL,
    UNDO,
    REDO
  }

  public WorkflowHistoryDescriptor {
    operationId = requireNotBlank(operationId, "operationId");
    operationType = requireNotBlank(operationType, "operationType");
    actorId = requireNotBlank(actorId, "actorId");
    Objects.requireNonNull(occurredAt, "occurredAt");
    if (revision <= 0 || sequence <= 0) {
      throw new IllegalArgumentException("revision and sequence must be > 0");
    }
    Objects.requireNonNull(commandKind, "commandKind");
    commandId = requireNotBlank(commandId, "commandId");
    if (commandKind == CommandKind.NORMAL) {
      if (targetOperationId != null && !targetOperationId.isBlank()) {
        throw new IllegalArgumentException("normal history entry must not have a target");
      }
      targetOperationId = null;
    } else {
      targetOperationId = requireNotBlank(targetOperationId, "targetOperationId");
    }
    affectedObjectIds = List.copyOf(Objects.requireNonNull(affectedObjectIds, "affectedObjectIds"));
  }

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