001package com.avaje.ebean.config; 002 003import com.avaje.ebean.EbeanServer; 004import com.avaje.ebean.config.dbplatform.DbPlatformName; 005import com.avaje.ebean.dbmigration.DbMigration; 006import org.avaje.dbmigration.MigrationConfig; 007import org.avaje.dbmigration.MigrationRunner; 008import org.slf4j.Logger; 009import org.slf4j.LoggerFactory; 010 011import java.util.Map; 012 013/** 014 * Configuration for the DB migration processing. 015 */ 016public class DbMigrationConfig { 017 018 protected static final Logger logger = LoggerFactory.getLogger(DbMigrationConfig.class); 019 020 /** 021 * The database platform to generate migration DDL for. 022 */ 023 protected DbPlatformName platform; 024 025 /** 026 * Set to true if the DB migration should be generated on server start. 027 */ 028 protected boolean generate; 029 030 /** 031 * The migration version name (typically FlywayDb compatible). 032 * <p> 033 * Example: 1.1.1_2 034 * <p> 035 * The version is expected to be the combination of the current pom version plus 036 * a 'feature' id. The combined version must be unique and ordered to work with 037 * FlywayDb so each developer sets a unique version so that the migration script 038 * generated is unique (typically just prior to being submitted as a merge request). 039 */ 040 protected String version; 041 042 /** 043 * Description text that can be appended to the version to become the ddl script file name. 044 * <p> 045 * So if the name is "a foo table" then the ddl script file could be: 046 * "1.1.1_2__a-foo-table.sql" 047 * <p> 048 * When the DB migration relates to a git feature (merge request) then this description text 049 * is a short description of the feature. 050 */ 051 protected String name; 052 053 /** 054 * Resource path for the migration xml and sql. 055 */ 056 protected String migrationPath = "dbmigration"; 057 058 /** 059 * Subdirectory the model xml files go into. 060 */ 061 protected String modelPath = "model"; 062 063 protected String applySuffix = ".sql"; 064 065 /** 066 * Set this to "V" to be compatible with FlywayDB. 067 */ 068 protected String applyPrefix = ""; 069 070 protected String modelSuffix = ".model.xml"; 071 072 protected boolean includeGeneratedFileComment; 073 074 /** 075 * The version of a pending drop that should be generated as the next migration. 076 */ 077 protected String generatePendingDrop; 078 079 /** 080 * For running migration the DB table that holds migration execution status. 081 */ 082 protected String metaTable = "db_migration"; 083 084 /** 085 * Flag set to true means to run any outstanding migrations on startup. 086 */ 087 protected boolean runMigration; 088 089 /** 090 * Comma and equals delimited key/value placeholders to replace in DDL scripts. 091 */ 092 protected String runPlaceholders; 093 094 /** 095 * Map of key/value placeholders to replace in DDL scripts. 096 */ 097 protected Map<String,String> runPlaceholderMap; 098 099 /** 100 * DB user used to run the DB migration. 101 */ 102 protected String dbUsername; 103 104 /** 105 * DB password used to run the DB migration. 106 */ 107 protected String dbPassword; 108 109 /** 110 * Return the DB platform to generate migration DDL for. 111 * 112 * We typically need to explicitly specify this as migration can often be generated 113 * when running against H2. 114 */ 115 public DbPlatformName getPlatform() { 116 return platform; 117 } 118 119 /** 120 * Set the DB platform to generate migration DDL for. 121 */ 122 public void setPlatform(DbPlatformName platform) { 123 this.platform = platform; 124 } 125 126 /** 127 * Return the resource path for db migrations. 128 */ 129 public String getMigrationPath() { 130 return migrationPath; 131 } 132 133 /** 134 * Set the resource path for db migrations. 135 * <p> 136 * The default of "dbmigration" is reasonable in most cases. You may look to set this 137 * to be something like "dbmigration/myapp" where myapp gives it a unique resource path 138 * in the case there are multiple EbeanServer applications in the single classpath. 139 * </p> 140 */ 141 public void setMigrationPath(String migrationPath) { 142 this.migrationPath = migrationPath; 143 } 144 145 /** 146 * Return the relative path for the model files (defaults to model). 147 */ 148 public String getModelPath() { 149 return modelPath; 150 } 151 152 /** 153 * Set the relative path for the model files. 154 */ 155 public void setModelPath(String modelPath) { 156 this.modelPath = modelPath; 157 } 158 159 /** 160 * Return the model suffix (defaults to model.xml) 161 */ 162 public String getModelSuffix() { 163 return modelSuffix; 164 } 165 166 /** 167 * Set the model suffix. 168 */ 169 public void setModelSuffix(String modelSuffix) { 170 this.modelSuffix = modelSuffix; 171 } 172 173 /** 174 * Return the apply script suffix (defaults to sql). 175 */ 176 public String getApplySuffix() { 177 return applySuffix; 178 } 179 180 /** 181 * Set the apply script suffix (defaults to sql). 182 */ 183 public void setApplySuffix(String applySuffix) { 184 this.applySuffix = applySuffix; 185 } 186 187 /** 188 * Return the apply prefix. 189 */ 190 public String getApplyPrefix() { 191 return applyPrefix; 192 } 193 194 /** 195 * Set the apply prefix. This might be set to "V" for use with FlywayDB. 196 */ 197 public void setApplyPrefix(String applyPrefix) { 198 this.applyPrefix = applyPrefix; 199 } 200 201 /** 202 * Return true if the generated file comment should be included. 203 */ 204 public boolean isIncludeGeneratedFileComment() { 205 return includeGeneratedFileComment; 206 } 207 208 /** 209 * Set to true if the generated file comment should be included. 210 */ 211 public void setIncludeGeneratedFileComment(boolean includeGeneratedFileComment) { 212 this.includeGeneratedFileComment = includeGeneratedFileComment; 213 } 214 215 /** 216 * Return the migration version (or "next") to generate pending drops for. 217 */ 218 public String getGeneratePendingDrop() { 219 return generatePendingDrop; 220 } 221 222 /** 223 * Set the migration version (or "next") to generate pending drops for. 224 */ 225 public void setGeneratePendingDrop(String generatePendingDrop) { 226 this.generatePendingDrop = generatePendingDrop; 227 } 228 229 /** 230 * Set the migration version. 231 * <p> 232 * Note that version set via System property or environment variable <code>ddl.migration.version</code> takes precedence. 233 */ 234 public void setVersion(String version) { 235 this.version = version; 236 } 237 238 /** 239 * Set the migration name. 240 * <p> 241 * Note that name set via System property or environment variable <code>ddl.migration.name</code> takes precedence. 242 */ 243 public void setName(String name) { 244 this.name = name; 245 } 246 247 /** 248 * Return the table name that holds the migration run details 249 * (used by DB Migration runner only). 250 */ 251 public String getMetaTable() { 252 return metaTable; 253 } 254 255 /** 256 * Set the table name that holds the migration run details 257 * (used by DB Migration runner only). 258 */ 259 public void setMetaTable(String metaTable) { 260 this.metaTable = metaTable; 261 } 262 263 /** 264 * Return a comma and equals delimited placeholders that are substituted in SQL scripts when running migration 265 * (used by DB Migration runner only). 266 */ 267 public String getRunPlaceholders() { 268 // environment properties take precedence 269 String placeholders = readEnvironment("ddl.migration.placeholders"); 270 if (placeholders != null) { 271 return placeholders; 272 } 273 return runPlaceholders; 274 } 275 276 /** 277 * Set a comma and equals delimited placeholders that are substituted in SQL scripts when running migration 278 * (used by DB Migration runner only). 279 */ 280 public void setRunPlaceholders(String runPlaceholders) { 281 this.runPlaceholders = runPlaceholders; 282 } 283 284 /** 285 * Return a map of placeholder values that are substituted in SQL scripts when running migration 286 * (used by DB Migration runner only). 287 */ 288 public Map<String, String> getRunPlaceholderMap() { 289 return runPlaceholderMap; 290 } 291 292 /** 293 * Set a map of placeholder values that are substituted when running migration 294 * (used by DB Migration runner only). 295 */ 296 public void setRunPlaceholderMap(Map<String, String> runPlaceholderMap) { 297 this.runPlaceholderMap = runPlaceholderMap; 298 } 299 300 /** 301 * Return true if the DB migration should be run on startup. 302 */ 303 public boolean isRunMigration() { 304 // environment properties take precedence 305 String run = readEnvironment("ddl.migration.run"); 306 if (run != null) { 307 return "true".equalsIgnoreCase(run.trim()); 308 } 309 return runMigration; 310 } 311 312 /** 313 * Set to true to run the DB migration on startup. 314 */ 315 public void setRunMigration(boolean runMigration) { 316 this.runMigration = runMigration; 317 } 318 319 /** 320 * Return the DB username to use for running DB migrations. 321 */ 322 public String getDbUsername() { 323 // environment properties take precedence 324 String user = readEnvironment("ddl.migration.user"); 325 if (user != null) { 326 return user; 327 } 328 return dbUsername; 329 } 330 331 /** 332 * Set the DB username to use for running DB migrations. 333 */ 334 public void setDbUsername(String dbUsername) { 335 this.dbUsername = dbUsername; 336 } 337 338 /** 339 * Return the DB password to use for running DB migrations. 340 */ 341 public String getDbPassword() { 342 String user = readEnvironment("ddl.migration.password"); 343 if (user != null) { 344 return user; 345 } 346 return dbPassword; 347 } 348 349 /** 350 * Set the DB password to use for running DB migrations. 351 */ 352 public void setDbPassword(String dbPassword) { 353 this.dbPassword = dbPassword; 354 } 355 356 /** 357 * Load the settings from the PropertiesWrapper. 358 */ 359 public void loadSettings(PropertiesWrapper properties, String serverName) { 360 361 migrationPath = properties.get("migration.migrationPath", migrationPath); 362 modelPath = properties.get("migration.modelPath", modelPath); 363 applyPrefix = properties.get("migration.applyPrefix", applyPrefix); 364 applySuffix = properties.get("migration.applySuffix", applySuffix); 365 modelSuffix = properties.get("migration.modelSuffix", modelSuffix); 366 includeGeneratedFileComment = properties.getBoolean("migration.includeGeneratedFileComment", includeGeneratedFileComment); 367 generatePendingDrop = properties.get("migration.generatePendingDrop", generatePendingDrop); 368 369 platform = properties.getEnum(DbPlatformName.class, "migration.platform", platform); 370 371 generate = properties.getBoolean("migration.generate", generate); 372 version = properties.get("migration.version", version); 373 name = properties.get("migration.name", name); 374 375 runMigration = properties.getBoolean("migration.run", runMigration); 376 metaTable = properties.get("migration.metaTable", metaTable); 377 runPlaceholders = properties.get("migration.placeholders", runPlaceholders); 378 379 String adminUser = properties.get("datasource."+serverName+".username", dbUsername); 380 adminUser = properties.get("datasource."+serverName+".adminusername", adminUser); 381 dbUsername = properties.get("migration.dbusername", adminUser); 382 383 String adminPwd = properties.get("datasource."+serverName+".password", dbPassword); 384 adminPwd = properties.get("datasource."+serverName+".adminpassword", adminPwd); 385 dbPassword = properties.get("migration.dbpassword", adminPwd); 386 } 387 388 /** 389 * Return true if the migration should be generated. 390 * <p> 391 * It is expected that when an environment variable <code>ddl.migration.enabled</code> 392 * is set to <code>true</code> then the DB migration will generate the migration DDL. 393 * </p> 394 */ 395 public boolean isGenerateOnStart() { 396 397 // environment properties take precedence 398 String envGenerate = readEnvironment("ddl.migration.generate"); 399 if (envGenerate != null) { 400 return "true".equalsIgnoreCase(envGenerate.trim()); 401 } 402 return generate; 403 } 404 405 /** 406 * Called by EbeanServer on start. 407 * 408 * <p> 409 * If enabled this generates the migration xml and DDL scripts. 410 * </p> 411 */ 412 public void generateOnStart(EbeanServer server) { 413 414 if (isGenerateOnStart()) { 415 if (platform == null) { 416 logger.warn("No platform set for migration DDL generation"); 417 } else { 418 // generate the migration xml and platform specific DDL 419 DbMigration migration = new DbMigration(server); 420 migration.setPlatform(platform); 421 try { 422 migration.generateMigration(); 423 } catch (Exception e) { 424 throw new RuntimeException("Error generating DB migration", e); 425 } 426 } 427 } 428 } 429 430 /** 431 * Return the migration version (typically FlywayDb compatible). 432 * <p> 433 * Example: 1.1.1_2 434 * <p> 435 * The version is expected to be the combination of the current pom version plus 436 * a 'feature' id. The combined version must be unique and ordered to work with 437 * FlywayDb so each developer sets a unique version so that the migration script 438 * generated is unique (typically just prior to being submitted as a merge request). 439 */ 440 public String getVersion() { 441 String envVersion = readEnvironment("ddl.migration.version"); 442 if (!isEmpty(envVersion)) { 443 return envVersion.trim(); 444 } 445 return version; 446 } 447 448 /** 449 * Return the migration name which is short description text that can be appended to 450 * the migration version to become the ddl script file name. 451 * <p> 452 * So if the name is "a foo table" then the ddl script file could be: 453 * "1.1.1_2__a-foo-table.sql" 454 * </p> 455 * <p> 456 * When the DB migration relates to a git feature (merge request) then this description text 457 * is a short description of the feature. 458 * </p> 459 */ 460 public String getName() { 461 String envName = readEnvironment("ddl.migration.name"); 462 if (!isEmpty(envName)) { 463 return envName.trim(); 464 } 465 return name; 466 } 467 468 /** 469 * Return the system or environment property. 470 */ 471 protected String readEnvironment(String key) { 472 473 String val = System.getProperty(key); 474 if (val == null) { 475 val = System.getenv(key); 476 } 477 return val; 478 } 479 480 /** 481 * Return true if the string is null or empty. 482 */ 483 protected boolean isEmpty(String val) { 484 return val == null || val.trim().isEmpty(); 485 } 486 487 /** 488 * Create the MigrationRunner to run migrations if necessary. 489 */ 490 public MigrationRunner createRunner(ClassLoader classLoader) { 491 492 MigrationConfig runnerConfig = new MigrationConfig(); 493 runnerConfig.setMetaTable(metaTable); 494 runnerConfig.setApplySuffix(applySuffix); 495 runnerConfig.setMigrationPath(migrationPath); 496 runnerConfig.setRunPlaceholderMap(runPlaceholderMap); 497 runnerConfig.setRunPlaceholders(runPlaceholders); 498 runnerConfig.setDbUsername(getDbUsername()); 499 runnerConfig.setDbPassword(getDbPassword()); 500 runnerConfig.setClassLoader(classLoader); 501 return new MigrationRunner(runnerConfig); 502 } 503}