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 io.ebean.migration.util.JdbcClose; 009import org.slf4j.Logger; 010import org.slf4j.LoggerFactory; 011 012import javax.sql.DataSource; 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 * Return the migrations that would be applied if the migration is run. 034 */ 035 public List<LocalMigrationResource> checkState() { 036 run(migrationConfig.createConnection(), true); 037 return checkMigrations; 038 } 039 040 /** 041 * Return the migrations that would be applied if the migration is run. 042 */ 043 public List<LocalMigrationResource> checkState(DataSource dataSource) { 044 run(getConnection(dataSource), true); 045 return checkMigrations; 046 } 047 048 /** 049 * Return the migrations that would be applied if the migration is run. 050 */ 051 public List<LocalMigrationResource> checkState(Connection connection) { 052 run(connection, true); 053 return checkMigrations; 054 } 055 056 /** 057 * Run by creating a DB connection from driver, url, username defined in MigrationConfig. 058 */ 059 public void run() { 060 run(migrationConfig.createConnection()); 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 /** 071 * Run the migrations if there are any that need running. 072 */ 073 public void run(Connection connection) { 074 run(connection, false); 075 } 076 077 private Connection getConnection(DataSource dataSource) { 078 String username = migrationConfig.getDbUsername(); 079 try { 080 if (username == null) { 081 return dataSource.getConnection(); 082 } 083 logger.debug("using db user [{}] to run migrations ...", username); 084 return dataSource.getConnection(username, migrationConfig.getDbPassword()); 085 } catch (SQLException e) { 086 String msgSuffix = (username == null) ? "" : " using user [" + username + "]"; 087 throw new IllegalArgumentException("Error trying to connect to database for DB Migration" + msgSuffix, e); 088 } 089 } 090 091 /** 092 * Run the migrations if there are any that need running. 093 */ 094 private void run(Connection connection, boolean checkStateMode) { 095 096 LocalMigrationResources resources = new LocalMigrationResources(migrationConfig); 097 if (!resources.readResources()) { 098 logger.debug("no migrations to check"); 099 return; 100 } 101 102 try { 103 connection.setAutoCommit(false); 104 MigrationPlatform platform = derivePlatformName(migrationConfig, connection); 105 106 new MigrationSchema(migrationConfig, connection).createAndSetIfNeeded(); 107 108 MigrationTable table = new MigrationTable(migrationConfig, connection, checkStateMode, platform); 109 table.createIfNeededAndLock(); 110 111 runMigrations(resources, table, checkStateMode); 112 connection.commit(); 113 114 table.runNonTransactional(); 115 116 } catch (MigrationException e) { 117 JdbcClose.rollback(connection); 118 throw e; 119 120 } catch (Exception e) { 121 JdbcClose.rollback(connection); 122 throw new RuntimeException(e); 123 124 } finally { 125 JdbcClose.close(connection); 126 } 127 } 128 129 /** 130 * Run all the migrations as needed. 131 */ 132 private void runMigrations(LocalMigrationResources resources, MigrationTable table, boolean checkStateMode) throws SQLException { 133 134 // get the migrations in version order 135 List<LocalMigrationResource> localVersions = resources.getVersions(); 136 137 if (table.isEmpty()) { 138 LocalMigrationResource initVersion = getInitVersion(); 139 if (initVersion != null) { 140 // run using a dbinit script 141 logger.info("dbinit migration version:{} local migrations:{} checkState:{}", initVersion, localVersions.size(), checkStateMode); 142 checkMigrations = table.runInit(initVersion, localVersions); 143 return; 144 } 145 } 146 147 logger.info("local migrations:{} existing migrations:{} checkState:{}", localVersions.size(), table.size(), checkStateMode); 148 checkMigrations = table.runAll(localVersions); 149 } 150 151 /** 152 * Return the last init migration. 153 */ 154 private LocalMigrationResource getInitVersion() { 155 LocalMigrationResources initResources = new LocalMigrationResources(migrationConfig); 156 if (initResources.readInitResources()) { 157 List<LocalMigrationResource> initVersions = initResources.getVersions(); 158 if (!initVersions.isEmpty()) { 159 return initVersions.get(initVersions.size() - 1); 160 } 161 } 162 return null; 163 } 164 165 /** 166 * Return the platform deriving from connection if required. 167 */ 168 private MigrationPlatform derivePlatformName(MigrationConfig migrationConfig, Connection connection) { 169 170 String platformName = migrationConfig.getPlatformName(); 171 if (platformName == null) { 172 platformName = DbNameUtil.normalise(connection); 173 migrationConfig.setPlatformName(platformName); 174 } 175 176 return DbNameUtil.platform(platformName); 177 } 178 179}