GenericWorkflowHistoryProjection.java
package org.hammer.audio.infrastructure.workflow.store;
import io.github.carstenartur.jgit.storage.hibernate.search.entity.GitCommitIndex;
import io.github.carstenartur.jgit.storage.hibernate.search.service.CommitHistoryQuery;
import io.github.carstenartur.jgit.storage.hibernate.search.service.CommitIndexer;
import io.github.carstenartur.jgit.storage.hibernate.search.service.GitHistorySearchService;
import java.io.IOException;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import java.util.NoSuchElementException;
import java.util.Objects;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.eclipse.jgit.lib.Constants;
import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.lib.Repository;
import org.hammer.audio.workflow.history.WorkflowHistoryTextQuery;
import org.hammer.audio.workflow.history.WorkflowHistoryTextResult;
import org.hammer.audio.workflow.store.CommitId;
import org.hibernate.SessionFactory;
/** Maintains the generic, rebuildable Git-history projection for one workflow repository. */
final class GenericWorkflowHistoryProjection {
private static final Logger LOGGER =
Logger.getLogger(GenericWorkflowHistoryProjection.class.getName());
private final Repository repository;
private final String repositoryName;
private final CommitIndexer commitIndexer;
private final GitHistorySearchService searchService;
GenericWorkflowHistoryProjection(
Repository repository, SessionFactory sessionFactory, String repositoryName) {
this.repository = Objects.requireNonNull(repository, "repository");
this.repositoryName = requireNotBlank(repositoryName, "repositoryName");
SessionFactory requiredSessionFactory =
Objects.requireNonNull(sessionFactory, "sessionFactory");
this.commitIndexer = new CommitIndexer(requiredSessionFactory, this.repositoryName);
this.searchService = new GitHistorySearchService(requiredSessionFactory);
}
List<WorkflowHistoryTextResult> search(WorkflowHistoryTextQuery query) {
return search(query, null);
}
List<WorkflowHistoryTextResult> searchWithinCandidates(
WorkflowHistoryTextQuery query, Collection<CommitId> candidates) {
return search(query, Objects.requireNonNull(candidates, "candidates"));
}
private List<WorkflowHistoryTextResult> search(
WorkflowHistoryTextQuery query, Collection<CommitId> candidates) {
Objects.requireNonNull(query, "query");
CommitHistoryQuery.Builder sharedQuery =
CommitHistoryQuery.forRepository(repositoryName)
.matchingText(query.text())
.authoredBy(query.authorEmail())
.touchingPath(query.pathText())
.limit(query.limit());
if (candidates != null) {
sharedQuery.restrictedToObjectIds(candidates.stream().map(CommitId::value).toList());
}
if (query.from() != null) {
sharedQuery.from(query.from());
}
if (query.to() != null) {
sharedQuery.to(query.to());
}
return searchService.findChanges(sharedQuery.build()).stream()
.map(GenericWorkflowHistoryProjection::toResult)
.toList();
}
int rebuild(String branch, int limit) {
String requiredBranch = requireNotBlank(branch, "branch");
try {
ObjectId head = repository.resolve(requiredBranch);
if (head == null) {
head = repository.resolve(Constants.R_HEADS + requiredBranch);
}
if (head == null) {
throw new NoSuchElementException("Unknown workflow branch: " + requiredBranch);
}
return commitIndexer.indexCommitsFrom(repository, head, limit);
} catch (IOException failure) {
throw new IllegalStateException(
"Could not rebuild workflow history search for branch " + requiredBranch, failure);
}
}
void indexBestEffort(CommitId commitId) {
try {
commitIndexer.indexCommit(repository, ObjectId.fromString(commitId.value()));
} catch (IOException | RuntimeException failure) {
LOGGER.log(
Level.WARNING,
"Workflow commit "
+ commitId.value()
+ " remains authoritative but its generic search projection is stale",
failure);
}
}
private static WorkflowHistoryTextResult toResult(GitCommitIndex hit) {
List<String> changedPaths =
hit.getChangedPaths() == null || hit.getChangedPaths().isBlank()
? List.of()
: Arrays.stream(hit.getChangedPaths().split("\\R"))
.filter(value -> !value.isBlank())
.toList();
return new WorkflowHistoryTextResult(
new CommitId(hit.getObjectId()),
hit.getShortMessage(),
hit.getAuthorName(),
hit.getAuthorEmail(),
hit.getCommitTime(),
changedPaths);
}
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;
}
}