io.convergence_platform.common.database.BaseDatabaseMigratorService Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of service-lib Show documentation
Show all versions of service-lib Show documentation
Holds the common functionality needed by all Convergence Platform-based services written in Java.
The newest version!
package io.convergence_platform.common.database;
import io.convergence_platform.common.dag.WorkflowDAG;
import io.convergence_platform.common.dag.WorkflowStepInfo;
import io.convergence_platform.common.database.blueprint_formatters.h2.*;
import io.convergence_platform.common.database.blueprint_formatters.mysql.*;
import io.convergence_platform.common.database.blueprint_formatters.postgres.*;
import io.convergence_platform.common.database.migrations.SqlDialectFormatter;
import java.sql.*;
import java.time.Instant;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
public abstract class BaseDatabaseMigratorService implements IDatabaseMigratorService {
protected abstract String getDatabaseURL();
protected abstract String getDatabaseUserName();
protected abstract String getDatabasePassword();
public void migrateDatabase() {
// logger.info("Started migrating database");
List migrations = getAllDatabasePreparationSteps();
// logger.info(String.format("Found %d migrations:", migrations.size()));
// for (int i = 0; i < migrations.size(); i++) {
// logger.info(String.format(" %d. %s", i + 1, migrations.get(i).name()));
// }
List appliedDatabaseMigrations = getListOfMigrations();
SqlDialectFormatter formatters = initializeFormatters();
try (Connection connection = DriverManager.getConnection(getDatabaseURL(), getDatabaseUserName(), getDatabasePassword())) {
for (IDatabasePreparationStep migration : migrations) {
if (!hasBeenApplied(migration, appliedDatabaseMigrations)) {
// logger.info("Start applying migration: " + migration.name());
applyMigration(migration, connection, formatters);
}
}
onAfterAppliedMigrations(connection);
} catch (SQLException e) {
throw new RuntimeException(e);
}
}
protected void onAfterAppliedMigrations(Connection connection) {
}
private String getMigrationSQL(IDatabasePreparationStep migration, SqlDialectFormatter formatters) {
String result = null;
if (migration instanceof IDatabaseMigration) {
result = ((IDatabaseMigration) migration).getMigrationDDL(formatters);
} else if (migration instanceof IDatabaseSeed) {
List entries = ((IDatabaseSeed) migration).getSeeds();
result = formatters.insertSeedsFormatter.toSQL(entries);
} else {
throw new RuntimeException();
}
return result;
}
private SqlDialectFormatter initializeFormatters() {
SqlDialectFormatter result = new SqlDialectFormatter();
if (getDatabaseURL().startsWith("jdbc:h2:mem:")) {
// H2 DB for test
result.createTableFormatter = new H2InMemoryTableBlueprintFormatter();
result.createRelationFormatter = new H2InMemoryTableRelationFormatter();
result.insertSeedsFormatter = new H2InMemoryDatabaseSeedFormatter();
} else if (getDatabaseURL().startsWith("jdbc:postgresql:")) {
// Production Postgres
result.createTableFormatter = new PostgresTableBlueprintFormatter();
result.createRelationFormatter = new PostgresTableRelationFormatter();
result.insertSeedsFormatter = new PostgresDatabaseSeedFormatter();
} else if (getDatabaseURL().startsWith("jdbc:mysql:")) {
// Production Postgres
result.createTableFormatter = new MysqlTableBlueprintFormatter();
result.createRelationFormatter = new MysqlTableRelationFormatter();
result.insertSeedsFormatter = new MysqlDatabaseSeedFormatter();
}
return result;
}
private void applyMigration(IDatabasePreparationStep migration, Connection connection, SqlDialectFormatter formatters) {
String command = null;
if (migration instanceof IDatabaseSeed) {
command = getMigrationSQL(migration, formatters);
executeSQL(migration, command, connection);
} else if (migration instanceof IMigrationWithCustomExecute && migration instanceof IDatabaseMigration) {
command = ((IDatabaseMigration) migration).getMigrationDDL(formatters);
((IMigrationWithCustomExecute) migration).execute(connection);
} else if (migration instanceof IDatabaseMigration) {
command = ((IDatabaseMigration) migration).getMigrationDDL(formatters);
executeSQL(migration, command, connection);
} else {
throw new RuntimeException();
}
IDatabaseMigrationEntry m = instantiateNewMigrationEntryForDB();
m.setMigrationName(migration.name());
m.setCommand(command);
m.setAppliedTimestamp(Timestamp.from(Instant.now()));
saveMigrationEntryToDatabase(m);
}
protected abstract IDatabaseMigrationEntry saveMigrationEntryToDatabase(IDatabaseMigrationEntry m);
protected abstract IDatabaseMigrationEntry instantiateNewMigrationEntryForDB();
private void executeSQL(IDatabasePreparationStep migration, String ddl, Connection connection) {
try {
Statement statement = connection.createStatement();
statement.execute(ddl);
} catch (Exception ex) {
// logger.error("Unable to run migration: " + migration.name());
// logger.error("Migration DDL:");
// logger.error(ddl);
// logger.error("Exception message: " + ex.getMessage());
boolean shouldThrowException = true;
if (migration instanceof IDatabaseMigration && ((IDatabaseMigration) migration).allowFailure()) {
shouldThrowException = false;
}
if (shouldThrowException) {
throw new RuntimeException(String.format("Migration '%s' failed with an exception.", migration.name()), ex);
}
}
}
private boolean hasBeenApplied(IDatabasePreparationStep migration, List appliedDatabaseMigrations) {
return getMigrationRecord(migration, appliedDatabaseMigrations) != null;
}
private IDatabaseMigrationEntry getMigrationRecord(IDatabasePreparationStep migration, List appliedDatabaseMigrations) {
IDatabaseMigrationEntry result = null;
for (IDatabaseMigrationEntry m : appliedDatabaseMigrations) {
if (m.getMigrationName().equals(migration.name())) {
result = m;
break;
}
}
return result;
}
protected abstract List getListOfMigrations();
private List getAllDatabasePreparationSteps() {
Map migrations = getMigrationBeans();
Map stepsMap = new HashMap<>();
Map stepsMigrationMap = new HashMap<>();
List steps = new ArrayList<>();
List noDependencyMigrations = new ArrayList<>();
for (String migrationName : migrations.keySet()) {
IDatabasePreparationStep migration = migrations.get(migrationName);
WorkflowStepInfo step = new WorkflowStepInfo();
step.id = migration.name();
step.name = migration.name();
stepsMap.put(migration.name(), step);
stepsMigrationMap.put(migration.name(), migration);
steps.add(step);
if (migration.getDependencies().isEmpty()) {
noDependencyMigrations.add(migration);
}
}
for (String migrationName : migrations.keySet()) {
IDatabasePreparationStep migration = migrations.get(migrationName);
WorkflowStepInfo step = stepsMap.get(migration.name());
if (!migration.getDependencies().isEmpty()) {
step.dependencies = new ArrayList<>();
for (String depName : migration.getDependencies()) {
step.dependencies.add(stepsMap.get(depName));
}
}
}
if (noDependencyMigrations.size() != 1) {
// Why this exception happens:
// 1. There is no migration without any dependency. This is most likely an indication of a loop in the
// migration dependency.
// 2. There are at least two migrations without any dependency. Considering that there should already
// be on that creates the database, this means the other migrations should at least be dependent of
// the create database migration.
throw new RuntimeException("The migrations list should only have one migration without any dependency.");
}
for (String migrationName : migrations.keySet()) {
IDatabasePreparationStep migration = migrations.get(migrationName);
List dependencies = stepsMap.get(migration.name()).dependencies;
for (String dependency : migration.getDependencies()) {
dependencies.add(stepsMap.get(dependency));
}
}
// Sort the DAG steps and run them 1 by 1
WorkflowDAG graph = WorkflowDAG.from(steps);
return graph.getSortedList().stream().map(s -> stepsMigrationMap.get(s.name)).collect(Collectors.toList());
}
protected abstract Map getMigrationBeans();
}