View Javadoc

1   package liquibase;
2   
3   import java.io.File;
4   import java.io.IOException;
5   import java.io.PrintStream;
6   import java.io.Writer;
7   import java.text.DateFormat;
8   import java.util.Date;
9   import java.util.List;
10  
11  import liquibase.changelog.ChangeLogIterator;
12  import liquibase.changelog.ChangeLogParameters;
13  import liquibase.changelog.ChangeSet;
14  import liquibase.changelog.DatabaseChangeLog;
15  import liquibase.changelog.RanChangeSet;
16  import liquibase.changelog.filter.AfterTagChangeSetFilter;
17  import liquibase.changelog.filter.AlreadyRanChangeSetFilter;
18  import liquibase.changelog.filter.ContextChangeSetFilter;
19  import liquibase.changelog.filter.CountChangeSetFilter;
20  import liquibase.changelog.filter.DbmsChangeSetFilter;
21  import liquibase.changelog.filter.ExecutedAfterChangeSetFilter;
22  import liquibase.changelog.filter.NotRanChangeSetFilter;
23  import liquibase.changelog.filter.ShouldRunChangeSetFilter;
24  import liquibase.changelog.visitor.ChangeLogSyncVisitor;
25  import liquibase.changelog.visitor.DBDocVisitor;
26  import liquibase.changelog.visitor.ListVisitor;
27  import liquibase.changelog.visitor.RollbackVisitor;
28  import liquibase.changelog.visitor.UpdateVisitor;
29  import liquibase.database.Database;
30  import liquibase.database.DatabaseConnection;
31  import liquibase.database.DatabaseFactory;
32  import liquibase.diff.Diff;
33  import liquibase.exception.DatabaseException;
34  import liquibase.exception.LiquibaseException;
35  import liquibase.exception.LockException;
36  import liquibase.executor.Executor;
37  import liquibase.executor.ExecutorService;
38  import liquibase.executor.LoggingExecutor;
39  import liquibase.lockservice.DatabaseChangeLogLock;
40  import liquibase.lockservice.LockService;
41  import liquibase.logging.LogFactory;
42  import liquibase.logging.Logger;
43  import liquibase.parser.ChangeLogParserFactory;
44  import liquibase.resource.ResourceAccessor;
45  import liquibase.statement.core.UpdateStatement;
46  import liquibase.util.LiquibaseUtil;
47  import liquibase.util.StreamUtil;
48  import liquibase.util.StringUtils;
49  
50  /**
51   * Core Liquibase facade. Although there are several ways of executing Liquibase (Ant, command line, etc.) they are all
52   * wrappers around this class.
53   */
54  public class Liquibase {
55  
56      public static final String SHOULD_RUN_SYSTEM_PROPERTY = "liquibase.should.run";
57  
58      private String changeLogFile;
59      private ResourceAccessor resourceAccessor;
60  
61      protected Database database;
62      private Logger log;
63  
64      private ChangeLogParameters changeLogParameters;
65  
66      public Liquibase(String changeLogFile, ResourceAccessor resourceAccessor, DatabaseConnection conn)
67              throws LiquibaseException {
68          this(changeLogFile, resourceAccessor, DatabaseFactory.getInstance().findCorrectDatabaseImplementation(conn));
69      }
70  
71      public Liquibase(String changeLogFile, ResourceAccessor resourceAccessor, Database database)
72              throws LiquibaseException {
73          log = LogFactory.getLogger();
74  
75          if (changeLogFile != null) {
76              this.changeLogFile = changeLogFile.replace('\\', '/'); // convert to standard / if usign absolute path on
77              // windows
78          }
79          this.resourceAccessor = resourceAccessor;
80  
81          changeLogParameters = new ChangeLogParameters(database);
82          setDatabase(database);
83  
84      }
85  
86      public ChangeLogParameters getChangeLogParameters() {
87          return changeLogParameters;
88      }
89  
90      public Database getDatabase() {
91          return database;
92      }
93  
94      private void setDatabase(Database database) throws DatabaseException {
95          this.database = database;
96          if (database != null) // Some tests use a null database
97              setDatabasePropertiesAsChangelogParameters(database);
98      }
99  
100     /**
101      * FileOpener to use for accessing changelog files.
102      */
103     public ResourceAccessor getFileOpener() {
104         return resourceAccessor;
105     }
106 
107     /**
108      * Use this function to override the current date/time function used to insert dates into the database. Especially
109      * useful when using an unsupported database.
110      */
111     public void setCurrentDateTimeFunction(String currentDateTimeFunction) {
112         if (currentDateTimeFunction != null) {
113             this.database.setCurrentDateTimeFunction(currentDateTimeFunction);
114         }
115     }
116 
117     public void update(String contexts) throws LiquibaseException {
118 
119         LockService lockService = LockService.getInstance(database);
120         lockService.waitForLock();
121 
122         changeLogParameters.setContexts(StringUtils.splitAndTrim(contexts, ","));
123 
124         try {
125             DatabaseChangeLog changeLog = ChangeLogParserFactory.getInstance()
126                     .getParser(changeLogFile, resourceAccessor)
127                     .parse(changeLogFile, changeLogParameters, resourceAccessor);
128 
129             checkDatabaseChangeLogTable(true, changeLog, contexts);
130 
131             changeLog.validate(database, contexts);
132             ChangeLogIterator changeLogIterator = getStandardChangelogIterator(contexts, changeLog);
133 
134             changeLogIterator.run(new UpdateVisitor(database), database);
135         } finally {
136             try {
137                 lockService.releaseLock();
138             } catch (LockException e) {
139                 log.severe("Could not release lock", e);
140             }
141         }
142     }
143 
144     private ChangeLogIterator getStandardChangelogIterator(String contexts, DatabaseChangeLog changeLog)
145             throws DatabaseException {
146         return new ChangeLogIterator(changeLog, new ShouldRunChangeSetFilter(database), new ContextChangeSetFilter(
147                 contexts), new DbmsChangeSetFilter(database));
148     }
149 
150     public void update(String contexts, Writer output) throws LiquibaseException {
151         changeLogParameters.setContexts(StringUtils.splitAndTrim(contexts, ","));
152 
153         Executor oldTemplate = ExecutorService.getInstance().getExecutor(database);
154         LoggingExecutor loggingExecutor = new LoggingExecutor(ExecutorService.getInstance().getExecutor(database),
155                 output, database);
156         ExecutorService.getInstance().setExecutor(database, loggingExecutor);
157 
158         outputHeader("Update Database Script");
159 
160         LockService lockService = LockService.getInstance(database);
161         lockService.waitForLock();
162 
163         try {
164 
165             update(contexts);
166 
167             output.flush();
168         } catch (IOException e) {
169             throw new LiquibaseException(e);
170         } finally {
171             lockService.releaseLock();
172         }
173 
174         ExecutorService.getInstance().setExecutor(database, oldTemplate);
175     }
176 
177     public void update(int changesToApply, String contexts) throws LiquibaseException {
178 
179         changeLogParameters.setContexts(StringUtils.splitAndTrim(contexts, ","));
180 
181         LockService lockService = LockService.getInstance(database);
182         lockService.waitForLock();
183 
184         try {
185 
186             DatabaseChangeLog changeLog = ChangeLogParserFactory.getInstance()
187                     .getParser(changeLogFile, resourceAccessor)
188                     .parse(changeLogFile, changeLogParameters, resourceAccessor);
189 
190             checkDatabaseChangeLogTable(true, changeLog, contexts);
191             changeLog.validate(database, contexts);
192 
193             ChangeLogIterator logIterator = new ChangeLogIterator(changeLog, new ShouldRunChangeSetFilter(database),
194                     new ContextChangeSetFilter(contexts), new DbmsChangeSetFilter(database), new CountChangeSetFilter(
195                             changesToApply));
196 
197             logIterator.run(new UpdateVisitor(database), database);
198         } finally {
199             lockService.releaseLock();
200         }
201     }
202 
203     public void update(int changesToApply, String contexts, Writer output) throws LiquibaseException {
204         changeLogParameters.setContexts(StringUtils.splitAndTrim(contexts, ","));
205 
206         Executor oldTemplate = ExecutorService.getInstance().getExecutor(database);
207         LoggingExecutor loggingExecutor = new LoggingExecutor(ExecutorService.getInstance().getExecutor(database),
208                 output, database);
209         ExecutorService.getInstance().setExecutor(database, loggingExecutor);
210 
211         outputHeader("Update " + changesToApply + " Change Sets Database Script");
212 
213         update(changesToApply, contexts);
214 
215         try {
216             output.flush();
217         } catch (IOException e) {
218             throw new LiquibaseException(e);
219         }
220 
221         ExecutorService.getInstance().setExecutor(database, oldTemplate);
222     }
223 
224     private void outputHeader(String message) throws DatabaseException {
225         Executor executor = ExecutorService.getInstance().getExecutor(database);
226         executor.comment("*********************************************************************");
227         executor.comment(message);
228         executor.comment("*********************************************************************");
229         executor.comment("Change Log: " + changeLogFile);
230         executor.comment("Ran at: "
231                 + DateFormat.getDateTimeInstance(DateFormat.SHORT, DateFormat.SHORT).format(new Date()));
232         executor.comment("Against: " + getDatabase().getConnection().getConnectionUserName() + "@"
233                 + getDatabase().getConnection().getURL());
234         executor.comment("Liquibase version: " + LiquibaseUtil.getBuildVersion());
235         executor.comment("*********************************************************************"
236                 + StreamUtil.getLineSeparator());
237     }
238 
239     public void rollback(int changesToRollback, String contexts, Writer output) throws LiquibaseException {
240         changeLogParameters.setContexts(StringUtils.splitAndTrim(contexts, ","));
241 
242         Executor oldTemplate = ExecutorService.getInstance().getExecutor(database);
243         ExecutorService.getInstance().setExecutor(database,
244                 new LoggingExecutor(ExecutorService.getInstance().getExecutor(database), output, database));
245 
246         outputHeader("Rollback " + changesToRollback + " Change(s) Script");
247 
248         rollback(changesToRollback, contexts);
249 
250         try {
251             output.flush();
252         } catch (IOException e) {
253             throw new LiquibaseException(e);
254         }
255         ExecutorService.getInstance().setExecutor(database, oldTemplate);
256     }
257 
258     public void rollback(int changesToRollback, String contexts) throws LiquibaseException {
259         changeLogParameters.setContexts(StringUtils.splitAndTrim(contexts, ","));
260 
261         LockService lockService = LockService.getInstance(database);
262         lockService.waitForLock();
263 
264         try {
265             DatabaseChangeLog changeLog = ChangeLogParserFactory.getInstance()
266                     .getParser(changeLogFile, resourceAccessor)
267                     .parse(changeLogFile, changeLogParameters, resourceAccessor);
268             checkDatabaseChangeLogTable(false, changeLog, contexts);
269 
270             changeLog.validate(database, contexts);
271 
272             ChangeLogIterator logIterator = new ChangeLogIterator(database.getRanChangeSetList(), changeLog,
273                     new AlreadyRanChangeSetFilter(database.getRanChangeSetList()),
274                     new ContextChangeSetFilter(contexts), new DbmsChangeSetFilter(database), new CountChangeSetFilter(
275                             changesToRollback));
276 
277             logIterator.run(new RollbackVisitor(database), database);
278         } finally {
279             try {
280                 lockService.releaseLock();
281             } catch (LockException e) {
282                 log.severe("Error releasing lock", e);
283             }
284         }
285     }
286 
287     public void rollback(String tagToRollBackTo, String contexts, Writer output) throws LiquibaseException {
288         changeLogParameters.setContexts(StringUtils.splitAndTrim(contexts, ","));
289 
290         Executor oldTemplate = ExecutorService.getInstance().getExecutor(database);
291         ExecutorService.getInstance().setExecutor(database,
292                 new LoggingExecutor(ExecutorService.getInstance().getExecutor(database), output, database));
293 
294         outputHeader("Rollback to '" + tagToRollBackTo + "' Script");
295 
296         rollback(tagToRollBackTo, contexts);
297 
298         try {
299             output.flush();
300         } catch (IOException e) {
301             throw new LiquibaseException(e);
302         }
303         ExecutorService.getInstance().setExecutor(database, oldTemplate);
304     }
305 
306     public void rollback(String tagToRollBackTo, String contexts) throws LiquibaseException {
307         changeLogParameters.setContexts(StringUtils.splitAndTrim(contexts, ","));
308 
309         LockService lockService = LockService.getInstance(database);
310         lockService.waitForLock();
311 
312         try {
313 
314             DatabaseChangeLog changeLog = ChangeLogParserFactory.getInstance()
315                     .getParser(changeLogFile, resourceAccessor)
316                     .parse(changeLogFile, changeLogParameters, resourceAccessor);
317             checkDatabaseChangeLogTable(false, changeLog, contexts);
318 
319             changeLog.validate(database, contexts);
320 
321             List<RanChangeSet> ranChangeSetList = database.getRanChangeSetList();
322             ChangeLogIterator logIterator = new ChangeLogIterator(ranChangeSetList, changeLog,
323                     new AfterTagChangeSetFilter(tagToRollBackTo, ranChangeSetList), new AlreadyRanChangeSetFilter(
324                             ranChangeSetList), new ContextChangeSetFilter(contexts), new DbmsChangeSetFilter(database));
325 
326             logIterator.run(new RollbackVisitor(database), database);
327         } finally {
328             lockService.releaseLock();
329         }
330     }
331 
332     public void rollback(Date dateToRollBackTo, String contexts, Writer output) throws LiquibaseException {
333         changeLogParameters.setContexts(StringUtils.splitAndTrim(contexts, ","));
334 
335         Executor oldTemplate = ExecutorService.getInstance().getExecutor(database);
336         ExecutorService.getInstance().setExecutor(database,
337                 new LoggingExecutor(ExecutorService.getInstance().getExecutor(database), output, database));
338 
339         outputHeader("Rollback to " + dateToRollBackTo + " Script");
340 
341         rollback(dateToRollBackTo, contexts);
342 
343         try {
344             output.flush();
345         } catch (IOException e) {
346             throw new LiquibaseException(e);
347         }
348         ExecutorService.getInstance().setExecutor(database, oldTemplate);
349     }
350 
351     public void rollback(Date dateToRollBackTo, String contexts) throws LiquibaseException {
352         changeLogParameters.setContexts(StringUtils.splitAndTrim(contexts, ","));
353 
354         LockService lockService = LockService.getInstance(database);
355         lockService.waitForLock();
356 
357         try {
358             DatabaseChangeLog changeLog = ChangeLogParserFactory.getInstance()
359                     .getParser(changeLogFile, resourceAccessor)
360                     .parse(changeLogFile, changeLogParameters, resourceAccessor);
361             checkDatabaseChangeLogTable(false, changeLog, contexts);
362             changeLog.validate(database, contexts);
363 
364             List<RanChangeSet> ranChangeSetList = database.getRanChangeSetList();
365             ChangeLogIterator logIterator = new ChangeLogIterator(ranChangeSetList, changeLog,
366                     new ExecutedAfterChangeSetFilter(dateToRollBackTo, ranChangeSetList),
367                     new AlreadyRanChangeSetFilter(ranChangeSetList), new ContextChangeSetFilter(contexts),
368                     new DbmsChangeSetFilter(database));
369 
370             logIterator.run(new RollbackVisitor(database), database);
371         } finally {
372             lockService.releaseLock();
373         }
374     }
375 
376     public void changeLogSync(String contexts, Writer output) throws LiquibaseException {
377         changeLogParameters.setContexts(StringUtils.splitAndTrim(contexts, ","));
378 
379         LoggingExecutor outputTemplate = new LoggingExecutor(ExecutorService.getInstance().getExecutor(database),
380                 output, database);
381         Executor oldTemplate = ExecutorService.getInstance().getExecutor(database);
382         ExecutorService.getInstance().setExecutor(database, outputTemplate);
383 
384         outputHeader("SQL to add all changesets to database history table");
385 
386         changeLogSync(contexts);
387 
388         try {
389             output.flush();
390         } catch (IOException e) {
391             throw new LiquibaseException(e);
392         }
393 
394         ExecutorService.getInstance().setExecutor(database, oldTemplate);
395     }
396 
397     public void changeLogSync(String contexts) throws LiquibaseException {
398         changeLogParameters.setContexts(StringUtils.splitAndTrim(contexts, ","));
399 
400         LockService lockService = LockService.getInstance(database);
401         lockService.waitForLock();
402 
403         try {
404             DatabaseChangeLog changeLog = ChangeLogParserFactory.getInstance()
405                     .getParser(changeLogFile, resourceAccessor)
406                     .parse(changeLogFile, changeLogParameters, resourceAccessor);
407             checkDatabaseChangeLogTable(true, changeLog, contexts);
408             changeLog.validate(database, contexts);
409 
410             ChangeLogIterator logIterator = new ChangeLogIterator(changeLog, new NotRanChangeSetFilter(
411                     database.getRanChangeSetList()), new ContextChangeSetFilter(contexts), new DbmsChangeSetFilter(
412                     database));
413 
414             logIterator.run(new ChangeLogSyncVisitor(database), database);
415         } finally {
416             lockService.releaseLock();
417         }
418     }
419 
420     public void markNextChangeSetRan(String contexts, Writer output) throws LiquibaseException {
421         changeLogParameters.setContexts(StringUtils.splitAndTrim(contexts, ","));
422 
423         LoggingExecutor outputTemplate = new LoggingExecutor(ExecutorService.getInstance().getExecutor(database),
424                 output, database);
425         Executor oldTemplate = ExecutorService.getInstance().getExecutor(database);
426         ExecutorService.getInstance().setExecutor(database, outputTemplate);
427 
428         outputHeader("SQL to add all changesets to database history table");
429 
430         markNextChangeSetRan(contexts);
431 
432         try {
433             output.flush();
434         } catch (IOException e) {
435             throw new LiquibaseException(e);
436         }
437 
438         ExecutorService.getInstance().setExecutor(database, oldTemplate);
439     }
440 
441     public void markNextChangeSetRan(String contexts) throws LiquibaseException {
442         changeLogParameters.setContexts(StringUtils.splitAndTrim(contexts, ","));
443 
444         LockService lockService = LockService.getInstance(database);
445         lockService.waitForLock();
446 
447         try {
448             DatabaseChangeLog changeLog = ChangeLogParserFactory.getInstance()
449                     .getParser(changeLogFile, resourceAccessor)
450                     .parse(changeLogFile, changeLogParameters, resourceAccessor);
451             checkDatabaseChangeLogTable(false, changeLog, contexts);
452             changeLog.validate(database, contexts);
453 
454             ChangeLogIterator logIterator = new ChangeLogIterator(changeLog, new NotRanChangeSetFilter(
455                     database.getRanChangeSetList()), new ContextChangeSetFilter(contexts), new DbmsChangeSetFilter(
456                     database), new CountChangeSetFilter(1));
457 
458             logIterator.run(new ChangeLogSyncVisitor(database), database);
459         } finally {
460             lockService.releaseLock();
461         }
462     }
463 
464     public void futureRollbackSQL(String contexts, Writer output) throws LiquibaseException {
465         changeLogParameters.setContexts(StringUtils.splitAndTrim(contexts, ","));
466 
467         LoggingExecutor outputTemplate = new LoggingExecutor(ExecutorService.getInstance().getExecutor(database),
468                 output, database);
469         Executor oldTemplate = ExecutorService.getInstance().getExecutor(database);
470         ExecutorService.getInstance().setExecutor(database, outputTemplate);
471 
472         outputHeader("SQL to roll back currently unexecuted changes");
473 
474         LockService lockService = LockService.getInstance(database);
475         lockService.waitForLock();
476 
477         try {
478             DatabaseChangeLog changeLog = ChangeLogParserFactory.getInstance()
479                     .getParser(changeLogFile, resourceAccessor)
480                     .parse(changeLogFile, changeLogParameters, resourceAccessor);
481             checkDatabaseChangeLogTable(false, changeLog, contexts);
482             changeLog.validate(database, contexts);
483 
484             ChangeLogIterator logIterator = new ChangeLogIterator(changeLog, new NotRanChangeSetFilter(
485                     database.getRanChangeSetList()), new ContextChangeSetFilter(contexts), new DbmsChangeSetFilter(
486                     database));
487 
488             logIterator.run(new RollbackVisitor(database), database);
489         } finally {
490             ExecutorService.getInstance().setExecutor(database, oldTemplate);
491             lockService.releaseLock();
492         }
493 
494         try {
495             output.flush();
496         } catch (IOException e) {
497             throw new LiquibaseException(e);
498         }
499 
500     }
501 
502     /**
503      * Drops all database objects owned by the current user.
504      */
505     public final void dropAll() throws DatabaseException, LockException {
506         dropAll(getDatabase().getDefaultSchemaName());
507     }
508 
509     /**
510      * Drops all database objects owned by the current user.
511      */
512     public final void dropAll(String... schemas) throws DatabaseException {
513         try {
514             LockService.getInstance(database).waitForLock();
515 
516             for (String schema : schemas) {
517                 log.info("Dropping Database Objects in schema: " + database.convertRequestedSchemaToSchema(schema));
518                 checkDatabaseChangeLogTable(false, null, null);
519                 getDatabase().dropDatabaseObjects(schema);
520                 checkDatabaseChangeLogTable(false, null, null);
521                 log.debug("Objects dropped successfully");
522             }
523         } catch (DatabaseException e) {
524             throw e;
525         } catch (Exception e) {
526             throw new DatabaseException(e);
527         } finally {
528             try {
529                 LockService.getInstance(database).releaseLock();
530             } catch (LockException e) {
531                 log.severe("Unable to release lock: " + e.getMessage());
532             }
533         }
534     }
535 
536     /**
537      * 'Tags' the database for future rollback
538      */
539     public void tag(String tagString) throws LiquibaseException {
540         LockService lockService = LockService.getInstance(database);
541         lockService.waitForLock();
542 
543         try {
544             checkDatabaseChangeLogTable(false, null, null);
545             getDatabase().tag(tagString);
546         } finally {
547             lockService.releaseLock();
548         }
549     }
550 
551     public void updateTestingRollback(String contexts) throws LiquibaseException {
552         changeLogParameters.setContexts(StringUtils.splitAndTrim(contexts, ","));
553 
554         Date baseDate = new Date();
555         update(contexts);
556         rollback(baseDate, contexts);
557         update(contexts);
558     }
559 
560     public void checkDatabaseChangeLogTable(boolean updateExistingNullChecksums, DatabaseChangeLog databaseChangeLog,
561             String contexts) throws LiquibaseException {
562         if (updateExistingNullChecksums && databaseChangeLog == null) {
563             throw new LiquibaseException("changeLog parameter is required if updating existing checksums");
564         }
565         String[] splitContexts = null;
566         if (StringUtils.trimToNull(contexts) != null) {
567             splitContexts = contexts.split(",");
568         }
569         getDatabase().checkDatabaseChangeLogTable(updateExistingNullChecksums, databaseChangeLog, splitContexts);
570         if (!LockService.getInstance(database).hasChangeLogLock()) {
571             getDatabase().checkDatabaseChangeLogLockTable();
572         }
573     }
574 
575     /**
576      * Returns true if it is "save" to migrate the database. Currently, "safe" is defined as running in an output-sql
577      * mode or against a database on localhost. It is fine to run Liquibase against a "non-safe" database, the method is
578      * mainly used to determine if the user should be prompted before continuing.
579      */
580     public boolean isSafeToRunMigration() throws DatabaseException {
581         return getDatabase().isLocalDatabase();
582     }
583 
584     /**
585      * Display change log lock information.
586      */
587     public DatabaseChangeLogLock[] listLocks() throws LiquibaseException {
588         checkDatabaseChangeLogTable(false, null, null);
589 
590         return LockService.getInstance(getDatabase()).listLocks();
591     }
592 
593     public void reportLocks(PrintStream out) throws LiquibaseException {
594         DatabaseChangeLogLock[] locks = listLocks();
595         out.println("Database change log locks for " + getDatabase().getConnection().getConnectionUserName() + "@"
596                 + getDatabase().getConnection().getURL());
597         if (locks.length == 0) {
598             out.println(" - No locks");
599         }
600         for (DatabaseChangeLogLock lock : locks) {
601             out.println(" - " + lock.getLockedBy() + " at "
602                     + DateFormat.getDateTimeInstance().format(lock.getLockGranted()));
603         }
604 
605     }
606 
607     public void forceReleaseLocks() throws LiquibaseException {
608         checkDatabaseChangeLogTable(false, null, null);
609 
610         LockService.getInstance(getDatabase()).forceReleaseLock();
611     }
612 
613     public List<ChangeSet> listUnrunChangeSets(String contexts) throws LiquibaseException {
614         changeLogParameters.setContexts(StringUtils.splitAndTrim(contexts, ","));
615 
616         DatabaseChangeLog changeLog = ChangeLogParserFactory.getInstance().getParser(changeLogFile, resourceAccessor)
617                 .parse(changeLogFile, changeLogParameters, resourceAccessor);
618 
619         changeLog.validate(database, contexts);
620 
621         ChangeLogIterator logIterator = getStandardChangelogIterator(contexts, changeLog);
622 
623         ListVisitor visitor = new ListVisitor();
624         logIterator.run(visitor, database);
625         return visitor.getSeenChangeSets();
626     }
627 
628     public void reportStatus(boolean verbose, String contexts, Writer out) throws LiquibaseException {
629         changeLogParameters.setContexts(StringUtils.splitAndTrim(contexts, ","));
630 
631         try {
632             List<ChangeSet> unrunChangeSets = listUnrunChangeSets(contexts);
633             if (unrunChangeSets.size() == 0) {
634                 out.append(getDatabase().getConnection().getConnectionUserName());
635                 out.append("@");
636                 out.append(getDatabase().getConnection().getURL());
637                 out.append(" is up to date");
638                 out.append(StreamUtil.getLineSeparator());
639             } else {
640                 out.append(String.valueOf(unrunChangeSets.size()));
641                 out.append(" change sets have not been applied to ");
642                 out.append(getDatabase().getConnection().getConnectionUserName());
643                 out.append("@");
644                 out.append(getDatabase().getConnection().getURL());
645                 out.append(StreamUtil.getLineSeparator());
646                 if (verbose) {
647                     for (ChangeSet changeSet : unrunChangeSets) {
648                         out.append("     ").append(changeSet.toString(false)).append(StreamUtil.getLineSeparator());
649                     }
650                 }
651             }
652 
653             out.flush();
654         } catch (IOException e) {
655             throw new LiquibaseException(e);
656         }
657 
658     }
659 
660     /**
661      * Sets checksums to null so they will be repopulated next run
662      */
663     public void clearCheckSums() throws LiquibaseException {
664         log.info("Clearing database change log checksums");
665         LockService lockService = LockService.getInstance(database);
666         lockService.waitForLock();
667 
668         try {
669             checkDatabaseChangeLogTable(false, null, null);
670 
671             UpdateStatement updateStatement = new UpdateStatement(getDatabase().getLiquibaseSchemaName(), getDatabase()
672                     .getDatabaseChangeLogTableName());
673             updateStatement.addNewColumnValue("MD5SUM", null);
674             ExecutorService.getInstance().getExecutor(database).execute(updateStatement);
675             getDatabase().commit();
676         } finally {
677             lockService.releaseLock();
678         }
679     }
680 
681     public void generateDocumentation(String outputDirectory) throws LiquibaseException {
682         // call without context
683         generateDocumentation(outputDirectory, null);
684     }
685 
686     public void generateDocumentation(String outputDirectory, String contexts) throws LiquibaseException {
687         log.info("Generating Database Documentation");
688         changeLogParameters.setContexts(StringUtils.splitAndTrim(contexts, ","));
689         LockService lockService = LockService.getInstance(database);
690         lockService.waitForLock();
691 
692         try {
693             DatabaseChangeLog changeLog = ChangeLogParserFactory.getInstance()
694                     .getParser(changeLogFile, resourceAccessor)
695                     .parse(changeLogFile, changeLogParameters, resourceAccessor);
696             checkDatabaseChangeLogTable(false, changeLog, null);
697 
698             String[] splitContexts = null;
699             if (StringUtils.trimToNull(contexts) != null) {
700                 splitContexts = contexts.split(",");
701             }
702             changeLog.validate(database, splitContexts);
703 
704             ChangeLogIterator logIterator = new ChangeLogIterator(changeLog, new DbmsChangeSetFilter(database));
705 
706             DBDocVisitor visitor = new DBDocVisitor(database);
707             logIterator.run(visitor, database);
708 
709             visitor.writeHTML(new File(outputDirectory), resourceAccessor);
710         } catch (IOException e) {
711             throw new LiquibaseException(e);
712         } finally {
713             lockService.releaseLock();
714         }
715 
716         // try {
717         // if (!LockService.getExecutor(database).waitForLock()) {
718         // return;
719         // }
720         //
721         // DBDocChangeLogHandler changeLogHandler = new DBDocChangeLogHandler(outputDirectory, this,
722         // changeLogFile,resourceAccessor);
723         // runChangeLogs(changeLogHandler);
724         //
725         // changeLogHandler.writeHTML(this);
726         // } finally {
727         // releaseLock();
728         // }
729     }
730 
731     public Diff diff(Database referenceDatabase, Database targetDatabase) {
732         return new Diff(referenceDatabase, targetDatabase);
733     }
734 
735     /**
736      * Checks changelogs for bad MD5Sums and preconditions before attempting a migration
737      */
738     public void validate() throws LiquibaseException {
739 
740         DatabaseChangeLog changeLog = ChangeLogParserFactory.getInstance().getParser(changeLogFile, resourceAccessor)
741                 .parse(changeLogFile, changeLogParameters, resourceAccessor);
742         changeLog.validate(database);
743     }
744 
745     public void setChangeLogParameter(String key, Object value) {
746         this.changeLogParameters.set(key, value);
747     }
748 
749     /**
750      * Add safe database properties as changelog parameters.<br/>
751      * Safe properties are the ones that doesn't have side effects in liquibase state and also don't change in during
752      * the liquibase execution
753      * 
754      * @param database
755      *            Database which propeties are put in the changelog
756      * @throws DatabaseException
757      */
758     private void setDatabasePropertiesAsChangelogParameters(Database database) throws DatabaseException {
759         setChangeLogParameter("database.autoIncrementClause", database.getAutoIncrementClause());
760         setChangeLogParameter("database.currentDateTimeFunction", database.getCurrentDateTimeFunction());
761         setChangeLogParameter("database.databaseChangeLogLockTableName", database.getDatabaseChangeLogLockTableName());
762         setChangeLogParameter("database.databaseChangeLogTableName", database.getDatabaseChangeLogTableName());
763         setChangeLogParameter("database.databaseMajorVersion", database.getDatabaseMajorVersion());
764         setChangeLogParameter("database.databaseMinorVersion", database.getDatabaseMinorVersion());
765         setChangeLogParameter("database.databaseProductName", database.getDatabaseProductName());
766         setChangeLogParameter("database.databaseProductVersion", database.getDatabaseProductVersion());
767         setChangeLogParameter("database.defaultCatalogName", database.getDefaultCatalogName());
768         setChangeLogParameter("database.defaultSchemaName", database.getDefaultSchemaName());
769         setChangeLogParameter("database.lineComment", database.getLineComment());
770         setChangeLogParameter("database.liquibaseSchemaName", database.getLiquibaseSchemaName());
771         setChangeLogParameter("database.typeName", database.getTypeName());
772         setChangeLogParameter("database.isLocalDatabase", database.isLocalDatabase());
773         setChangeLogParameter("database.requiresPassword", database.requiresPassword());
774         setChangeLogParameter("database.requiresUsername", database.requiresUsername());
775         setChangeLogParameter("database.supportsForeignKeyDisable", database.supportsForeignKeyDisable());
776         setChangeLogParameter("database.supportsInitiallyDeferrableColumns",
777                 database.supportsInitiallyDeferrableColumns());
778         setChangeLogParameter("database.supportsRestrictForeignKeys", database.supportsRestrictForeignKeys());
779         setChangeLogParameter("database.supportsSchemas", database.supportsSchemas());
780         setChangeLogParameter("database.supportsSequences", database.supportsSequences());
781         setChangeLogParameter("database.supportsTablespaces", database.supportsTablespaces());
782     }
783 }