HibernateJGitVersionedWorkflowStore.java

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

import io.github.carstenartur.jgit.storage.hibernate.HibernateGitStorage;
import io.github.carstenartur.jgit.storage.hibernate.HibernateRepositoryFactory;
import io.github.carstenartur.jgit.storage.hibernate.RepositoryName;
import java.util.List;
import java.util.Objects;
import org.hammer.audio.workflow.history.IndexedWorkflowCombinedHistorySearch;
import org.hammer.audio.workflow.history.IndexedWorkflowHistorySearch;
import org.hammer.audio.workflow.history.IndexedWorkflowSemanticHistorySearch;
import org.hammer.audio.workflow.history.WorkflowCombinedHistoryQuery;
import org.hammer.audio.workflow.history.WorkflowCombinedHistoryResult;
import org.hammer.audio.workflow.history.WorkflowHistoryTextQuery;
import org.hammer.audio.workflow.history.WorkflowHistoryTextResult;
import org.hammer.audio.workflow.history.WorkflowSemanticHistoryQuery;
import org.hammer.audio.workflow.history.WorkflowSemanticHistoryResult;
import org.hammer.audio.workflow.store.CommitId;
import org.hammer.audio.workflow.store.CommitInfo;
import org.hammer.audio.workflow.store.CommitMetadata;
import org.hammer.audio.workflow.store.RefUpdateResult;
import org.hammer.audio.workflow.store.VersionedWorkflowStore;
import org.hammer.audio.workflow.store.WorkflowSnapshot;
import org.hibernate.SessionFactory;

/** Production workflow store and indexed search adapter over one shared Hibernate context. */
public final class HibernateJGitVersionedWorkflowStore
    implements VersionedWorkflowStore,
        IndexedWorkflowHistorySearch,
        IndexedWorkflowSemanticHistorySearch,
        IndexedWorkflowCombinedHistorySearch,
        AutoCloseable {

  private final HibernateGitStorage storage;
  private final JGitRepositoryVersionedWorkflowStore delegate;
  private final GenericWorkflowHistoryProjection genericHistoryProjection;
  private final WorkflowSemanticHistoryProjection semanticHistoryProjection;
  private final CombinedWorkflowHistorySearch combinedHistorySearch;

  /** Opens a searchable logical repository using the application-managed SessionFactory. */
  public HibernateJGitVersionedWorkflowStore(SessionFactory sessionFactory, String repositoryName) {
    this(
        HibernateWorkflowGitStorage.open(sessionFactory, repositoryName),
        sessionFactory,
        repositoryName);
  }

  /** Opens a storage-only adapter through a supplied shared repository factory. */
  public HibernateJGitVersionedWorkflowStore(
      HibernateRepositoryFactory repositoryFactory, RepositoryName repositoryName) {
    this(
        Objects.requireNonNull(repositoryFactory, "repositoryFactory")
            .open(Objects.requireNonNull(repositoryName, "repositoryName")));
  }

  HibernateJGitVersionedWorkflowStore(HibernateGitStorage storage) {
    this.storage = Objects.requireNonNull(storage, "storage");
    this.delegate = new JGitRepositoryVersionedWorkflowStore(storage.repository());
    this.genericHistoryProjection = null;
    this.semanticHistoryProjection = null;
    this.combinedHistorySearch = null;
  }

  private HibernateJGitVersionedWorkflowStore(
      HibernateGitStorage storage, SessionFactory sessionFactory, String repositoryName) {
    this.storage = Objects.requireNonNull(storage, "storage");
    this.delegate = new JGitRepositoryVersionedWorkflowStore(storage.repository());
    String requiredRepositoryName = requireNotBlank(repositoryName, "repositoryName");
    SessionFactory requiredSessionFactory =
        Objects.requireNonNull(sessionFactory, "sessionFactory");
    this.genericHistoryProjection =
        new GenericWorkflowHistoryProjection(
            storage.repository(), requiredSessionFactory, requiredRepositoryName);
    this.semanticHistoryProjection =
        new WorkflowSemanticHistoryProjection(
            delegate, requiredSessionFactory, requiredRepositoryName);
    this.combinedHistorySearch =
        new CombinedWorkflowHistorySearch(genericHistoryProjection, semanticHistoryProjection);
  }

  @Override
  public CommitId commit(String branch, WorkflowSnapshot snapshot, CommitMetadata metadata) {
    CommitId commitId = delegate.commit(branch, snapshot, metadata);
    indexBestEffort(branch, commitId, snapshot);
    return commitId;
  }

  @Override
  public CommitId commitIfHead(
      String branch, CommitId expectedHead, WorkflowSnapshot snapshot, CommitMetadata metadata) {
    CommitId commitId = delegate.commitIfHead(branch, expectedHead, snapshot, metadata);
    indexBestEffort(branch, commitId, snapshot);
    return commitId;
  }

  @Override
  public WorkflowSnapshot loadAtCommit(CommitId commitId) {
    return delegate.loadAtCommit(commitId);
  }

  @Override
  public WorkflowSnapshot loadHead(String branch) {
    return delegate.loadHead(branch);
  }

  @Override
  public RefUpdateResult updateRef(String refName, CommitId expectedOldCommit, CommitId newCommit) {
    RefUpdateResult result = delegate.updateRef(refName, expectedOldCommit, newCommit);
    if (result == RefUpdateResult.SUCCESS && semanticHistoryProjection != null) {
      semanticHistoryProjection.rebuildBestEffort(refName);
    }
    return result;
  }

  @Override
  public List<CommitInfo> history(String refName, int limit) {
    return delegate.history(refName, limit);
  }

  @Override
  public List<WorkflowHistoryTextResult> search(WorkflowHistoryTextQuery query) {
    requireSearchEnabled();
    return genericHistoryProjection.search(query);
  }

  @Override
  public List<WorkflowSemanticHistoryResult> searchSemantic(WorkflowSemanticHistoryQuery query) {
    requireSemanticSearchEnabled();
    return semanticHistoryProjection.search(query);
  }

  @Override
  public List<WorkflowCombinedHistoryResult> searchCombined(WorkflowCombinedHistoryQuery query) {
    requireCombinedSearchEnabled();
    return combinedHistorySearch.search(query);
  }

  @Override
  public int rebuild(String branch, int limit) {
    requireSearchEnabled();
    requireSemanticSearchEnabled();
    if (limit == 0) {
      return 0;
    }
    int newlyIndexed = genericHistoryProjection.rebuild(branch, limit);
    semanticHistoryProjection.rebuild(branch, limit);
    return newlyIndexed;
  }

  @Override
  public void close() {
    storage.close();
  }

  private void indexBestEffort(
      String branch, CommitId commitId, WorkflowSnapshot authoritativeSnapshot) {
    if (genericHistoryProjection != null) {
      genericHistoryProjection.indexBestEffort(commitId);
    }
    if (semanticHistoryProjection != null) {
      semanticHistoryProjection.indexBestEffort(branch, commitId, authoritativeSnapshot);
    }
  }

  private void requireSearchEnabled() {
    if (genericHistoryProjection == null) {
      throw new IllegalStateException(
          "Indexed workflow history search requires the application-managed SessionFactory");
    }
  }

  private void requireSemanticSearchEnabled() {
    if (semanticHistoryProjection == null) {
      throw new IllegalStateException(
          "Semantic workflow history search requires the application-managed SessionFactory");
    }
  }

  private void requireCombinedSearchEnabled() {
    if (combinedHistorySearch == null) {
      throw new IllegalStateException(
          "Combined workflow history search requires the application-managed SessionFactory");
    }
  }

  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;
  }
}