001: package liquibase;
002:
003: import liquibase.ChangeSet;
004: import liquibase.DatabaseChangeLog;
005: import liquibase.DatabaseChangeLogLock;
006: import liquibase.FileOpener;
007: import liquibase.database.Database;
008: import liquibase.database.sql.UpdateStatement;
009: import liquibase.database.template.JdbcOutputTemplate;
010: import liquibase.database.template.JdbcTemplate;
011: import liquibase.exception.JDBCException;
012: import liquibase.exception.LiquibaseException;
013: import liquibase.exception.LockException;
014: import liquibase.lock.LockHandler;
015: import liquibase.log.LogFactory;
016: import liquibase.parser.ChangeLogIterator;
017: import liquibase.parser.ChangeLogParser;
018: import liquibase.parser.filter.*;
019: import liquibase.parser.visitor.*;
020: import liquibase.util.LiquibaseUtil;
021: import liquibase.util.StreamUtil;
022:
023: import java.io.File;
024: import java.io.IOException;
025: import java.io.PrintStream;
026: import java.io.Writer;
027: import java.text.DateFormat;
028: import java.util.Date;
029: import java.util.List;
030: import java.util.logging.Logger;
031: import java.util.logging.Level;
032:
033: /**
034: * Core LiquiBase facade.
035: * Although there are several ways of executing LiquiBase (Ant, command line, etc.) they are all wrappers around this class.
036: */
037: public class Liquibase {
038:
039: public static final String SHOULD_RUN_SYSTEM_PROPERTY = "liquibase.should.run";
040:
041: private String changeLogFile;
042: private FileOpener fileOpener;
043:
044: private Database database;
045: private Logger log;
046:
047: public Liquibase(String changeLogFile, FileOpener fileOpener,
048: Database database) {
049: log = LogFactory.getLogger();
050:
051: if (changeLogFile != null) {
052: this .changeLogFile = changeLogFile.replace('\\', '/'); //convert to standard / if usign absolute path on windows
053: }
054: this .fileOpener = fileOpener;
055:
056: this .database = database;
057: }
058:
059: public Database getDatabase() {
060: return database;
061: }
062:
063: /**
064: * FileOpener to use for accessing changelog files.
065: */
066: public FileOpener getFileOpener() {
067: return fileOpener;
068: }
069:
070: /**
071: * Use this function to override the current date/time function used to insert dates into the database.
072: * Especially useful when using an unsupported database.
073: */
074: public void setCurrentDateTimeFunction(
075: String currentDateTimeFunction) {
076: if (currentDateTimeFunction != null) {
077: this .database
078: .setCurrentDateTimeFunction(currentDateTimeFunction);
079: }
080: }
081:
082: public void update(String contexts) throws LiquibaseException {
083:
084: LockHandler lockHandler = LockHandler.getInstance(database);
085: lockHandler.waitForLock();
086:
087: try {
088: database.checkDatabaseChangeLogTable();
089:
090: DatabaseChangeLog changeLog = new ChangeLogParser().parse(
091: changeLogFile, fileOpener);
092: changeLog.validate(database);
093: ChangeLogIterator logIterator = new ChangeLogIterator(
094: changeLog, new ShouldRunChangeSetFilter(database),
095: new ContextChangeSetFilter(contexts),
096: new DbmsChangeSetFilter(database));
097:
098: logIterator.run(new UpdateVisitor(database));
099: } catch (LiquibaseException e) {
100: throw e;
101: } finally {
102: try {
103: lockHandler.releaseLock();
104: } catch (LockException e) {
105: log.log(Level.SEVERE, "Could not release lock", e);
106: }
107: }
108: }
109:
110: public void update(String contexts, Writer output)
111: throws LiquibaseException {
112: JdbcTemplate oldTemplate = database.getJdbcTemplate();
113: JdbcOutputTemplate outputTemplate = new JdbcOutputTemplate(
114: output, database);
115: database.setJdbcTemplate(outputTemplate);
116:
117: outputHeader("Update Database Script");
118:
119: update(contexts);
120:
121: try {
122: output.flush();
123: } catch (IOException e) {
124: throw new LiquibaseException(e);
125: }
126:
127: database.setJdbcTemplate(oldTemplate);
128: }
129:
130: public void update(int changesToApply, String contexts)
131: throws LiquibaseException {
132:
133: LockHandler lockHandler = LockHandler.getInstance(database);
134: lockHandler.waitForLock();
135:
136: try {
137: database.checkDatabaseChangeLogTable();
138:
139: DatabaseChangeLog changeLog = new ChangeLogParser().parse(
140: changeLogFile, fileOpener);
141: changeLog.validate(database);
142:
143: ChangeLogIterator logIterator = new ChangeLogIterator(
144: changeLog, new ShouldRunChangeSetFilter(database),
145: new ContextChangeSetFilter(contexts),
146: new DbmsChangeSetFilter(database),
147: new CountChangeSetFilter(changesToApply));
148:
149: logIterator.run(new UpdateVisitor(database));
150: } finally {
151: lockHandler.releaseLock();
152: }
153: }
154:
155: public void update(int changesToApply, String contexts,
156: Writer output) throws LiquibaseException {
157: JdbcTemplate oldTemplate = database.getJdbcTemplate();
158: JdbcOutputTemplate outputTemplate = new JdbcOutputTemplate(
159: output, database);
160: database.setJdbcTemplate(outputTemplate);
161:
162: outputHeader("Update " + changesToApply
163: + " Change Sets Database Script");
164:
165: update(changesToApply, contexts);
166:
167: try {
168: output.flush();
169: } catch (IOException e) {
170: throw new LiquibaseException(e);
171: }
172:
173: database.setJdbcTemplate(oldTemplate);
174: }
175:
176: private void outputHeader(String message) throws JDBCException {
177: database
178: .getJdbcTemplate()
179: .comment(
180: "*********************************************************************");
181: database.getJdbcTemplate().comment(message);
182: database
183: .getJdbcTemplate()
184: .comment(
185: "*********************************************************************");
186: database.getJdbcTemplate().comment(
187: "Change Log: " + changeLogFile);
188: database.getJdbcTemplate().comment(
189: "Ran at: "
190: + DateFormat.getDateTimeInstance(
191: DateFormat.SHORT, DateFormat.SHORT)
192: .format(new Date()));
193: database.getJdbcTemplate().comment(
194: "Against: " + getDatabase().getConnectionUsername()
195: + "@" + getDatabase().getConnectionURL());
196: database.getJdbcTemplate()
197: .comment(
198: "LiquiBase version: "
199: + LiquibaseUtil.getBuildVersion());
200: database.getJdbcTemplate().comment(
201: "*********************************************************************"
202: + StreamUtil.getLineSeparator());
203: }
204:
205: public void rollback(int changesToRollback, String contexts,
206: Writer output) throws LiquibaseException {
207: JdbcTemplate oldTemplate = database.getJdbcTemplate();
208: database.setJdbcTemplate(new JdbcOutputTemplate(output,
209: database));
210:
211: outputHeader("Rollback " + changesToRollback
212: + " Change(s) Script");
213:
214: rollback(changesToRollback, contexts);
215:
216: try {
217: output.flush();
218: } catch (IOException e) {
219: throw new LiquibaseException(e);
220: }
221: database.setJdbcTemplate(oldTemplate);
222: }
223:
224: public void rollback(int changesToRollback, String contexts)
225: throws LiquibaseException {
226: LockHandler lockHandler = LockHandler.getInstance(database);
227: lockHandler.waitForLock();
228:
229: try {
230: database.checkDatabaseChangeLogTable();
231:
232: DatabaseChangeLog changeLog = new ChangeLogParser().parse(
233: changeLogFile, fileOpener);
234: changeLog.validate(database);
235:
236: ChangeLogIterator logIterator = new ChangeLogIterator(
237: changeLog, new AlreadyRanChangeSetFilter(database
238: .getRanChangeSetList()),
239: new ContextChangeSetFilter(contexts),
240: new DbmsChangeSetFilter(database),
241: new CountChangeSetFilter(changesToRollback));
242:
243: logIterator.run(new RollbackVisitor(database));
244: } finally {
245: try {
246: lockHandler.releaseLock();
247: } catch (LockException e) {
248: log.log(Level.SEVERE, "Error releasing lock", e);
249: }
250: }
251: }
252:
253: public void rollback(String tagToRollBackTo, String contexts,
254: Writer output) throws LiquibaseException {
255: JdbcTemplate oldTemplate = database.getJdbcTemplate();
256: database.setJdbcTemplate(new JdbcOutputTemplate(output,
257: database));
258:
259: outputHeader("Rollback to '" + tagToRollBackTo + "' Script");
260:
261: rollback(tagToRollBackTo, contexts);
262:
263: try {
264: output.flush();
265: } catch (IOException e) {
266: throw new LiquibaseException(e);
267: }
268: database.setJdbcTemplate(oldTemplate);
269: }
270:
271: public void rollback(String tagToRollBackTo, String contexts)
272: throws LiquibaseException {
273: LockHandler lockHandler = LockHandler.getInstance(database);
274: lockHandler.waitForLock();
275:
276: try {
277: database.checkDatabaseChangeLogTable();
278:
279: DatabaseChangeLog changeLog = new ChangeLogParser().parse(
280: changeLogFile, fileOpener);
281: changeLog.validate(database);
282:
283: ChangeLogIterator logIterator = new ChangeLogIterator(
284: changeLog, new AfterTagChangeSetFilter(
285: tagToRollBackTo, database
286: .getRanChangeSetList()),
287: new ContextChangeSetFilter(contexts),
288: new DbmsChangeSetFilter(database));
289:
290: logIterator.run(new RollbackVisitor(database));
291: } finally {
292: lockHandler.releaseLock();
293: }
294: }
295:
296: public void rollback(Date dateToRollBackTo, String contexts,
297: Writer output) throws LiquibaseException {
298: JdbcTemplate oldTemplate = database.getJdbcTemplate();
299: database.setJdbcTemplate(new JdbcOutputTemplate(output,
300: database));
301:
302: outputHeader("Rollback to " + dateToRollBackTo + " Script");
303:
304: rollback(dateToRollBackTo, contexts);
305:
306: try {
307: output.flush();
308: } catch (IOException e) {
309: throw new LiquibaseException(e);
310: }
311: database.setJdbcTemplate(oldTemplate);
312: }
313:
314: public void rollback(Date dateToRollBackTo, String contexts)
315: throws LiquibaseException {
316: LockHandler lockHandler = LockHandler.getInstance(database);
317: lockHandler.waitForLock();
318:
319: try {
320: database.checkDatabaseChangeLogTable();
321:
322: DatabaseChangeLog changeLog = new ChangeLogParser().parse(
323: changeLogFile, fileOpener);
324: changeLog.validate(database);
325:
326: ChangeLogIterator logIterator = new ChangeLogIterator(
327: changeLog, new ExecutedAfterChangeSetFilter(
328: dateToRollBackTo, database
329: .getRanChangeSetList()),
330: new ContextChangeSetFilter(contexts),
331: new DbmsChangeSetFilter(database));
332:
333: logIterator.run(new RollbackVisitor(database));
334: } finally {
335: lockHandler.releaseLock();
336: }
337: }
338:
339: public void changeLogSync(String contexts, Writer output)
340: throws LiquibaseException {
341:
342: JdbcOutputTemplate outputTemplate = new JdbcOutputTemplate(
343: output, database);
344: JdbcTemplate oldTemplate = database.getJdbcTemplate();
345: database.setJdbcTemplate(outputTemplate);
346:
347: outputHeader("SQL to add all changesets to database history table");
348:
349: changeLogSync(contexts);
350:
351: try {
352: output.flush();
353: } catch (IOException e) {
354: throw new LiquibaseException(e);
355: }
356:
357: database.setJdbcTemplate(oldTemplate);
358: }
359:
360: public void changeLogSync(String contexts)
361: throws LiquibaseException {
362: LockHandler lockHandler = LockHandler.getInstance(database);
363: lockHandler.waitForLock();
364:
365: try {
366: database.checkDatabaseChangeLogTable();
367:
368: DatabaseChangeLog changeLog = new ChangeLogParser().parse(
369: changeLogFile, fileOpener);
370: changeLog.validate(database);
371:
372: ChangeLogIterator logIterator = new ChangeLogIterator(
373: changeLog, new NotRanChangeSetFilter(database
374: .getRanChangeSetList()),
375: new ContextChangeSetFilter(contexts),
376: new DbmsChangeSetFilter(database));
377:
378: logIterator.run(new ChangeLogSyncVisitor(database));
379: } finally {
380: lockHandler.releaseLock();
381: }
382: }
383:
384: public void futureRollbackSQL(String contexts, Writer output)
385: throws LiquibaseException {
386: JdbcOutputTemplate outputTemplate = new JdbcOutputTemplate(
387: output, database);
388: JdbcTemplate oldTemplate = database.getJdbcTemplate();
389: database.setJdbcTemplate(outputTemplate);
390:
391: outputHeader("SQL to roll back currently unexecuted changes");
392:
393: LockHandler lockHandler = LockHandler.getInstance(database);
394: lockHandler.waitForLock();
395:
396: try {
397: database.checkDatabaseChangeLogTable();
398:
399: DatabaseChangeLog changeLog = new ChangeLogParser().parse(
400: changeLogFile, fileOpener);
401: changeLog.validate(database);
402:
403: ChangeLogIterator logIterator = new ChangeLogIterator(
404: changeLog, new NotRanChangeSetFilter(database
405: .getRanChangeSetList()),
406: new ContextChangeSetFilter(contexts),
407: new DbmsChangeSetFilter(database));
408:
409: logIterator.run(new RollbackVisitor(database));
410: } finally {
411: database.setJdbcTemplate(oldTemplate);
412: lockHandler.releaseLock();
413: }
414:
415: try {
416: output.flush();
417: } catch (IOException e) {
418: throw new LiquibaseException(e);
419: }
420:
421: }
422:
423: /**
424: * Drops all database objects owned by the current user.
425: */
426: public final void dropAll() throws JDBCException, LockException {
427: dropAll(getDatabase().getDefaultSchemaName());
428: }
429:
430: /**
431: * Drops all database objects owned by the current user.
432: */
433: public final void dropAll(String... schemas) throws JDBCException {
434: try {
435: LockHandler.getInstance(database).waitForLock();
436:
437: for (String schema : schemas) {
438: log.info("Dropping Database Objects in " + schema);
439: checkDatabaseChangeLogTable();
440: getDatabase().dropDatabaseObjects(schema);
441: checkDatabaseChangeLogTable();
442: log.finest("Objects dropped successfully");
443: }
444: } catch (JDBCException e) {
445: throw e;
446: } catch (Exception e) {
447: throw new JDBCException(e);
448: } finally {
449: try {
450: LockHandler.getInstance(database).releaseLock();
451: } catch (LockException e) {
452: log.severe("Unable to release lock: " + e.getMessage());
453: }
454: }
455: }
456:
457: /**
458: * 'Tags' the database for future rollback
459: */
460: public void tag(String tagString) throws JDBCException {
461: getDatabase().tag(tagString);
462: }
463:
464: public void checkDatabaseChangeLogTable() throws JDBCException {
465: getDatabase().checkDatabaseChangeLogTable();
466: getDatabase().checkDatabaseChangeLogLockTable();
467: }
468:
469: /**
470: * Returns true if it is "save" to migrate the database.
471: * Currently, "safe" is defined as running in an output-sql mode or against a database on localhost.
472: * It is fine to run LiquiBase against a "non-safe" database, the method is mainly used to determine if the user
473: * should be prompted before continuing.
474: */
475: public boolean isSafeToRunMigration() throws JDBCException {
476: return !getDatabase().getJdbcTemplate().executesStatements()
477: || getDatabase().getConnectionURL()
478: .indexOf("localhost") >= 0;
479: }
480:
481: /**
482: * Display change log lock information.
483: */
484: public DatabaseChangeLogLock[] listLocks() throws JDBCException,
485: IOException, LockException {
486: checkDatabaseChangeLogTable();
487:
488: return LockHandler.getInstance(getDatabase()).listLocks();
489: }
490:
491: public void reportLocks(PrintStream out) throws LockException,
492: IOException, JDBCException {
493: DatabaseChangeLogLock[] locks = listLocks();
494: out.println("Database change log locks for "
495: + getDatabase().getConnectionUsername() + "@"
496: + getDatabase().getConnectionURL());
497: if (locks.length == 0) {
498: out.println(" - No locks");
499: }
500: for (DatabaseChangeLogLock lock : locks) {
501: out.println(" - "
502: + lock.getLockedBy()
503: + " at "
504: + DateFormat.getDateTimeInstance().format(
505: lock.getLockGranted()));
506: }
507:
508: }
509:
510: public void forceReleaseLocks() throws LockException, IOException,
511: JDBCException {
512: checkDatabaseChangeLogTable();
513:
514: LockHandler.getInstance(getDatabase()).forceReleaseLock();
515: }
516:
517: public List<ChangeSet> listUnrunChangeSets(String contexts)
518: throws LiquibaseException {
519: LockHandler lockHandler = LockHandler.getInstance(database);
520: lockHandler.waitForLock();
521:
522: try {
523: database.checkDatabaseChangeLogTable();
524:
525: DatabaseChangeLog changeLog = new ChangeLogParser().parse(
526: changeLogFile, fileOpener);
527: changeLog.validate(database);
528:
529: ChangeLogIterator logIterator = new ChangeLogIterator(
530: changeLog, new ShouldRunChangeSetFilter(database),
531: new ContextChangeSetFilter(contexts),
532: new DbmsChangeSetFilter(database));
533:
534: ListVisitor visitor = new ListVisitor();
535: logIterator.run(visitor);
536: return visitor.getSeenChangeSets();
537: } finally {
538: lockHandler.releaseLock();
539: }
540: }
541:
542: public void reportStatus(boolean verbose, String contexts,
543: Writer out) throws LiquibaseException {
544: try {
545: List<ChangeSet> unrunChangeSets = listUnrunChangeSets(contexts);
546: out.append(String.valueOf(unrunChangeSets.size()));
547: out.append(" change sets have not been applied to ");
548: out.append(getDatabase().getConnectionUsername());
549: out.append("@");
550: out.append(getDatabase().getConnectionURL());
551: out.append(StreamUtil.getLineSeparator());
552: if (verbose) {
553: for (ChangeSet changeSet : unrunChangeSets) {
554: out.append(" ").append(
555: changeSet.toString(false)).append(
556: StreamUtil.getLineSeparator());
557: }
558: }
559:
560: out.flush();
561: } catch (IOException e) {
562: throw new LiquibaseException(e);
563: }
564:
565: }
566:
567: /**
568: * Sets checksums to null so they will be repopulated next run
569: */
570: public void clearCheckSums() throws LiquibaseException {
571: log.info("Clearing database change log checksums");
572: LockHandler lockHandler = LockHandler.getInstance(database);
573: lockHandler.waitForLock();
574:
575: try {
576: database.checkDatabaseChangeLogTable();
577:
578: UpdateStatement updateStatement = new UpdateStatement(
579: getDatabase().getDefaultSchemaName(), getDatabase()
580: .getDatabaseChangeLogTableName());
581: updateStatement.addNewColumnValue("MD5SUM", null);
582: getDatabase().getJdbcTemplate().execute(updateStatement);
583: getDatabase().commit();
584: } finally {
585: lockHandler.releaseLock();
586: }
587: }
588:
589: public void generateDocumentation(String outputDirectory)
590: throws LiquibaseException {
591: log.info("Generating Database Documentation");
592: LockHandler lockHandler = LockHandler.getInstance(database);
593: lockHandler.waitForLock();
594:
595: try {
596: database.checkDatabaseChangeLogTable();
597:
598: DatabaseChangeLog changeLog = new ChangeLogParser().parse(
599: changeLogFile, fileOpener);
600: changeLog.validate(database);
601:
602: ChangeLogIterator logIterator = new ChangeLogIterator(
603: changeLog, new DbmsChangeSetFilter(database));
604:
605: DBDocVisitor visitor = new DBDocVisitor(database);
606: logIterator.run(visitor);
607:
608: visitor.writeHTML(new File(outputDirectory), fileOpener);
609: } catch (IOException e) {
610: throw new LiquibaseException(e);
611: } finally {
612: lockHandler.releaseLock();
613: }
614:
615: // try {
616: // if (!LockHandler.getInstance(database).waitForLock()) {
617: // return;
618: // }
619: //
620: // DBDocChangeLogHandler changeLogHandler = new DBDocChangeLogHandler(outputDirectory, this, changeLogFile,fileOpener);
621: // runChangeLogs(changeLogHandler);
622: //
623: // changeLogHandler.writeHTML(this);
624: // } finally {
625: // releaseLock();
626: // }
627: }
628:
629: /**
630: * Checks changelogs for bad MD5Sums and preconditions before attempting a migration
631: */
632: public void validate() throws LiquibaseException {
633:
634: DatabaseChangeLog changeLog = new ChangeLogParser().parse(
635: changeLogFile, fileOpener);
636: changeLog.validate(database);
637: }
638: }
|