001package io.ebean.migration; 002 003import java.sql.Connection; 004import java.sql.DriverManager; 005import java.sql.SQLException; 006import java.util.HashSet; 007import java.util.Map; 008import java.util.Properties; 009import java.util.Set; 010 011/** 012 * Configuration used to run the migration. 013 */ 014public class MigrationConfig { 015 016 private String migrationPath = "dbmigration"; 017 018 private String metaTable = "db_migration"; 019 020 private String applySuffix = ".sql"; 021 022 private String runPlaceholders; 023 024 private boolean skipChecksum; 025 026 private Map<String, String> runPlaceholderMap; 027 028 private ClassLoader classLoader; 029 030 private String dbUsername; 031 private String dbPassword; 032 private String dbDriver; 033 private String dbUrl; 034 035 private String dbSchema; 036 private boolean createSchemaIfNotExists = true; 037 private String platformName; 038 039 private JdbcMigrationFactory jdbcMigrationFactory = new DefaultMigrationFactory(); 040 041 /** 042 * Versions that we want to insert into migration history without actually running. 043 */ 044 private Set<String> patchInsertOn; 045 046 /** 047 * Versions that we want to update the checksum on without actually running. 048 */ 049 private Set<String> patchResetChecksumOn; 050 051 /** 052 * Return the name of the migration table. 053 */ 054 public String getMetaTable() { 055 return metaTable; 056 } 057 058 /** 059 * Set the name of the migration table. 060 */ 061 public void setMetaTable(String metaTable) { 062 this.metaTable = metaTable; 063 } 064 065 /** 066 * Parse as comma delimited versions. 067 */ 068 private Set<String> parseCommaDelimited(String versionsCommaDelimited) { 069 if (versionsCommaDelimited != null) { 070 Set<String> versions = new HashSet<>(); 071 String[] split = versionsCommaDelimited.split(","); 072 for (String version : split) { 073 if (version.startsWith("R__")) { 074 version = version.substring(3); 075 } 076 versions.add(version); 077 } 078 return versions; 079 } 080 return null; 081 } 082 083 /** 084 * Set the migrations that should have their checksum reset as a comma delimited list. 085 */ 086 public void setPatchResetChecksumOn(String versionsCommaDelimited) { 087 patchResetChecksumOn = parseCommaDelimited(versionsCommaDelimited); 088 } 089 090 /** 091 * Set the migrations that should have their checksum reset. 092 */ 093 public void setPatchResetChecksumOn(Set<String> patchResetChecksumOn) { 094 this.patchResetChecksumOn = patchResetChecksumOn; 095 } 096 097 /** 098 * Return the migrations that should have their checksum reset. 099 */ 100 public Set<String> getPatchResetChecksumOn() { 101 return patchResetChecksumOn; 102 } 103 104 /** 105 * Set the migrations that should not be run but inserted into history as if they have run. 106 */ 107 public void setPatchInsertOn(String versionsCommaDelimited) { 108 patchInsertOn = parseCommaDelimited(versionsCommaDelimited); 109 } 110 111 /** 112 * Set the migrations that should not be run but inserted into history as if they have run. 113 * <p> 114 * This can be useful when we need to pull out DDL from a repeatable migration that should really 115 * only run once. We can pull out that DDL as a new migration and add it to history as if it had been 116 * run (we can only do this when we know it exists in all environments including production). 117 * </p> 118 */ 119 public void setPatchInsertOn(Set<String> patchInsertOn) { 120 this.patchInsertOn = patchInsertOn; 121 } 122 123 /** 124 * Return the migrations that should not be run but inserted into history as if they have run. 125 */ 126 public Set<String> getPatchInsertOn() { 127 return patchInsertOn; 128 } 129 130 /** 131 * Return true if checksum check should be skipped (during development). 132 */ 133 public boolean isSkipChecksum() { 134 return skipChecksum; 135 } 136 137 /** 138 * Set to true to skip the checksum check. 139 * <p> 140 * This is intended for use during development only. 141 * </p> 142 */ 143 public void setSkipChecksum(boolean skipChecksum) { 144 this.skipChecksum = skipChecksum; 145 } 146 147 /** 148 * Return a Comma and equals delimited key/value placeholders to replace in DDL scripts. 149 */ 150 public String getRunPlaceholders() { 151 return runPlaceholders; 152 } 153 154 /** 155 * Set a Comma and equals delimited key/value placeholders to replace in DDL scripts. 156 */ 157 public void setRunPlaceholders(String runPlaceholders) { 158 this.runPlaceholders = runPlaceholders; 159 } 160 161 /** 162 * Return a map of name/value pairs that can be expressions replaced in migration scripts. 163 */ 164 public Map<String, String> getRunPlaceholderMap() { 165 return runPlaceholderMap; 166 } 167 168 /** 169 * Set a map of name/value pairs that can be expressions replaced in migration scripts. 170 */ 171 public void setRunPlaceholderMap(Map<String, String> runPlaceholderMap) { 172 this.runPlaceholderMap = runPlaceholderMap; 173 } 174 175 /** 176 * Return the root path used to find migrations. 177 */ 178 public String getMigrationPath() { 179 return migrationPath; 180 } 181 182 /** 183 * Set the root path used to find migrations. 184 */ 185 public void setMigrationPath(String migrationPath) { 186 this.migrationPath = migrationPath; 187 } 188 189 /** 190 * Return the suffix for migration resources (defaults to .sql). 191 */ 192 public String getApplySuffix() { 193 return applySuffix; 194 } 195 196 /** 197 * Set the suffix for migration resources. 198 */ 199 public void setApplySuffix(String applySuffix) { 200 this.applySuffix = applySuffix; 201 } 202 203 /** 204 * Return the DB username. 205 * <p> 206 * Used when a Connection to run the migration is not supplied. 207 * </p> 208 */ 209 public String getDbUsername() { 210 return dbUsername; 211 } 212 213 /** 214 * Set the DB username. 215 * <p> 216 * Used when a Connection to run the migration is not supplied. 217 * </p> 218 */ 219 public void setDbUsername(String dbUsername) { 220 this.dbUsername = dbUsername; 221 } 222 223 /** 224 * Return the DB password. 225 * <p> 226 * Used when creating a Connection to run the migration. 227 * </p> 228 */ 229 public String getDbPassword() { 230 return dbPassword; 231 } 232 233 /** 234 * Set the DB password. 235 * <p> 236 * Used when creating a Connection to run the migration. 237 * </p> 238 */ 239 public void setDbPassword(String dbPassword) { 240 this.dbPassword = dbPassword; 241 } 242 243 /** 244 * Return the DB Driver. 245 * <p> 246 * Used when creating a Connection to run the migration. 247 * </p> 248 */ 249 public String getDbDriver() { 250 return dbDriver; 251 } 252 253 /** 254 * Set the DB Driver. 255 * <p> 256 * Used when creating a Connection to run the migration. 257 * </p> 258 */ 259 public void setDbDriver(String dbDriver) { 260 this.dbDriver = dbDriver; 261 } 262 263 /** 264 * Return the DB connection URL. 265 * <p> 266 * Used when creating a Connection to run the migration. 267 * </p> 268 */ 269 public String getDbUrl() { 270 return dbUrl; 271 } 272 273 /** 274 * Set the DB connection URL. 275 * <p> 276 * Used when creating a Connection to run the migration. 277 * </p> 278 */ 279 public void setDbUrl(String dbUrl) { 280 this.dbUrl = dbUrl; 281 } 282 283 /** 284 * Return the DB connection Schema. 285 * <p> 286 * Used when creating a Connection to run the migration. 287 * </p> 288 */ 289 public String getDbSchema() { 290 return dbSchema; 291 } 292 293 /** 294 * Set the DB connection Schema. 295 * <p> 296 * Used when creating a Connection to run the migration. 297 * </p> 298 */ 299 public void setDbSchema(String dbSchema) { 300 this.dbSchema = dbSchema; 301 } 302 303 /** 304 * Return true if migration should create the schema if it does not exist. 305 */ 306 public boolean isCreateSchemaIfNotExists() { 307 return createSchemaIfNotExists; 308 } 309 310 /** 311 * Set to create Schema if it does not exist. 312 */ 313 public void setCreateSchemaIfNotExists(boolean createSchemaIfNotExists) { 314 this.createSchemaIfNotExists = createSchemaIfNotExists; 315 } 316 317 /** 318 * Return the DB platform name (used for platform create table and select for update syntax). 319 */ 320 public String getPlatformName() { 321 return platformName; 322 } 323 324 /** 325 * Set a DB platform name (to load specific create table and select for update syntax). 326 */ 327 public void setPlatformName(String platformName) { 328 this.platformName = platformName; 329 } 330 331 /** 332 * Return the ClassLoader to use to load resources. 333 */ 334 public ClassLoader getClassLoader() { 335 if (classLoader == null) { 336 classLoader = Thread.currentThread().getContextClassLoader(); 337 if (classLoader == null) { 338 classLoader = this.getClass().getClassLoader(); 339 } 340 } 341 return classLoader; 342 } 343 344 /** 345 * Set the ClassLoader to use when loading resources. 346 */ 347 public void setClassLoader(ClassLoader classLoader) { 348 this.classLoader = classLoader; 349 } 350 351 /** 352 * Returns the jdbcMigrationFactory. 353 */ 354 public JdbcMigrationFactory getJdbcMigrationFactory() { 355 return jdbcMigrationFactory; 356 } 357 358 /** 359 * Sets the jdbcMigrationFactory. 360 */ 361 public void setJdbcMigrationFactory(JdbcMigrationFactory jdbcMigrationFactory) { 362 this.jdbcMigrationFactory = jdbcMigrationFactory; 363 } 364 365 /** 366 * Load configuration from standard properties. 367 */ 368 public void load(Properties props) { 369 370 dbUsername = props.getProperty("dbmigration.username", dbUsername); 371 dbPassword = props.getProperty("dbmigration.password", dbPassword); 372 dbDriver = props.getProperty("dbmigration.driver", dbDriver); 373 dbUrl = props.getProperty("dbmigration.url", dbUrl); 374 dbSchema = props.getProperty("dbmigration.schema", dbSchema); 375 376 String skip = props.getProperty("dbmigration.skipchecksum"); 377 if (skip != null) { 378 skipChecksum = Boolean.parseBoolean(skip); 379 } 380 381 String createSchema = props.getProperty("dbmigration.createSchemaIfNotExists"); 382 if (createSchema != null) { 383 createSchemaIfNotExists = Boolean.parseBoolean(createSchema); 384 } 385 386 platformName = props.getProperty("dbmigration.platformName", platformName); 387 applySuffix = props.getProperty("dbmigration.applySuffix", applySuffix); 388 metaTable = props.getProperty("dbmigration.metaTable", metaTable); 389 migrationPath = props.getProperty("dbmigration.migrationPath", migrationPath); 390 runPlaceholders = props.getProperty("dbmigration.placeholders", runPlaceholders); 391 392 String patchInsertOn = props.getProperty("dbmigration.patchInsertOn"); 393 if (patchInsertOn != null) { 394 setPatchInsertOn(patchInsertOn); 395 } 396 String patchResetChecksumOn = props.getProperty("dbmigration.patchResetChecksumOn"); 397 if (patchInsertOn != null) { 398 setPatchResetChecksumOn(patchResetChecksumOn); 399 } 400 String runPlaceholders = props.getProperty("dbmigration.runPlaceholders"); 401 if (runPlaceholders != null) { 402 setRunPlaceholders(runPlaceholders); 403 } 404 } 405 406 /** 407 * Create a Connection to the database using the configured driver, url, username etc. 408 * <p> 409 * Used when an existing DataSource or Connection is not supplied. 410 * </p> 411 */ 412 public Connection createConnection() { 413 414 if (dbUsername == null) throw new MigrationException("Database username is null?"); 415 if (dbPassword == null) throw new MigrationException("Database password is null?"); 416 if (dbDriver == null) throw new MigrationException("Database Driver is null?"); 417 if (dbUrl == null) throw new MigrationException("Database connection URL is null?"); 418 419 loadDriver(); 420 421 try { 422 Properties props = new Properties(); 423 props.setProperty("user", dbUsername); 424 props.setProperty("password", dbPassword); 425 return DriverManager.getConnection(dbUrl, props); 426 427 } catch (SQLException e) { 428 throw new MigrationException("Error trying to create Connection", e); 429 } 430 } 431 432 private void loadDriver() { 433 try { 434 Class.forName(dbDriver, true, getClassLoader()); 435 } catch (Throwable e) { 436 throw new MigrationException("Problem loading Database Driver [" + dbDriver + "]: " + e.getMessage(), e); 437 } 438 } 439 440 /** 441 * Default factory. Uses the migration's class loader and injects the config if necessary. 442 * 443 * @author Roland Praml, FOCONIS AG 444 */ 445 public class DefaultMigrationFactory implements JdbcMigrationFactory { 446 447 @Override 448 public JdbcMigration createInstance(String className) { 449 try { 450 Class<?> clazz = Class.forName(className, true, MigrationConfig.this.getClassLoader()); 451 JdbcMigration migration = (JdbcMigration)clazz.newInstance(); 452 if (migration instanceof ConfigurationAware) { 453 ((ConfigurationAware) migration).setMigrationConfig(MigrationConfig.this); 454 } 455 return migration; 456 } catch (Exception e) { 457 throw new IllegalArgumentException(className + " is not a valid JdbcMigration", e); 458 } 459 } 460 } 461 462}