WorkflowSchemaMigrator.java
package org.hammer.audio.infrastructure.workflow.collaboration.schema;
import io.github.carstenartur.jgit.storage.hibernate.schema.CoreSchemaMigrations;
import io.github.carstenartur.jgit.storage.hibernate.search.schema.SearchSchemaMigrations;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.Locale;
import java.util.Objects;
import javax.sql.DataSource;
import org.flywaydb.core.Flyway;
import org.flywaydb.core.api.configuration.FluentConfiguration;
import org.hammer.audio.infrastructure.workflow.search.WorkflowSemanticSchemaMigrations;
/** Applies shared and application-owned migrations before Hibernate schema validation. */
public final class WorkflowSchemaMigrator {
private final DataSource dataSource;
/** Creates an ordered migrator over the shared application data source. */
public WorkflowSchemaMigrator(DataSource dataSource) {
this.dataSource = Objects.requireNonNull(dataSource, "dataSource");
}
/**
* Migrates with no legacy Search adoption.
*
* <p>This overload preserves the pre-Search application integration contract.
*/
public WorkflowSchemaMigrationResult migrate(
boolean adoptLegacyCoreSchema, boolean adoptPreLeaseCollaborationSchema) {
return migrate(adoptLegacyCoreSchema, false, adoptPreLeaseCollaborationSchema);
}
/**
* Migrates Core, generic Search, application semantic Search and collaboration state in order.
*/
public WorkflowSchemaMigrationResult migrate(
boolean adoptLegacyCoreSchema,
boolean adoptLegacySearchSchema,
boolean adoptPreLeaseCollaborationSchema) {
DatabaseFamily family = detectDatabaseFamily();
int core = migrateCore(family, adoptLegacyCoreSchema);
int search = migrateSearch(family, adoptLegacySearchSchema);
int semantic = migrateSemantic(family);
int collaboration = migrateCollaboration(family, adoptPreLeaseCollaborationSchema);
return new WorkflowSchemaMigrationResult(true, core, search, semantic, collaboration);
}
private int migrateCore(DatabaseFamily family, boolean legacy) {
FluentConfiguration configuration =
Flyway.configure()
.dataSource(dataSource)
.locations(family.coreLocation)
.table(CoreSchemaMigrations.SCHEMA_HISTORY_TABLE)
.baselineOnMigrate(true);
baseline(
configuration,
legacy,
CoreSchemaMigrations.LEGACY_SCHEMA_VERSION,
CoreSchemaMigrations.LEGACY_BASELINE_DESCRIPTION,
CoreSchemaMigrations.PRE_MIGRATION_BASELINE_VERSION,
CoreSchemaMigrations.PRE_MIGRATION_BASELINE_DESCRIPTION);
return configuration.load().migrate().migrationsExecuted;
}
private int migrateSearch(DatabaseFamily family, boolean legacy) {
FluentConfiguration configuration =
Flyway.configure()
.dataSource(dataSource)
.locations(family.searchLocation)
.table(SearchSchemaMigrations.SCHEMA_HISTORY_TABLE)
.baselineOnMigrate(true);
baseline(
configuration,
legacy,
SearchSchemaMigrations.LEGACY_SCHEMA_VERSION,
SearchSchemaMigrations.LEGACY_BASELINE_DESCRIPTION,
SearchSchemaMigrations.PRE_MIGRATION_BASELINE_VERSION,
SearchSchemaMigrations.PRE_MIGRATION_BASELINE_DESCRIPTION);
return configuration.load().migrate().migrationsExecuted;
}
private int migrateSemantic(DatabaseFamily family) {
return Flyway.configure()
.dataSource(dataSource)
.locations(family.semanticLocation)
.table(WorkflowSemanticSchemaMigrations.SCHEMA_HISTORY_TABLE)
.baselineOnMigrate(true)
.baselineVersion(WorkflowSemanticSchemaMigrations.PRE_MIGRATION_BASELINE_VERSION)
.baselineDescription(WorkflowSemanticSchemaMigrations.PRE_MIGRATION_BASELINE_DESCRIPTION)
.load()
.migrate()
.migrationsExecuted;
}
private int migrateCollaboration(DatabaseFamily family, boolean legacy) {
FluentConfiguration configuration =
Flyway.configure()
.dataSource(dataSource)
.locations(family.collaborationLocation)
.table(CollaborationSchemaMigrations.SCHEMA_HISTORY_TABLE)
.baselineOnMigrate(true);
baseline(
configuration,
legacy,
CollaborationSchemaMigrations.PRE_LEASE_SCHEMA_VERSION,
CollaborationSchemaMigrations.PRE_LEASE_BASELINE_DESCRIPTION,
CollaborationSchemaMigrations.PRE_MIGRATION_BASELINE_VERSION,
CollaborationSchemaMigrations.PRE_MIGRATION_BASELINE_DESCRIPTION);
return configuration.load().migrate().migrationsExecuted;
}
private static void baseline(
FluentConfiguration configuration,
boolean legacy,
String legacyVersion,
String legacyDescription,
String initialVersion,
String initialDescription) {
if (legacy) {
configuration.baselineVersion(legacyVersion).baselineDescription(legacyDescription);
} else {
configuration.baselineVersion(initialVersion).baselineDescription(initialDescription);
}
}
private DatabaseFamily detectDatabaseFamily() {
try (Connection connection = dataSource.getConnection()) {
String product = connection.getMetaData().getDatabaseProductName().toLowerCase(Locale.ROOT);
if (product.contains("h2")) {
return DatabaseFamily.H2;
}
if (product.contains("postgresql")) {
return DatabaseFamily.POSTGRESQL;
}
throw new IllegalStateException(
"Unsupported workflow migration database: "
+ connection.getMetaData().getDatabaseProductName());
} catch (SQLException failure) {
throw new IllegalStateException("Cannot inspect workflow migration database", failure);
}
}
private enum DatabaseFamily {
H2(
CoreSchemaMigrations.H2_LOCATION,
SearchSchemaMigrations.H2_LOCATION,
WorkflowSemanticSchemaMigrations.H2_LOCATION,
CollaborationSchemaMigrations.H2_LOCATION),
POSTGRESQL(
CoreSchemaMigrations.POSTGRESQL_LOCATION,
SearchSchemaMigrations.POSTGRESQL_LOCATION,
WorkflowSemanticSchemaMigrations.POSTGRESQL_LOCATION,
CollaborationSchemaMigrations.POSTGRESQL_LOCATION);
private final String coreLocation;
private final String searchLocation;
private final String semanticLocation;
private final String collaborationLocation;
DatabaseFamily(
String coreLocation,
String searchLocation,
String semanticLocation,
String collaborationLocation) {
this.coreLocation = coreLocation;
this.searchLocation = searchLocation;
this.semanticLocation = semanticLocation;
this.collaborationLocation = collaborationLocation;
}
}
}