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}