WorkflowHistoryCommandService.java
package org.hammer.audio.workflow.history;
import java.util.List;
import java.util.Objects;
import org.hammer.audio.workflow.Workflow;
import org.hammer.audio.workflow.dsl.WorkflowDslParser;
import org.hammer.audio.workflow.store.CommitId;
import org.hammer.audio.workflow.store.CommitInfo;
import org.hammer.audio.workflow.store.RefUpdateResult;
import org.hammer.audio.workflow.store.StaleWorkflowHeadException;
import org.hammer.audio.workflow.store.VersionedWorkflowStore;
import org.hammer.audio.workflow.store.WorkflowSnapshot;
/** Application service for explicit branch-scoped workflow comparison, branching and restore. */
public final class WorkflowHistoryCommandService {
private final VersionedWorkflowStore store;
private final WorkflowHistoryAccessPolicy accessPolicy;
private final WorkflowDslParser parser;
/**
* Creates a command service over one repository-scoped workflow store.
*
* @param store authoritative workflow history store
* @param accessPolicy application access policy for history commands
*/
public WorkflowHistoryCommandService(
VersionedWorkflowStore store, WorkflowHistoryAccessPolicy accessPolicy) {
this.store = Objects.requireNonNull(store, "store");
this.accessPolicy = Objects.requireNonNull(accessPolicy, "accessPolicy");
this.parser = new WorkflowDslParser();
}
/**
* Compares two exact commits that are reachable from the requested branch.
*
* @param branch branch used as reachability boundary
* @param beforeCommit exact before commit
* @param afterCommit exact after commit
* @return both authoritative workflows and their semantic diff
*/
public WorkflowHistoryComparison compare(
String branch, CommitId beforeCommit, CommitId afterCommit) {
String requiredBranch = requireNotBlank(branch, "branch");
Objects.requireNonNull(beforeCommit, "beforeCommit");
Objects.requireNonNull(afterCommit, "afterCommit");
List<CommitInfo> reachable = store.history(requiredBranch, Integer.MAX_VALUE);
requireReachable(reachable, beforeCommit, requiredBranch);
requireReachable(reachable, afterCommit, requiredBranch);
Workflow beforeWorkflow = loadWorkflow(beforeCommit);
Workflow afterWorkflow = loadWorkflow(afterCommit);
if (!beforeWorkflow.id().equals(afterWorkflow.id())) {
throw new IllegalArgumentException(
"Cannot compare commits with different workflow ids: "
+ beforeWorkflow.id()
+ " and "
+ afterWorkflow.id());
}
accessPolicy.assertCompareAllowed(requiredBranch, beforeWorkflow.id());
return new WorkflowHistoryComparison(
beforeCommit,
afterCommit,
beforeWorkflow,
afterWorkflow,
WorkflowDiff.compute(beforeWorkflow, afterWorkflow));
}
/**
* Creates a new branch from one exact commit reachable from a source branch.
*
* @param command source reachability boundary, new branch and exact initial HEAD
* @return newly created branch evidence
*/
public WorkflowBranchCreationResult createBranch(CreateWorkflowBranchCommand command) {
Objects.requireNonNull(command, "command");
List<CommitInfo> reachable = store.history(command.sourceBranch(), Integer.MAX_VALUE);
if (reachable.isEmpty()) {
throw new IllegalArgumentException(
"Unknown or empty workflow branch: " + command.sourceBranch());
}
requireReachable(reachable, command.fromCommit(), command.sourceBranch());
Workflow workflow = loadWorkflow(command.fromCommit());
accessPolicy.assertRestoreAllowed(command.newBranch(), workflow.id());
RefUpdateResult update = store.updateRef(command.newBranch(), null, command.fromCommit());
if (update != RefUpdateResult.SUCCESS) {
throw new IllegalArgumentException(
"Workflow branch already exists or cannot be created: " + command.newBranch());
}
return new WorkflowBranchCreationResult(
command.sourceBranch(), command.newBranch(), command.fromCommit(), workflow.id());
}
/**
* Restores a reachable historical snapshot as a new commit on the current branch HEAD.
*
* @param command explicit target, expected HEAD and audit metadata
* @return historical source, previous HEAD and new restore commit evidence
*/
public WorkflowRestoreResult restore(RestoreWorkflowVersionCommand command) {
Objects.requireNonNull(command, "command");
List<CommitInfo> reachable = store.history(command.branch(), Integer.MAX_VALUE);
if (reachable.isEmpty()) {
throw new IllegalArgumentException("Unknown or empty workflow branch: " + command.branch());
}
requireReachable(reachable, command.targetCommit(), command.branch());
CommitId actualHead = reachable.getFirst().commitId();
if (!command.expectedHead().equals(actualHead)) {
throw new StaleWorkflowHeadException(command.branch(), command.expectedHead(), actualHead);
}
WorkflowSnapshot targetSnapshot = store.loadAtCommit(command.targetCommit());
Workflow targetWorkflow = parseAndValidate(targetSnapshot);
accessPolicy.assertRestoreAllowed(command.branch(), targetWorkflow.id());
CommitId restoredCommit =
store.commitIfHead(
command.branch(), command.expectedHead(), targetSnapshot, command.metadata());
return new WorkflowRestoreResult(
command.branch(), command.targetCommit(), actualHead, restoredCommit);
}
private Workflow loadWorkflow(CommitId commitId) {
return parseAndValidate(store.loadAtCommit(commitId));
}
private Workflow parseAndValidate(WorkflowSnapshot snapshot) {
Workflow workflow = parser.parse(snapshot.dslText());
if (!snapshot.workflowId().equals(workflow.id())) {
throw new IllegalArgumentException(
"snapshot workflowId '"
+ snapshot.workflowId()
+ "' does not match DSL workflow id '"
+ workflow.id()
+ "'");
}
return workflow;
}
private static void requireReachable(
List<CommitInfo> reachable, CommitId commitId, String branch) {
boolean found = reachable.stream().anyMatch(info -> info.commitId().equals(commitId));
if (!found) {
throw new IllegalArgumentException(
"Commit " + commitId.value() + " is not reachable from branch " + branch);
}
}
private static String requireNotBlank(String value, String name) {
Objects.requireNonNull(value, name);
String normalized = value.trim();
if (normalized.isEmpty()) {
throw new IllegalArgumentException(name + " must not be blank");
}
return normalized;
}
}