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 metaTable = "db_migration";
019
020  private String applySuffix = ".sql";
021
022  private String runPlaceholders;
023
024  private boolean skipChecksum;
025
026  private Map<String, String> runPlaceholderMap;
027
028  private ClassLoader classLoader;
029
030  private String dbUsername;
031  private String dbPassword;
032  private String dbDriver;
033  private String dbUrl;
034
035  private String dbSchema;
036  private boolean createSchemaIfNotExists;
037  private String platformName;
038
039  /**
040   * Versions that we want to insert into migration history without actually running.
041   */
042  private Set<String> patchInsertOn;
043
044  /**
045   * Versions that we want to update the checksum on without actually running.
046   */
047  private Set<String> patchResetChecksumOn;
048
049  /**
050   * Return the name of the migration table.
051   */
052  public String getMetaTable() {
053    return metaTable;
054  }
055
056  /**
057   * Set the name of the migration table.
058   */
059  public void setMetaTable(String metaTable) {
060    this.metaTable = metaTable;
061  }
062
063  /**
064   * Parse as comma delimited versions.
065   */
066  private Set<String> parseCommaDelimited(String versionsCommaDelimited) {
067    if (versionsCommaDelimited != null) {
068      Set<String> versions = new HashSet<>();
069      String[] split = versionsCommaDelimited.split(",");
070      for (String version : split) {
071        if (version.startsWith("R__")) {
072          version = version.substring(3);
073        }
074        versions.add(version);
075      }
076      return versions;
077    }
078    return null;
079  }
080
081  /**
082   * Set the migrations that should have their checksum reset as a comma delimited list.
083   */
084  public void setPatchResetChecksumOn(String versionsCommaDelimited) {
085    patchResetChecksumOn = parseCommaDelimited(versionsCommaDelimited);
086  }
087
088  /**
089   * Set the migrations that should have their checksum reset.
090   */
091  public void setPatchResetChecksumOn(Set<String> patchResetChecksumOn) {
092    this.patchResetChecksumOn = patchResetChecksumOn;
093  }
094
095  /**
096   * Return the migrations that should have their checksum reset.
097   */
098  public Set<String> getPatchResetChecksumOn() {
099    return patchResetChecksumOn;
100  }
101
102  /**
103   * Set the migrations that should not be run but inserted into history as if they have run.
104   */
105  public void setPatchInsertOn(String versionsCommaDelimited) {
106    patchInsertOn = parseCommaDelimited(versionsCommaDelimited);
107  }
108
109  /**
110   * Set the migrations that should not be run but inserted into history as if they have run.
111   * <p>
112   * This can be useful when we need to pull out DDL from a repeatable migration that should really
113   * only run once. We can pull out that DDL as a new migration and add it to history as if it had been
114   * run (we can only do this when we know it exists in all environments including production).
115   * </p>
116   */
117  public void setPatchInsertOn(Set<String> patchInsertOn) {
118    this.patchInsertOn = patchInsertOn;
119  }
120
121  /**
122   * Return the migrations that should not be run but inserted into history as if they have run.
123   */
124  public Set<String> getPatchInsertOn() {
125    return patchInsertOn;
126  }
127
128  /**
129   * Return true if checksum check should be skipped (during development).
130   */
131  public boolean isSkipChecksum() {
132    return skipChecksum;
133  }
134
135  /**
136   * Set to true to skip the checksum check.
137   * <p>
138   * This is intended for use during development only.
139   * </p>
140   */
141  public void setSkipChecksum(boolean skipChecksum) {
142    this.skipChecksum = skipChecksum;
143  }
144
145  /**
146   * Return a Comma and equals delimited key/value placeholders to replace in DDL scripts.
147   */
148  public String getRunPlaceholders() {
149    return runPlaceholders;
150  }
151
152  /**
153   * Set a Comma and equals delimited key/value placeholders to replace in DDL scripts.
154   */
155  public void setRunPlaceholders(String runPlaceholders) {
156    this.runPlaceholders = runPlaceholders;
157  }
158
159  /**
160   * Return a map of name/value pairs that can be expressions replaced in migration scripts.
161   */
162  public Map<String, String> getRunPlaceholderMap() {
163    return runPlaceholderMap;
164  }
165
166  /**
167   * Set a map of name/value pairs that can be expressions replaced in migration scripts.
168   */
169  public void setRunPlaceholderMap(Map<String, String> runPlaceholderMap) {
170    this.runPlaceholderMap = runPlaceholderMap;
171  }
172
173  /**
174   * Return the root path used to find migrations.
175   */
176  public String getMigrationPath() {
177    return migrationPath;
178  }
179
180  /**
181   * Set the root path used to find migrations.
182   */
183  public void setMigrationPath(String migrationPath) {
184    this.migrationPath = migrationPath;
185  }
186
187  /**
188   * Return the suffix for migration resources (defaults to .sql).
189   */
190  public String getApplySuffix() {
191    return applySuffix;
192  }
193
194  /**
195   * Set the suffix for migration resources.
196   */
197  public void setApplySuffix(String applySuffix) {
198    this.applySuffix = applySuffix;
199  }
200
201  /**
202   * Return the DB username.
203   * <p>
204   * Used when a Connection to run the migration is not supplied.
205   * </p>
206   */
207  public String getDbUsername() {
208    return dbUsername;
209  }
210
211  /**
212   * Set the DB username.
213   * <p>
214   * Used when a Connection to run the migration is not supplied.
215   * </p>
216   */
217  public void setDbUsername(String dbUsername) {
218    this.dbUsername = dbUsername;
219  }
220
221  /**
222   * Return the DB password.
223   * <p>
224   * Used when creating a Connection to run the migration.
225   * </p>
226   */
227  public String getDbPassword() {
228    return dbPassword;
229  }
230
231  /**
232   * Set the DB password.
233   * <p>
234   * Used when creating a Connection to run the migration.
235   * </p>
236   */
237  public void setDbPassword(String dbPassword) {
238    this.dbPassword = dbPassword;
239  }
240
241  /**
242   * Return the DB Driver.
243   * <p>
244   * Used when creating a Connection to run the migration.
245   * </p>
246   */
247  public String getDbDriver() {
248    return dbDriver;
249  }
250
251  /**
252   * Set the DB Driver.
253   * <p>
254   * Used when creating a Connection to run the migration.
255   * </p>
256   */
257  public void setDbDriver(String dbDriver) {
258    this.dbDriver = dbDriver;
259  }
260
261  /**
262   * Return the DB connection URL.
263   * <p>
264   * Used when creating a Connection to run the migration.
265   * </p>
266   */
267  public String getDbUrl() {
268    return dbUrl;
269  }
270
271  /**
272   * Set the DB connection URL.
273   * <p>
274   * Used when creating a Connection to run the migration.
275   * </p>
276   */
277  public void setDbUrl(String dbUrl) {
278    this.dbUrl = dbUrl;
279  }
280
281  /**
282   * Return the DB connection Schema.
283   * <p>
284   * Used when creating a Connection to run the migration.
285   * </p>
286   */
287  public String getDbSchema() {
288    return dbSchema;
289  }
290
291  /**
292   * Set the DB connection Schema.
293   * <p>
294   * Used when creating a Connection to run the migration.
295   * </p>
296   */
297  public void setDbSchema(String dbSchema) {
298    this.dbSchema = dbSchema;
299  }
300
301  /**
302   * Return true if migration should create the schema if it does not exist.
303   */
304  public boolean isCreateSchemaIfNotExists() {
305    return createSchemaIfNotExists;
306  }
307
308  /**
309   * Set to create Schema if it does not exist.
310   */
311  public void setCreateSchemaIfNotExists(boolean createSchemaIfNotExists) {
312    this.createSchemaIfNotExists = createSchemaIfNotExists;
313  }
314
315  /**
316   * Return the DB platform name (used for platform create table and select for update syntax).
317   */
318  public String getPlatformName() {
319    return platformName;
320  }
321
322  /**
323   * Set a DB platform name (to load specific create table and select for update syntax).
324   */
325  public void setPlatformName(String platformName) {
326    this.platformName = platformName;
327  }
328
329  /**
330   * Return the ClassLoader to use to load resources.
331   */
332  public ClassLoader getClassLoader() {
333    if (classLoader == null) {
334      classLoader = Thread.currentThread().getContextClassLoader();
335      if (classLoader == null) {
336        classLoader = this.getClass().getClassLoader();
337      }
338    }
339    return classLoader;
340  }
341
342  /**
343   * Set the ClassLoader to use when loading resources.
344   */
345  public void setClassLoader(ClassLoader classLoader) {
346    this.classLoader = classLoader;
347  }
348
349  /**
350   * Load configuration from standard properties.
351   */
352  public void load(Properties props) {
353
354    dbUsername = props.getProperty("dbmigration.username", dbUsername);
355    dbPassword = props.getProperty("dbmigration.password", dbPassword);
356    dbDriver = props.getProperty("dbmigration.driver", dbDriver);
357    dbUrl = props.getProperty("dbmigration.url", dbUrl);
358
359    String skip = props.getProperty("dbmigration.skipchecksum");
360    if (skip != null) {
361      skipChecksum = Boolean.parseBoolean(skip);
362    }
363    platformName = props.getProperty("dbmigration.platformName", platformName);
364    applySuffix = props.getProperty("dbmigration.applySuffix", applySuffix);
365    metaTable = props.getProperty("dbmigration.metaTable", metaTable);
366    migrationPath = props.getProperty("dbmigration.migrationPath", migrationPath);
367    runPlaceholders = props.getProperty("dbmigration.placeholders", runPlaceholders);
368  }
369
370  /**
371   * Create a Connection to the database using the configured driver, url, username etc.
372   * <p>
373   * Used when an existing DataSource or Connection is not supplied.
374   * </p>
375   */
376  public Connection createConnection() {
377
378    if (dbUsername == null) throw new MigrationException("Database username is null?");
379    if (dbPassword == null) throw new MigrationException("Database password is null?");
380    if (dbDriver == null) throw new MigrationException("Database Driver is null?");
381    if (dbUrl == null) throw new MigrationException("Database connection URL is null?");
382
383    loadDriver();
384
385    try {
386      Properties props = new Properties();
387      props.setProperty("user", dbUsername);
388      props.setProperty("password", dbPassword);
389      return DriverManager.getConnection(dbUrl, props);
390
391    } catch (SQLException e) {
392      throw new MigrationException("Error trying to create Connection", e);
393    }
394  }
395
396  private void loadDriver() {
397    try {
398      ClassLoader contextLoader = getClassLoader();
399      Class.forName(dbDriver, true, contextLoader);
400    } catch (Throwable e) {
401      throw new MigrationException("Problem loading Database Driver [" + dbDriver + "]: " + e.getMessage(), e);
402    }
403  }
404
405}