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