001package io.ebean.migration; 002 003import io.ebean.migration.runner.LocalMigrationResource; 004import io.ebean.migration.runner.LocalMigrationResources; 005import io.ebean.migration.runner.MigrationPlatform; 006import io.ebean.migration.runner.MigrationSchema; 007import io.ebean.migration.runner.MigrationTable; 008import org.slf4j.Logger; 009import org.slf4j.LoggerFactory; 010 011import javax.sql.DataSource; 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(MigrationRunner.class); 022 023 private final MigrationConfig migrationConfig; 024 025 private List<LocalMigrationResource> checkMigrations; 026 027 public MigrationRunner(MigrationConfig migrationConfig) { 028 this.migrationConfig = migrationConfig; 029 } 030 031 /** 032 * Return the migrations that would be applied if the migration is run. 033 */ 034 public List<LocalMigrationResource> checkState() { 035 run(migrationConfig.createConnection(), true); 036 return checkMigrations; 037 } 038 039 /** 040 * Return the migrations that would be applied if the migration is run. 041 */ 042 public List<LocalMigrationResource> checkState(DataSource dataSource) { 043 run(getConnection(dataSource), 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(Connection connection) { 051 run(connection, true); 052 return checkMigrations; 053 } 054 055 /** 056 * Run by creating a DB connection from driver, url, username defined in MigrationConfig. 057 */ 058 public void run() { 059 run(migrationConfig.createConnection()); 060 } 061 062 /** 063 * Run using the connection from the DataSource. 064 */ 065 public void run(DataSource dataSource) { 066 run(getConnection(dataSource)); 067 } 068 069 /** 070 * Run the migrations if there are any that need running. 071 */ 072 public void run(Connection connection) { 073 run(connection, false); 074 } 075 076 private Connection getConnection(DataSource dataSource) { 077 String username = migrationConfig.getDbUsername(); 078 try { 079 if (username == null) { 080 return dataSource.getConnection(); 081 } 082 logger.debug("using db user [{}] to run migrations ...", username); 083 return dataSource.getConnection(username, migrationConfig.getDbPassword()); 084 } catch (SQLException e) { 085 String msgSuffix = (username == null) ? "" : " using user [" + username + "]"; 086 throw new IllegalArgumentException("Error trying to connect to database for DB Migration" + msgSuffix, e); 087 } 088 } 089 090 /** 091 * Run the migrations if there are any that need running. 092 */ 093 private void run(Connection connection, boolean checkStateMode) { 094 095 LocalMigrationResources resources = new LocalMigrationResources(migrationConfig); 096 if (!resources.readResources()) { 097 logger.debug("no migrations to check"); 098 return; 099 } 100 101 try { 102 connection.setAutoCommit(false); 103 MigrationPlatform platform = derivePlatformName(migrationConfig, connection); 104 105 new MigrationSchema(migrationConfig, connection).createAndSetIfNeeded(); 106 107 MigrationTable table = new MigrationTable(migrationConfig, connection, checkStateMode, platform); 108 table.createIfNeededAndLock(); 109 110 runMigrations(resources, table, checkStateMode); 111 connection.commit(); 112 113 table.runNonTransactional(); 114 115 } catch (MigrationException e) { 116 rollback(connection); 117 throw e; 118 119 } catch (Exception e) { 120 rollback(connection); 121 throw new RuntimeException(e); 122 123 } finally { 124 close(connection); 125 } 126 } 127 128 /** 129 * Run all the migrations as needed. 130 */ 131 private void runMigrations(LocalMigrationResources resources, MigrationTable table, boolean checkStateMode) throws SQLException { 132 133 // get the migrations in version order 134 List<LocalMigrationResource> localVersions = resources.getVersions(); 135 136 if (table.isEmpty()) { 137 LocalMigrationResource initVersion = getInitVersion(); 138 if (initVersion != null) { 139 // run using a dbinit script 140 logger.info("dbinit migration version:{} local migrations:{} checkState:{}", initVersion, localVersions.size(), checkStateMode); 141 checkMigrations = table.runInit(initVersion, localVersions); 142 return; 143 } 144 } 145 146 logger.info("local migrations:{} existing migrations:{} checkState:{}", localVersions.size(), table.size(), checkStateMode); 147 checkMigrations = table.runAll(localVersions); 148 } 149 150 /** 151 * Return the last init migration. 152 */ 153 private LocalMigrationResource getInitVersion() { 154 LocalMigrationResources initResources = new LocalMigrationResources(migrationConfig); 155 if (initResources.readInitResources()) { 156 List<LocalMigrationResource> initVersions = initResources.getVersions(); 157 if (!initVersions.isEmpty()) { 158 return initVersions.get(initVersions.size() - 1); 159 } 160 } 161 return null; 162 } 163 164 /** 165 * Return the platform deriving from connection if required. 166 */ 167 private MigrationPlatform derivePlatformName(MigrationConfig migrationConfig, Connection connection) { 168 169 String platformName = migrationConfig.getPlatformName(); 170 if (platformName == null) { 171 platformName = DbNameUtil.normalise(connection); 172 migrationConfig.setPlatformName(platformName); 173 } 174 175 return DbNameUtil.platform(platformName); 176 } 177 178 /** 179 * Close the connection logging if an error occurs. 180 */ 181 private void close(Connection connection) { 182 try { 183 if (connection != null) { 184 connection.close(); 185 } 186 } catch (SQLException e) { 187 logger.warn("Error closing connection", e); 188 } 189 } 190 191 /** 192 * Rollback the connection logging if an error occurs. 193 */ 194 private void rollback(Connection connection) { 195 try { 196 if (connection != null) { 197 connection.rollback(); 198 } 199 } catch (SQLException e) { 200 logger.warn("Error on connection rollback", e); 201 } 202 } 203}