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