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.slf4j.Logger;
007import org.slf4j.LoggerFactory;
008
009/**
010 * Configuration for the DB migration processing.
011 */
012public class DbMigrationConfig {
013
014  protected static final Logger logger = LoggerFactory.getLogger(DbMigrationConfig.class);
015
016  /**
017   * The database platform to generate migration DDL for.
018   */
019  protected DbPlatformName platform;
020
021  /**
022   * Set to true if the DB migration should be generated on server start.
023   */
024  protected boolean generate;
025
026  /**
027   * The migration version name (typically FlywayDb compatible).
028   * <p>
029   * Example: 1.1.1_2
030   * <p>
031   * The version is expected to be the combination of the current pom version plus
032   * a 'feature' id. The combined version must be unique and ordered to work with
033   * FlywayDb so each developer sets a unique version so that the migration script
034   * generated is unique (typically just prior to being submitted as a merge request).
035   */
036  protected String version;
037
038  /**
039   * Description text that can be appended to the version to become the ddl script file name.
040   * <p>
041   * So if the name is "a foo table" then the ddl script file could be:
042   * "1.1.1_2__a-foo-table.sql"
043   * <p>
044   * When the DB migration relates to a git feature (merge request) then this description text
045   * is a short description of the feature.
046   */
047  protected String name;
048
049  /**
050   * Resource path for the migration xml and sql.
051   */
052  protected String migrationPath = "dbmigration";
053
054  /**
055   * Subdirectory the model xml files go into.
056   */
057  protected String modelPath = "model";
058
059  protected String applySuffix = ".sql";
060
061  protected String modelSuffix = ".model.xml";
062
063  protected boolean includeGeneratedFileComment;
064
065  /**
066   * The version of a pending drop that should be generated as the next migration.
067   */
068  protected String generatePendingDrop;
069
070  /**
071   * Return the DB platform to generate migration DDL for.
072   *
073   * We typically need to explicitly specify this as migration can often be generated
074   * when running against H2.
075   */
076  public DbPlatformName getPlatform() {
077    return platform;
078  }
079
080  /**
081   * Set the DB platform to generate migration DDL for.
082   */
083  public void setPlatform(DbPlatformName platform) {
084    this.platform = platform;
085  }
086
087  /**
088   * Return the resource path for db migrations.
089   */
090  public String getMigrationPath() {
091    return migrationPath;
092  }
093
094  /**
095   * Set the resource path for db migrations.
096   * <p>
097   * The default of "dbmigration" is reasonable in most cases. You may look to set this
098   * to be something like "dbmigration/myapp" where myapp gives it a unique resource path
099   * in the case there are multiple EbeanServer applications in the single classpath.
100   * </p>
101   */
102  public void setMigrationPath(String migrationPath) {
103    this.migrationPath = migrationPath;
104  }
105
106  /**
107   * Return the relative path for the model files (defaults to model).
108   */
109  public String getModelPath() {
110    return modelPath;
111  }
112
113  /**
114   * Set the relative path for the model files.
115   */
116  public void setModelPath(String modelPath) {
117    this.modelPath = modelPath;
118  }
119
120  /**
121   * Return the model suffix (defaults to model.xml)
122   */
123  public String getModelSuffix() {
124    return modelSuffix;
125  }
126
127  /**
128   * Set the model suffix.
129   */
130  public void setModelSuffix(String modelSuffix) {
131    this.modelSuffix = modelSuffix;
132  }
133
134  /**
135   * Return the apply script suffix (defaults to sql).
136   */
137  public String getApplySuffix() {
138    return applySuffix;
139  }
140
141  /**
142   * Set the apply script suffix (defaults to sql).
143   */
144  public void setApplySuffix(String applySuffix) {
145    this.applySuffix = applySuffix;
146  }
147
148  /**
149   * Return true if the generated file comment should be included.
150   */
151  public boolean isIncludeGeneratedFileComment() {
152    return includeGeneratedFileComment;
153  }
154
155  /**
156   * Set to true if the generated file comment should be included.
157   */
158  public void setIncludeGeneratedFileComment(boolean includeGeneratedFileComment) {
159    this.includeGeneratedFileComment = includeGeneratedFileComment;
160  }
161
162  /**
163   * Return the migration version (or "next") to generate pending drops for.
164   */
165  public String getGeneratePendingDrop() {
166    return generatePendingDrop;
167  }
168
169  /**
170   * Set the migration version (or "next") to generate pending drops for.
171   */
172  public void setGeneratePendingDrop(String generatePendingDrop) {
173    this.generatePendingDrop = generatePendingDrop;
174  }
175
176  /**
177   * Set the migration version.
178   * <p>
179   * Note that version set via System property or environment variable <code>ddl.migration.version</code> takes precedence.
180   */
181  public void setVersion(String version) {
182    this.version = version;
183  }
184
185  /**
186   * Set the migration name.
187   * <p>
188   * Note that name set via System property or environment variable <code>ddl.migration.name</code> takes precedence.
189   */
190  public void setName(String name) {
191    this.name = name;
192  }
193
194  /**
195   * Set the model, rollback and drop paths to be empty such that all the migration files are generated
196   * into a single directory.
197   */
198  public void singleDirectory() {
199    this.modelPath = "";
200  }
201
202  /**
203   * Load the settings from the PropertiesWrapper.
204   */
205  public void loadSettings(PropertiesWrapper properties) {
206
207    migrationPath = properties.get("migration.migrationPath", migrationPath);
208    if (properties.getBoolean("migration.singleDirectory", false)) {
209      singleDirectory();
210    } else {
211      modelPath = properties.get("migration.modelPath", modelPath);
212    }
213    applySuffix = properties.get("migration.applySuffix", applySuffix);
214    modelSuffix = properties.get("migration.modelSuffix", modelSuffix);
215    includeGeneratedFileComment = properties.getBoolean("migration.includeGeneratedFileComment", includeGeneratedFileComment);
216    generatePendingDrop = properties.get("migration.generatePendingDrop", generatePendingDrop);
217
218    platform = properties.getEnum(DbPlatformName.class, "migration.platform", platform);
219
220    generate = properties.getBoolean("migration.generate", generate);
221    version = properties.get("migration.version", version);
222    name = properties.get("migration.name", name);
223  }
224
225  /**
226   * Return true if the migration should be generated.
227   * <p>
228   * It is expected that when an environment variable <code>ddl.migration.enabled</code>
229   * is set to <code>true</code> then the DB migration will generate the migration DDL.
230   * </p>
231   */
232  public boolean isGenerateOnStart() {
233
234    // environment properties take precedence
235    String envGenerate = readEnvironment("ddl.migration.generate");
236    if (envGenerate != null) {
237      return "true".equalsIgnoreCase(envGenerate.trim());
238    }
239    return generate;
240  }
241
242  /**
243   * Called by EbeanServer on start.
244   *
245   * <p>
246   * If enabled this generates the migration xml and DDL scripts.
247   * </p>
248   */
249  public void generateOnStart(EbeanServer server) {
250
251    if (isGenerateOnStart()) {
252      if (platform == null) {
253        logger.warn("No platform set for migration DDL generation");
254      } else {
255        // generate the migration xml and platform specific DDL
256        DbMigration migration = new DbMigration(server);
257        migration.setPlatform(platform);
258        try {
259          migration.generateMigration();
260        } catch (Exception e) {
261          throw new RuntimeException("Error generating DB migration", e);
262        }
263      }
264    }
265  }
266
267  /**
268   * Return the migration version (typically FlywayDb compatible).
269   * <p>
270   * Example: 1.1.1_2
271   * <p>
272   * The version is expected to be the combination of the current pom version plus
273   * a 'feature' id. The combined version must be unique and ordered to work with
274   * FlywayDb so each developer sets a unique version so that the migration script
275   * generated is unique (typically just prior to being submitted as a merge request).
276   */
277  public String getVersion() {
278    String envVersion = readEnvironment("ddl.migration.version");
279    if (!isEmpty(envVersion)) {
280      return envVersion.trim();
281    }
282    return version;
283  }
284
285  /**
286   * Return the migration name which is short description text that can be appended to
287   * the migration version to become the ddl script file name.
288   * <p>
289   * So if the name is "a foo table" then the ddl script file could be:
290   * "1.1.1_2__a-foo-table.sql"
291   * </p>
292   * <p>
293   * When the DB migration relates to a git feature (merge request) then this description text
294   * is a short description of the feature.
295   * </p>
296   */
297  public String getName() {
298    String envName = readEnvironment("ddl.migration.name");
299    if (!isEmpty(envName)) {
300      return envName.trim();
301    }
302    return name;
303  }
304
305  /**
306   * Return the system or environment property.
307   */
308  protected String readEnvironment(String key) {
309
310    String val = System.getProperty(key);
311    if (val == null) {
312      val = System.getenv(key);
313    }
314    return val;
315  }
316
317  /**
318   * Return true if the string is null or empty.
319   */
320  protected boolean isEmpty(String val) {
321    return val == null || val.trim().isEmpty();
322  }
323
324}