WorkflowSemanticHistoryHttpAdapter.java
package org.hammer.audio.workflow.editor.http;
import java.util.List;
import java.util.Objects;
import org.hammer.audio.workflow.history.IndexedWorkflowSemanticHistorySearch;
import org.hammer.audio.workflow.history.WorkflowSemanticHistoryQuery;
import org.hammer.audio.workflow.history.WorkflowSemanticHistoryResult;
import org.hammer.audio.workflow.history.WorkflowSemanticProperty;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/** HTTP boundary for branch-aware semantic workflow-history queries. */
@RestController
@RequestMapping("/workflow/history/semantic")
@ConditionalOnBean(IndexedWorkflowSemanticHistorySearch.class)
public final class WorkflowSemanticHistoryHttpAdapter {
private static final int DEFAULT_LIMIT = 20;
private final IndexedWorkflowSemanticHistorySearch semanticSearch;
public WorkflowSemanticHistoryHttpAdapter(IndexedWorkflowSemanticHistorySearch semanticSearch) {
this.semanticSearch = Objects.requireNonNull(semanticSearch, "semanticSearch");
}
/** Searches exact workflow semantics derived from commits reachable on one branch. */
@GetMapping
public List<SemanticHitResponse> search(@ModelAttribute SemanticSearchRequest request) {
return semanticSearch.searchSemantic(request.toQuery()).stream()
.map(SemanticHitResponse::from)
.toList();
}
/**
* Bindable semantic-history query parameters.
*
* @param branch branch whose reachable commits are searched; defaults to {@code main}
* @param workflow exact workflow identifier
* @param node exact node identifier
* @param type exact node type
* @param label workflow-name or node-label full-text expression
* @param propertyKey exact metadata key
* @param propertyValue exact metadata value
* @param limit bounded result count; defaults to 20
*/
public record SemanticSearchRequest(
String branch,
String workflow,
String node,
String type,
String label,
String propertyKey,
String propertyValue,
String limit) {
public SemanticSearchRequest {
branch = branch == null || branch.isBlank() ? "main" : branch.trim();
limit = limit == null || limit.isBlank() ? Integer.toString(DEFAULT_LIMIT) : limit.trim();
}
WorkflowSemanticHistoryQuery toQuery() {
return new WorkflowSemanticHistoryQuery(
branch, workflow, node, type, label, propertyKey, propertyValue, Integer.parseInt(limit));
}
}
/**
* Domain-semantic evidence tied to one exact authoritative Git commit.
*
* @param commitId exact Git commit identity
* @param branch branch for which the commit is reachable
* @param workflowId stable workflow identifier
* @param workflowName human-readable workflow name
* @param nodeIds stable node identifiers contained in the workflow
* @param nodeTypes logical node types contained in the workflow
* @param nodeLabels human-readable node labels contained in the workflow
* @param properties exact workflow and node metadata entries
*/
public record SemanticHitResponse(
String commitId,
String branch,
String workflowId,
String workflowName,
List<String> nodeIds,
List<String> nodeTypes,
List<String> nodeLabels,
List<WorkflowSemanticProperty> properties) {
public SemanticHitResponse {
commitId = Objects.requireNonNull(commitId, "commitId");
branch = Objects.requireNonNull(branch, "branch");
workflowId = Objects.requireNonNull(workflowId, "workflowId");
workflowName = Objects.requireNonNull(workflowName, "workflowName");
nodeIds = List.copyOf(Objects.requireNonNull(nodeIds, "nodeIds"));
nodeTypes = List.copyOf(Objects.requireNonNull(nodeTypes, "nodeTypes"));
nodeLabels = List.copyOf(Objects.requireNonNull(nodeLabels, "nodeLabels"));
properties = List.copyOf(Objects.requireNonNull(properties, "properties"));
}
static SemanticHitResponse from(WorkflowSemanticHistoryResult result) {
return new SemanticHitResponse(
result.commitId().value(),
result.branch(),
result.workflowId(),
result.workflowName(),
result.nodeIds(),
result.nodeTypes(),
result.nodeLabels(),
result.properties());
}
}
}