WorkflowPersistenceConfiguration.java

package org.hammer.audio.app;

import io.github.carstenartur.jgit.storage.hibernate.config.HibernateSessionFactoryProvider;
import java.nio.file.Path;
import java.util.Collection;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Objects;
import java.util.Properties;
import java.util.Set;
import javax.sql.DataSource;
import org.hammer.audio.infrastructure.workflow.collaboration.schema.WorkflowSchemaMigrationResult;
import org.hammer.audio.infrastructure.workflow.store.FileSystemJGitVersionedWorkflowStore;
import org.hammer.audio.infrastructure.workflow.store.HibernateJGitVersionedWorkflowStore;
import org.hammer.audio.workflow.store.VersionedWorkflowStore;
import org.hibernate.SessionFactory;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.env.Environment;

/** Selects an explicit workflow persistence implementation for the workbench. */
@Configuration
public class WorkflowPersistenceConfiguration {

  private static final String PERSISTENCE_MODE_PROPERTY = "workbench.persistence.mode";

  /** Builds the shared Hibernate context over the Spring-managed DataSource. */
  @Bean(destroyMethod = "close")
  @ConditionalOnProperty(name = PERSISTENCE_MODE_PROPERTY, havingValue = "hibernate")
  public HibernateSessionFactoryProvider workflowHibernateSessionFactoryProvider(
      DataSource dataSource,
      Environment environment,
      List<WorkflowPersistenceEntityContributor> entityContributors,
      WorkflowSchemaMigrationResult migrationResult,
      @Value("${workbench.persistence.schema-action:validate}") String schemaAction) {
    requireExplicitDataSource(environment);
    migrationResult.requireValidateSchemaAction(schemaAction);
    Properties properties = new Properties();
    properties.put("hibernate.connection.datasource", dataSource);
    properties.setProperty("hibernate.hbm2ddl.auto", requireNotBlank(schemaAction, "schemaAction"));
    properties.setProperty("hibernate.show_sql", "false");
    properties.setProperty("hibernate.format_sql", "false");
    properties.setProperty("hibernate.search.backend.type", "lucene");
    properties.setProperty(
        "hibernate.search.backend.directory.type",
        environment.getProperty("workbench.history-search.directory-type", "local-heap"));
    String searchDirectoryRoot = environment.getProperty("workbench.history-search.directory-root");
    if (searchDirectoryRoot != null && !searchDirectoryRoot.isBlank()) {
      properties.setProperty("hibernate.search.backend.directory.root", searchDirectoryRoot.trim());
    }
    return new HibernateSessionFactoryProvider(
        properties, additionalAnnotatedClasses(entityContributors));
  }

  /** Exposes the application-managed SessionFactory for all persistent workflow adapters. */
  @Bean(destroyMethod = "")
  @ConditionalOnProperty(name = PERSISTENCE_MODE_PROPERTY, havingValue = "hibernate")
  public SessionFactory workflowPersistenceSessionFactory(
      HibernateSessionFactoryProvider provider) {
    return provider.getSessionFactory();
  }

  /** Opens the production database-backed logical JGit repository. */
  @Bean(destroyMethod = "close")
  @ConditionalOnProperty(name = PERSISTENCE_MODE_PROPERTY, havingValue = "hibernate")
  public HibernateJGitVersionedWorkflowStore hibernateVersionedWorkflowStore(
      @Qualifier("workflowPersistenceSessionFactory") SessionFactory sessionFactory,
      @Value("${workbench.persistence.repository-name:audio-analyzer-workflows}")
          String repositoryName) {
    return new HibernateJGitVersionedWorkflowStore(
        sessionFactory, requireNotBlank(repositoryName, "repositoryName"));
  }

  /** Opens the explicitly selected filesystem store for tests and local demonstrations only. */
  @Bean(destroyMethod = "close")
  @ConditionalOnProperty(name = PERSISTENCE_MODE_PROPERTY, havingValue = "filesystem")
  public VersionedWorkflowStore fileSystemVersionedWorkflowStore(
      @Value("${workbench.persistence.filesystem.path}") String repositoryPath) {
    return new FileSystemJGitVersionedWorkflowStore(
        Path.of(requireNotBlank(repositoryPath, "repositoryPath")));
  }

  private static List<Class<?>> additionalAnnotatedClasses(
      List<WorkflowPersistenceEntityContributor> entityContributors) {
    Objects.requireNonNull(entityContributors, "entityContributors");
    Set<Class<?>> annotatedClasses = new LinkedHashSet<>();
    for (WorkflowPersistenceEntityContributor contributor : entityContributors) {
      Collection<Class<?>> contributedClasses =
          Objects.requireNonNull(contributor, "entityContributor").annotatedClasses();
      Collection<Class<?>> requiredClasses =
          Objects.requireNonNull(contributedClasses, "annotatedClasses");
      for (Class<?> contributedClass : requiredClasses) {
        annotatedClasses.add(Objects.requireNonNull(contributedClass, "annotatedClass"));
      }
    }
    return List.copyOf(annotatedClasses);
  }

  private static void requireExplicitDataSource(Environment environment) {
    String jdbcUrl = environment.getProperty("spring.datasource.url");
    String jndiName = environment.getProperty("spring.datasource.jndi-name");
    if (isBlank(jdbcUrl) && isBlank(jndiName)) {
      throw new IllegalStateException(
          PERSISTENCE_MODE_PROPERTY
              + "=hibernate requires spring.datasource.url "
              + "or spring.datasource.jndi-name");
    }
  }

  private static boolean isBlank(String value) {
    return value == null || value.isBlank();
  }

  private static String requireNotBlank(String value, String name) {
    if (value == null || value.isBlank()) {
      throw new IllegalArgumentException(name + " must not be blank");
    }
    return value;
  }
}