001package org.avaje.dbmigration;
002
003import org.avaje.dbmigration.runner.LocalMigrationResource;
004import org.avaje.dbmigration.runner.LocalMigrationResources;
005import org.avaje.dbmigration.runner.MigrationTable;
006import org.avaje.dbmigration.util.JdbcClose;
007import org.slf4j.Logger;
008import org.slf4j.LoggerFactory;
009
010import javax.sql.DataSource;
011import java.io.IOException;
012import java.sql.Connection;
013import java.sql.SQLException;
014import java.util.List;
015
016/**
017 * Runs the DB migration typically on application start.
018 */
019public class MigrationRunner {
020
021  private static final Logger logger = LoggerFactory.getLogger("org.avaje.dbmigration.MigrationRunner");
022
023  private final MigrationConfig migrationConfig;
024
025  public MigrationRunner(MigrationConfig migrationConfig) {
026    this.migrationConfig = migrationConfig;
027  }
028
029  /**
030   * Run by creating a DB connection from driver, url, username defined in MigrationConfig.
031   */
032  public void run() {
033
034    Connection connection = migrationConfig.createConnection();
035    run(connection);
036  }
037
038  public void run(DataSource dataSource) {
039
040    String username = migrationConfig.getDbUsername();
041    String password =  migrationConfig.getDbPassword();
042    if (username == null) {
043      throw new IllegalStateException("No DB migration user specified (to run the db migration) ?");
044    }
045
046    try {
047      Connection connection = dataSource.getConnection(username, password);
048      logger.debug("using db user [{}] to run migrations ...", username);
049      run(connection);
050
051    } catch (SQLException e) {
052      throw new IllegalArgumentException("Error trying to connect to database using DB Migration user [" + username + "]", e);
053    }
054  }
055
056  /**
057   * Run the migrations if there are any that need running.
058   */
059  public void run(Connection connection) {
060
061    LocalMigrationResources resources = new LocalMigrationResources(migrationConfig);
062    if (!resources.readResources()) {
063      logger.debug("no migrations to check");
064      return;
065    }
066
067    try {
068      connection.setAutoCommit(false);
069      runMigrations(resources, connection);
070
071      connection.commit();
072
073    } catch (Exception e) {
074      JdbcClose.rollback(connection);
075      throw new RuntimeException(e);
076
077    } finally {
078      JdbcClose.close(connection);
079    }
080  }
081
082  /**
083   * Run all the migrations as needed.
084   */
085  private void runMigrations(LocalMigrationResources resources, Connection connection) throws SQLException, IOException {
086
087    MigrationTable table = new MigrationTable(migrationConfig, connection);
088    table.createIfNeeded();
089
090    // get the migrations in version order
091    List<LocalMigrationResource> localVersions = resources.getVersions();
092
093    logger.info("local migrations:{}  existing migrations:{}", localVersions.size(), table.size());
094
095    LocalMigrationResource priorVersion = null;
096
097    // run migrations in order
098    for (int i = 0; i < localVersions.size(); i++) {
099      LocalMigrationResource localVersion = localVersions.get(i);
100      if (!table.shouldRun(localVersion, priorVersion)) {
101        break;
102      }
103      priorVersion = localVersion;
104      connection.commit();
105    }
106  }
107
108}