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 * 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 private 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 MigrationPlatform platform = derivePlatformName(migrationConfig, connection); 106 107 new MigrationSchema(migrationConfig, connection).createAndSetIfNeeded(); 108 109 MigrationTable table = new MigrationTable(migrationConfig, connection, checkStateMode); 110 table.createIfNeededAndLock(platform); 111 112 runMigrations(resources, table, checkStateMode); 113 connection.commit(); 114 115 } catch (MigrationException e) { 116 JdbcClose.rollback(connection); 117 throw e; 118 119 } catch (Exception e) { 120 JdbcClose.rollback(connection); 121 throw new RuntimeException(e); 122 123 } finally { 124 JdbcClose.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}