001package io.ebean.migration;
002
003import io.ebean.migration.runner.LocalMigrationResource;
004import io.ebean.migration.runner.LocalMigrationResources;
005import io.ebean.migration.runner.MigrationTable;
006import io.ebean.migration.runner.MigrationSchema;
007import io.ebean.migration.util.JdbcClose;
008import org.slf4j.Logger;
009import org.slf4j.LoggerFactory;
010
011import javax.sql.DataSource;
012import java.io.IOException;
013import java.sql.Connection;
014import java.sql.SQLException;
015import java.util.List;
016
017/**
018 * Runs the DB migration typically on application start.
019 */
020public class MigrationRunner {
021
022  private static final Logger logger = LoggerFactory.getLogger(MigrationRunner.class);
023
024  private final MigrationConfig migrationConfig;
025
026  private List<LocalMigrationResource> checkMigrations;
027
028  public MigrationRunner(MigrationConfig migrationConfig) {
029    this.migrationConfig = migrationConfig;
030  }
031
032  /**
033   * Run by creating a DB connection from driver, url, username defined in MigrationConfig.
034   */
035  public void run() {
036    run(migrationConfig.createConnection());
037  }
038
039  /**
040   * Return the migrations that would be applied if the migration is run.
041   */
042  public List<LocalMigrationResource> checkState() {
043    run(migrationConfig.createConnection(), true);
044    return checkMigrations;
045  }
046
047  /**
048   * Return the migrations that would be applied if the migration is run.
049   */
050  public List<LocalMigrationResource> checkState(DataSource dataSource) {
051    run(getConnection(dataSource), true);
052    return checkMigrations;
053  }
054
055  /**
056   * Return the migrations that would be applied if the migration is run.
057   */
058  public List<LocalMigrationResource> checkState(Connection connection) {
059    run(connection, true);
060    return checkMigrations;
061  }
062
063  /**
064   * Run using the connection from the DataSource.
065   */
066  public void run(DataSource dataSource) {
067    run(getConnection(dataSource));
068  }
069
070  private Connection getConnection(DataSource dataSource) {
071
072    String username = migrationConfig.getDbUsername();
073    try {
074      if (username == null) {
075        return dataSource.getConnection();
076      }
077      logger.debug("using db user [{}] to run migrations ...", username);
078      return dataSource.getConnection(username, migrationConfig.getDbPassword());
079    } catch (SQLException e) {
080      String msgSuffix = (username == null) ? "" : " using user [" + username + "]";
081      throw new IllegalArgumentException("Error trying to connect to database for DB Migration" + msgSuffix, e);
082    }
083  }
084
085  /**
086   * Run the migrations if there are any that need running.
087   */
088  public void run(Connection connection) {
089    run(connection, false);
090  }
091
092  /**
093   * Run the migrations if there are any that need running.
094   */
095  public void run(Connection connection, boolean checkStateMode) {
096
097    LocalMigrationResources resources = new LocalMigrationResources(migrationConfig);
098    if (!resources.readResources()) {
099      logger.debug("no migrations to check");
100      return;
101    }
102
103    try {
104      connection.setAutoCommit(false);
105
106      MigrationSchema schema = new MigrationSchema(migrationConfig, connection);
107      schema.createAndSetIfNeeded();
108
109      runMigrations(resources, connection, checkStateMode);
110      connection.commit();
111
112    } catch (MigrationException e) {
113      JdbcClose.rollback(connection);
114      throw e;
115
116    } catch (Exception e) {
117      JdbcClose.rollback(connection);
118      throw new RuntimeException(e);
119
120    } finally {
121      JdbcClose.close(connection);
122    }
123  }
124
125  /**
126   * Run all the migrations as needed.
127   */
128  private void runMigrations(LocalMigrationResources resources, Connection connection, boolean checkStateMode) throws SQLException, IOException {
129    derivePlatformName(migrationConfig, connection);
130
131    MigrationTable table = new MigrationTable(migrationConfig, connection, checkStateMode);
132    table.createIfNeededAndLock();
133
134    // get the migrations in version order
135    List<LocalMigrationResource> localVersions = resources.getVersions();
136
137    logger.info("local migrations:{}  existing migrations:{}  checkState:{}", localVersions.size(), table.size(), checkStateMode);
138
139    LocalMigrationResource priorVersion = null;
140
141    // run migrations in order
142    for (LocalMigrationResource localVersion : localVersions) {
143      if (!table.shouldRun(localVersion, priorVersion)) {
144        break;
145      }
146      priorVersion = localVersion;
147    }
148    if (checkStateMode) {
149      checkMigrations = table.ran();
150    }
151  }
152
153  /**
154   * Derive and set the platform name if required.
155   */
156  private void derivePlatformName(MigrationConfig migrationConfig, Connection connection) {
157
158    if (migrationConfig.getPlatformName() == null) {
159      migrationConfig.setPlatformName(DbNameUtil.normalise(connection));
160    }
161  }
162
163}