001package io.ebean.config; 002 003import io.ebean.EbeanVersion; 004import io.ebean.annotation.Platform; 005import io.ebean.migration.MigrationConfig; 006import io.ebean.migration.MigrationRunner; 007import io.ebean.util.StringHelper; 008import org.slf4j.Logger; 009import org.slf4j.LoggerFactory; 010 011import java.time.ZonedDateTime; 012import java.time.format.DateTimeFormatter; 013import java.util.Map; 014import java.util.Properties; 015 016/** 017 * Configuration for the DB migration processing. 018 */ 019public class DbMigrationConfig { 020 021 protected static final Logger logger = LoggerFactory.getLogger(DbMigrationConfig.class); 022 023 protected MigrationConfig runnerConfig = new MigrationConfig(); 024 025 /** 026 * The database platform to generate migration DDL for. 027 */ 028 protected Platform platform; 029 030 /** 031 * Resource path for the migration xml and sql. 032 */ 033 protected String migrationPath = "dbmigration"; 034 035 protected String migrationInitPath = "dbinit"; 036 037 /** 038 * Subdirectory the model xml files go into. 039 */ 040 protected String modelPath = "model"; 041 042 protected String applySuffix = ".sql"; 043 044 /** 045 * Set this to "V" to be compatible with FlywayDB. 046 */ 047 protected String applyPrefix = ""; 048 049 protected String modelSuffix = ".model.xml"; 050 051 /** 052 * For running migration the DB table that holds migration execution status. 053 */ 054 protected String metaTable = "db_migration"; 055 056 /** 057 * Flag set to true means to run any outstanding migrations on startup. 058 */ 059 protected boolean runMigration; 060 061 /** 062 * Comma and equals delimited key/value placeholders to replace in DDL scripts. 063 */ 064 protected String runPlaceholders; 065 066 /** 067 * Map of key/value placeholders to replace in DDL scripts. 068 */ 069 protected Map<String, String> runPlaceholderMap; 070 071 /** 072 * DB schema used for the migration (and testing). 073 */ 074 protected String dbSchema; 075 076 /** 077 * Set to true if we consider this the 'default schema' (Postgres schema that matches DB username) 078 */ 079 protected boolean defaultDbSchema; 080 081 /** 082 * DB user used to run the DB migration. 083 */ 084 protected String dbUsername; 085 086 /** 087 * DB password used to run the DB migration. 088 */ 089 protected String dbPassword; 090 091 protected String patchInsertOn; 092 093 protected String patchResetChecksumOn; 094 095 /** 096 * Mode used to check non-null columns added via migration have a default value specified etc. 097 */ 098 protected boolean strictMode = true; 099 100 /** 101 * Contains the DDL-header information. 102 */ 103 protected String ddlHeader; 104 105 /** 106 * Return the DB platform to generate migration DDL for. 107 * <p> 108 * We typically need to explicitly specify this as migration can often be generated 109 * when running against H2. 110 */ 111 public Platform getPlatform() { 112 return platform; 113 } 114 115 /** 116 * Set the DB platform to generate migration DDL for. 117 */ 118 public void setPlatform(Platform platform) { 119 this.platform = platform; 120 } 121 122 /** 123 * Return the path for normal migrations or dbinit migrations. 124 * 125 * @param dbinitMigration When true return the path for dbinit migrations. 126 */ 127 public String getMigrationPath(boolean dbinitMigration) { 128 return dbinitMigration ? migrationInitPath : migrationPath; 129 } 130 131 /** 132 * Return the resource path for db migrations. 133 */ 134 public String getMigrationPath() { 135 return migrationPath; 136 } 137 138 /** 139 * Set the resource path for db migrations. 140 * <p> 141 * The default of "dbmigration" is reasonable in most cases. You may look to set this 142 * to be something like "dbmigration/myapp" where myapp gives it a unique resource path 143 * in the case there are multiple EbeanServer applications in the single classpath. 144 * </p> 145 */ 146 public void setMigrationPath(String migrationPath) { 147 this.migrationPath = migrationPath; 148 } 149 150 /** 151 * Return the relative path for the model files (defaults to model). 152 */ 153 public String getModelPath() { 154 return modelPath; 155 } 156 157 /** 158 * Set the relative path for the model files. 159 */ 160 public void setModelPath(String modelPath) { 161 this.modelPath = modelPath; 162 } 163 164 /** 165 * Return the model suffix (defaults to model.xml) 166 */ 167 public String getModelSuffix() { 168 return modelSuffix; 169 } 170 171 /** 172 * Set the model suffix. 173 */ 174 public void setModelSuffix(String modelSuffix) { 175 this.modelSuffix = modelSuffix; 176 } 177 178 /** 179 * Return the apply script suffix (defaults to sql). 180 */ 181 public String getApplySuffix() { 182 return applySuffix; 183 } 184 185 /** 186 * Set the apply script suffix (defaults to sql). 187 */ 188 public void setApplySuffix(String applySuffix) { 189 this.applySuffix = applySuffix; 190 } 191 192 /** 193 * Return the apply prefix. 194 */ 195 public String getApplyPrefix() { 196 return applyPrefix; 197 } 198 199 /** 200 * Set the apply prefix. This might be set to "V" for use with FlywayDB. 201 */ 202 public void setApplyPrefix(String applyPrefix) { 203 this.applyPrefix = applyPrefix; 204 } 205 206 /** 207 * Return the table name that holds the migration run details 208 * (used by DB Migration runner only). 209 */ 210 public String getMetaTable() { 211 return metaTable; 212 } 213 214 /** 215 * Set the table name that holds the migration run details 216 * (used by DB Migration runner only). 217 */ 218 public void setMetaTable(String metaTable) { 219 this.metaTable = metaTable; 220 } 221 222 /** 223 * Return a comma and equals delimited placeholders that are substituted in SQL scripts when running migration 224 * (used by DB Migration runner only). 225 */ 226 public String getRunPlaceholders() { 227 // environment properties take precedence 228 String placeholders = readEnvironment("ddl.migration.placeholders"); 229 if (placeholders != null) { 230 return placeholders; 231 } 232 return runPlaceholders; 233 } 234 235 /** 236 * Set a comma and equals delimited placeholders that are substituted in SQL scripts when running migration 237 * (used by DB Migration runner only). 238 */ 239 public void setRunPlaceholders(String runPlaceholders) { 240 this.runPlaceholders = runPlaceholders; 241 } 242 243 /** 244 * Return a map of placeholder values that are substituted in SQL scripts when running migration 245 * (used by DB Migration runner only). 246 */ 247 public Map<String, String> getRunPlaceholderMap() { 248 return runPlaceholderMap; 249 } 250 251 /** 252 * Set a map of placeholder values that are substituted when running migration 253 * (used by DB Migration runner only). 254 */ 255 public void setRunPlaceholderMap(Map<String, String> runPlaceholderMap) { 256 this.runPlaceholderMap = runPlaceholderMap; 257 } 258 259 /** 260 * Return true if the DB migration should be run on startup. 261 */ 262 public boolean isRunMigration() { 263 // environment properties take precedence 264 String run = readEnvironment("ddl.migration.run"); 265 if (run != null) { 266 return "true".equalsIgnoreCase(run.trim()); 267 } 268 return runMigration; 269 } 270 271 /** 272 * Set to true to run the DB migration on startup. 273 */ 274 public void setRunMigration(boolean runMigration) { 275 this.runMigration = runMigration; 276 } 277 278 /** 279 * Return the DB username to use for running DB migrations. 280 */ 281 public String getDbUsername() { 282 // environment properties take precedence 283 String user = readEnvironment("ddl.migration.user"); 284 if (user != null) { 285 return user; 286 } 287 return dbUsername; 288 } 289 290 /** 291 * Set the DB username to use for running DB migrations. 292 */ 293 public void setDbUsername(String dbUsername) { 294 this.dbUsername = dbUsername; 295 } 296 297 /** 298 * Return the DB password to use for running DB migrations. 299 */ 300 public String getDbPassword() { 301 String user = readEnvironment("ddl.migration.password"); 302 if (user != null) { 303 return user; 304 } 305 return dbPassword; 306 } 307 308 /** 309 * Set the DB password to use for running DB migrations. 310 */ 311 public void setDbPassword(String dbPassword) { 312 this.dbPassword = dbPassword; 313 } 314 315 /** 316 * Return the DB schema to use (for migration, testing etc). 317 */ 318 public String getDbSchema() { 319 String schema = readEnvironment("ddl.migration.schema"); 320 if (schema != null) { 321 return schema; 322 } 323 return dbSchema; 324 } 325 326 /** 327 * Set the Db schema to use. 328 */ 329 public void setDbSchema(String dbSchema) { 330 this.dbSchema = dbSchema; 331 } 332 333 /** 334 * Set the Db schema if it hasn't already been defined. 335 */ 336 public void setDefaultDbSchema(String dbSchema) { 337 this.defaultDbSchema = true; 338 this.dbSchema = dbSchema; 339 } 340 341 /** 342 * Return true if this is considered the default DB schema (Postgres schema matching DB username). 343 */ 344 public boolean isDefaultDbSchema() { 345 return defaultDbSchema; 346 } 347 348 /** 349 * Return migration versions that should be added to history without running. 350 */ 351 public String getPatchInsertOn() { 352 return patchInsertOn; 353 } 354 355 /** 356 * Set migration versions that should be added to history without running. 357 * <p> 358 * Value can be a string containing comma delimited list of version numbers. 359 * </p> 360 */ 361 public void setPatchInsertOn(String patchInsertOn) { 362 this.patchInsertOn = patchInsertOn; 363 } 364 365 /** 366 * Return migration versions that should have their checksum reset and not run. 367 */ 368 public String getPatchResetChecksumOn() { 369 return patchResetChecksumOn; 370 } 371 372 /** 373 * Returns a DDL header prepend for each DDL. E.g. for copyright headers 374 * You can use placeholders like ${version} or ${timestamp} in properties file. 375 */ 376 public String getDdlHeader() { 377 if (ddlHeader != null && !ddlHeader.isEmpty()) { 378 ddlHeader = StringHelper.replaceString(ddlHeader, "${version}", EbeanVersion.getVersion()); 379 ddlHeader = StringHelper.replaceString(ddlHeader, "${timestamp}", ZonedDateTime.now().format( DateTimeFormatter.ISO_INSTANT )); 380 } 381 return ddlHeader; 382 } 383 384 /** 385 * Set the header prepended to the DDL. 386 */ 387 public void setDdlHeader(String ddlHeader) { 388 this.ddlHeader = ddlHeader; 389 } 390 391 /** 392 * Set migration versions that should have their checksum reset and not run. 393 * <p> 394 * Value can be a string containing comma delimited list of version numbers. 395 * </p> 396 */ 397 public void setPatchResetChecksumOn(String patchResetChecksumOn) { 398 this.patchResetChecksumOn = patchResetChecksumOn; 399 } 400 401 /** 402 * Return true if strict mode is used which includes a check that non-null columns have a default value. 403 */ 404 public boolean isStrictMode() { 405 String envValue = readEnvironment("ddl.migration.strictMode"); 406 if (!isEmpty(envValue)) { 407 return Boolean.parseBoolean(envValue.trim()); 408 } 409 return strictMode; 410 } 411 412 /** 413 * Set to false to turn off strict mode allowing non-null columns to not have a default value. 414 */ 415 public void setStrictMode(boolean strictMode) { 416 this.strictMode = strictMode; 417 } 418 419 /** 420 * Return the underlying migration runner configuration allowing for more advanced settings. 421 */ 422 public MigrationConfig getRunnerConfig() { 423 return runnerConfig; 424 } 425 426 /** 427 * Load the settings from the PropertiesWrapper. 428 */ 429 public void loadSettings(PropertiesWrapper properties, String serverName) { 430 431 migrationPath = properties.get("migration.migrationPath", migrationPath); 432 migrationInitPath = properties.get("migration.migrationInitPath", migrationInitPath); 433 modelPath = properties.get("migration.modelPath", modelPath); 434 applyPrefix = properties.get("migration.applyPrefix", applyPrefix); 435 applySuffix = properties.get("migration.applySuffix", applySuffix); 436 modelSuffix = properties.get("migration.modelSuffix", modelSuffix); 437 438 platform = properties.getEnum(Platform.class, "migration.platform", platform); 439 patchInsertOn = properties.get("migration.patchInsertOn", patchInsertOn); 440 patchResetChecksumOn = properties.get("migration.patchResetChecksumOn", patchResetChecksumOn); 441 442 runMigration = properties.getBoolean("migration.run", runMigration); 443 metaTable = properties.get("migration.metaTable", metaTable); 444 runPlaceholders = properties.get("migration.placeholders", runPlaceholders); 445 dbSchema = properties.get("migration.dbSchema", dbSchema); 446 447 //Do not set user and pass from "datasource.db.username" 448 //There is a null test in MigrationRunner::getConnection to handle this 449 //String adminUser = properties.get("datasource." + serverName + ".username", dbUsername); 450 String adminUser = properties.get("datasource." + serverName + ".adminusername", dbUsername); 451 dbUsername = properties.get("migration.dbusername", adminUser); 452 453 //String adminPwd = properties.get("datasource." + serverName + ".password", dbPassword); 454 String adminPwd = properties.get("datasource." + serverName + ".adminpassword", dbPassword); 455 dbPassword = properties.get("migration.dbpassword", adminPwd); 456 ddlHeader = properties.get("ddl.header", ddlHeader); 457 } 458 459 /** 460 * Return the system or environment property. 461 */ 462 protected String readEnvironment(String key) { 463 464 String val = System.getProperty(key); 465 if (val == null) { 466 val = System.getenv(key); 467 } 468 return val; 469 } 470 471 /** 472 * Return true if the string is null or empty. 473 */ 474 protected boolean isEmpty(String val) { 475 return val == null || val.trim().isEmpty(); 476 } 477 478 /** 479 * Create the MigrationRunner to run migrations if necessary. 480 */ 481 public MigrationRunner createRunner(ClassLoader classLoader, Properties properties) { 482 483 runnerConfig.setMetaTable(metaTable); 484 runnerConfig.setApplySuffix(applySuffix); 485 runnerConfig.setMigrationPath(migrationPath); 486 runnerConfig.setMigrationInitPath(migrationInitPath); 487 runnerConfig.setRunPlaceholderMap(runPlaceholderMap); 488 runnerConfig.setRunPlaceholders(runPlaceholders); 489 runnerConfig.setDbUsername(getDbUsername()); 490 runnerConfig.setDbPassword(getDbPassword()); 491 runnerConfig.setDbSchema(getDbSchema()); 492 if (defaultDbSchema) { 493 runnerConfig.setSetCurrentSchema(false); 494 } 495 runnerConfig.setClassLoader(classLoader); 496 if (patchInsertOn != null) { 497 runnerConfig.setPatchInsertOn(patchInsertOn); 498 } 499 if (patchResetChecksumOn != null) { 500 runnerConfig.setPatchResetChecksumOn(patchResetChecksumOn); 501 } 502 if (properties != null) { 503 runnerConfig.load(properties); 504 } 505 return new MigrationRunner(runnerConfig); 506 } 507}