View Javadoc

1   package liquibase.integration.commandline;
2   
3   import java.io.BufferedInputStream;
4   import java.io.BufferedOutputStream;
5   import java.io.File;
6   import java.io.FileInputStream;
7   import java.io.FileOutputStream;
8   import java.io.IOException;
9   import java.io.InputStream;
10  import java.io.OutputStreamWriter;
11  import java.io.PrintStream;
12  import java.io.Writer;
13  import java.lang.reflect.Field;
14  import java.net.URL;
15  import java.net.URLClassLoader;
16  import java.security.AccessController;
17  import java.security.PrivilegedAction;
18  import java.text.DateFormat;
19  import java.text.ParseException;
20  import java.text.SimpleDateFormat;
21  import java.util.ArrayList;
22  import java.util.Arrays;
23  import java.util.Enumeration;
24  import java.util.HashMap;
25  import java.util.LinkedHashSet;
26  import java.util.List;
27  import java.util.Map;
28  import java.util.Properties;
29  import java.util.Set;
30  import java.util.jar.JarEntry;
31  import java.util.jar.JarFile;
32  
33  import liquibase.Liquibase;
34  import liquibase.database.Database;
35  import liquibase.exception.CommandLineParsingException;
36  import liquibase.exception.DatabaseException;
37  import liquibase.exception.ValidationFailedException;
38  import liquibase.lockservice.LockService;
39  import liquibase.logging.LogFactory;
40  import liquibase.logging.LogLevel;
41  import liquibase.logging.Logger;
42  import liquibase.resource.ClassLoaderResourceAccessor;
43  import liquibase.resource.CompositeResourceAccessor;
44  import liquibase.resource.FileSystemResourceAccessor;
45  import liquibase.servicelocator.ServiceLocator;
46  import liquibase.util.LiquibaseUtil;
47  import liquibase.util.StreamUtil;
48  import liquibase.util.StringUtils;
49  
50  /**
51   * Class for executing Liquibase via the command line.
52   */
53  public class Main {
54      protected ClassLoader classLoader;
55  
56      protected String driver;
57      protected String username;
58      protected String password;
59      protected String url;
60      protected String databaseClass;
61      protected String defaultSchemaName;
62      protected String changeLogFile;
63      protected String classpath;
64      protected String contexts;
65      protected String driverPropertiesFile;
66      protected Boolean promptForNonLocalDatabase = null;
67      protected Boolean includeSystemClasspath;
68      protected String defaultsFile = "liquibase.properties";
69  
70      protected String diffTypes;
71      protected String changeSetAuthor;
72      protected String changeSetContext;
73      protected String dataDir;
74  
75      protected String referenceDriver;
76      protected String referenceUrl;
77      protected String referenceUsername;
78      protected String referencePassword;
79  
80      protected String currentDateTimeFunction;
81  
82      protected String command;
83      protected Set<String> commandParams = new LinkedHashSet<String>();
84  
85      protected String logLevel;
86      protected String logFile;
87  
88      protected Map<String, Object> changeLogParameters = new HashMap<String, Object>();
89  
90      public static void main(String args[]) throws CommandLineParsingException, IOException {
91          try {
92              String shouldRunProperty = System.getProperty(Liquibase.SHOULD_RUN_SYSTEM_PROPERTY);
93              if (shouldRunProperty != null && !Boolean.valueOf(shouldRunProperty)) {
94                  System.out.println("Liquibase did not run because '" + Liquibase.SHOULD_RUN_SYSTEM_PROPERTY
95                          + "' system property was set to false");
96                  return;
97              }
98  
99              Main main = new Main();
100             if (args.length == 1 && "--help".equals(args[0])) {
101                 main.printHelp(System.out);
102                 return;
103             } else if (args.length == 1 && "--version".equals(args[0])) {
104                 System.out.println("Liquibase Version: " + LiquibaseUtil.getBuildVersion()
105                         + StreamUtil.getLineSeparator());
106                 return;
107             }
108 
109             try {
110                 main.parseOptions(args);
111             } catch (CommandLineParsingException e) {
112                 main.printHelp(Arrays.asList(e.getMessage()), System.out);
113                 System.exit(-2);
114             }
115 
116             File propertiesFile = new File(main.defaultsFile);
117             File localPropertiesFile = new File(main.defaultsFile.replaceFirst("(\\.[^\\.]+)$", ".local$1"));
118 
119             if (localPropertiesFile.exists()) {
120                 main.parsePropertiesFile(new FileInputStream(localPropertiesFile));
121             }
122             if (propertiesFile.exists()) {
123                 main.parsePropertiesFile(new FileInputStream(propertiesFile));
124             }
125 
126             List<String> setupMessages = main.checkSetup();
127             if (setupMessages.size() > 0) {
128                 main.printHelp(setupMessages, System.out);
129                 return;
130             }
131 
132             try {
133                 main.applyDefaults();
134                 main.configureClassLoader();
135                 main.doMigration();
136             } catch (Throwable e) {
137                 String message = e.getMessage();
138                 if (e.getCause() != null) {
139                     message = e.getCause().getMessage();
140                 }
141                 if (message == null) {
142                     message = "Unknown Reason";
143                 }
144 
145                 if (e.getCause() instanceof ValidationFailedException) {
146                     ((ValidationFailedException) e.getCause()).printDescriptiveError(System.out);
147                 } else {
148                     System.out.println("Liquibase Update Failed: " + message);
149                     LogFactory.getLogger().severe(message, e);
150                     System.out.println(generateLogLevelWarningMessage());
151                 }
152                 System.exit(-1);
153             }
154 
155             if ("update".equals(main.command)) {
156                 System.out.println("Liquibase Update Successful");
157             } else if (main.command.startsWith("rollback") && !main.command.endsWith("SQL")) {
158                 System.out.println("Liquibase Rollback Successful");
159             } else if (!main.command.endsWith("SQL")) {
160                 System.out.println("Liquibase '" + main.command + "' Successful");
161             }
162         } catch (Throwable e) {
163             String message = "Unexpected error running Liquibase: " + e.getMessage();
164             System.out.println(message);
165             try {
166                 LogFactory.getLogger().severe(message, e);
167             } catch (Exception e1) {
168                 e.printStackTrace();
169             }
170             System.exit(-3);
171         }
172         System.exit(0);
173     }
174 
175     private static String generateLogLevelWarningMessage() {
176         Logger logger = LogFactory.getLogger();
177         if (logger == null || logger.getLogLevel() == null || (logger.getLogLevel().equals(LogLevel.DEBUG))) {
178             return "";
179         } else {
180             return "\n\nFor more information, use the --logLevel flag)";
181         }
182     }
183 
184     /**
185      * On windows machines, it splits args on '=' signs. Put it back like it was.
186      */
187     protected String[] fixupArgs(String[] args) {
188         List<String> fixedArgs = new ArrayList<String>();
189 
190         for (int i = 0; i < args.length; i++) {
191             String arg = args[i];
192             if ((arg.startsWith("--") || arg.startsWith("-D")) && !arg.contains("=")) {
193                 String nextArg = null;
194                 if (i + 1 < args.length) {
195                     nextArg = args[i + 1];
196                 }
197                 if (nextArg != null && !nextArg.startsWith("--") && !isCommand(nextArg)) {
198                     arg = arg + "=" + nextArg;
199                     i++;
200                 }
201             }
202             fixedArgs.add(arg);
203         }
204 
205         return fixedArgs.toArray(new String[fixedArgs.size()]);
206     }
207 
208     protected List<String> checkSetup() {
209         List<String> messages = new ArrayList<String>();
210         if (command == null) {
211             messages.add("Command not passed");
212         } else if (!isCommand(command)) {
213             messages.add("Unknown command: " + command);
214         } else {
215             if (url == null) {
216                 messages.add("--url is required");
217             }
218 
219             if (isChangeLogRequired(command) && changeLogFile == null) {
220                 messages.add("--changeLog is required");
221             }
222         }
223         return messages;
224     }
225 
226     private boolean isChangeLogRequired(String command) {
227         return command.toLowerCase().startsWith("update") || command.toLowerCase().startsWith("rollback")
228                 || "validate".equals(command);
229     }
230 
231     private boolean isCommand(String arg) {
232         return "migrate".equals(arg) || "migrateSQL".equalsIgnoreCase(arg) || "update".equalsIgnoreCase(arg)
233                 || "updateSQL".equalsIgnoreCase(arg) || "updateCount".equalsIgnoreCase(arg)
234                 || "updateCountSQL".equalsIgnoreCase(arg) || "rollback".equalsIgnoreCase(arg)
235                 || "rollbackToDate".equalsIgnoreCase(arg) || "rollbackCount".equalsIgnoreCase(arg)
236                 || "rollbackSQL".equalsIgnoreCase(arg) || "rollbackToDateSQL".equalsIgnoreCase(arg)
237                 || "rollbackCountSQL".equalsIgnoreCase(arg) || "futureRollbackSQL".equalsIgnoreCase(arg)
238                 || "updateTestingRollback".equalsIgnoreCase(arg) || "tag".equalsIgnoreCase(arg)
239                 || "listLocks".equalsIgnoreCase(arg) || "dropAll".equalsIgnoreCase(arg)
240                 || "releaseLocks".equalsIgnoreCase(arg) || "status".equalsIgnoreCase(arg)
241                 || "validate".equalsIgnoreCase(arg) || "help".equalsIgnoreCase(arg) || "diff".equalsIgnoreCase(arg)
242                 || "diffChangeLog".equalsIgnoreCase(arg) || "generateChangeLog".equalsIgnoreCase(arg)
243                 || "clearCheckSums".equalsIgnoreCase(arg) || "dbDoc".equalsIgnoreCase(arg)
244                 || "changelogSync".equalsIgnoreCase(arg) || "changelogSyncSQL".equalsIgnoreCase(arg)
245                 || "markNextChangeSetRan".equalsIgnoreCase(arg) || "markNextChangeSetRanSQL".equalsIgnoreCase(arg);
246     }
247 
248     protected void parsePropertiesFile(InputStream propertiesInputStream) throws IOException,
249             CommandLineParsingException {
250         Properties props = new Properties();
251         props.load(propertiesInputStream);
252 
253         for (Map.Entry entry : props.entrySet()) {
254             try {
255                 if (entry.getKey().equals("promptOnNonLocalDatabase")) {
256                     continue;
257                 }
258                 if (((String) entry.getKey()).startsWith("parameter.")) {
259                     changeLogParameters
260                             .put(((String) entry.getKey()).replaceFirst("^parameter.", ""), entry.getValue());
261                 } else {
262                     Field field = getClass().getDeclaredField((String) entry.getKey());
263                     Object currentValue = field.get(this);
264 
265                     if (currentValue == null) {
266                         String value = entry.getValue().toString().trim();
267                         if (field.getType().equals(Boolean.class)) {
268                             field.set(this, Boolean.valueOf(value));
269                         } else {
270                             field.set(this, value);
271                         }
272                     }
273                 }
274             } catch (Exception e) {
275                 throw new CommandLineParsingException("Unknown parameter: '" + entry.getKey() + "'");
276             }
277         }
278     }
279 
280     protected void printHelp(List<String> errorMessages, PrintStream stream) {
281         stream.println("Errors:");
282         for (String message : errorMessages) {
283             stream.println("  " + message);
284         }
285         stream.println();
286         printHelp(stream);
287     }
288 
289     protected void printHelp(PrintStream stream) {
290         stream.println("Usage: java -jar liquibase.jar [options] [command]");
291         stream.println("");
292         stream.println("Standard Commands:");
293         stream.println(" update                         Updates database to current version");
294         stream.println(" updateSQL                      Writes SQL to update database to current");
295         stream.println("                                version to STDOUT");
296         stream.println(" updateCount <num>              Applies next NUM changes to the database");
297         stream.println(" updateSQL <num>                Writes SQL to apply next NUM changes");
298         stream.println("                                to the database");
299         stream.println(" rollback <tag>                 Rolls back the database to the the state is was");
300         stream.println("                                when the tag was applied");
301         stream.println(" rollbackSQL <tag>              Writes SQL to roll back the database to that");
302         stream.println("                                state it was in when the tag was applied");
303         stream.println("                                to STDOUT");
304         stream.println(" rollbackToDate <date/time>     Rolls back the database to the the state is was");
305         stream.println("                                at the given date/time.");
306         stream.println("                                Date Format: yyyy-MM-dd HH:mm:ss");
307         stream.println(" rollbackToDateSQL <date/time>  Writes SQL to roll back the database to that");
308         stream.println("                                state it was in at the given date/time version");
309         stream.println("                                to STDOUT");
310         stream.println(" rollbackCount <value>          Rolls back the last <value> change sets");
311         stream.println("                                applied to the database");
312         stream.println(" rollbackCountSQL <value>       Writes SQL to roll back the last");
313         stream.println("                                <value> change sets to STDOUT");
314         stream.println("                                applied to the database");
315         stream.println(" futureRollbackSQL              Writes SQL to roll back the database to the ");
316         stream.println("                                current state after the changes in the ");
317         stream.println("                                changeslog have been applied");
318         stream.println(" updateTestingRollback          Updates database, then rolls back changes before");
319         stream.println("                                updating again. Useful for testing");
320         stream.println("                                rollback support");
321         stream.println(" generateChangeLog              Writes Change Log XML to copy the current state");
322         stream.println("                                of the database to standard out");
323         stream.println("");
324         stream.println("Diff Commands");
325         stream.println(" diff [diff parameters]          Writes description of differences");
326         stream.println("                                 to standard out");
327         stream.println(" diffChangeLog [diff parameters] Writes Change Log XML to update");
328         stream.println("                                 the database");
329         stream.println("                                 to the reference database to standard out");
330         stream.println("");
331         stream.println("Documentation Commands");
332         stream.println(" dbDoc <outputDirectory>         Generates Javadoc-like documentation");
333         stream.println("                                 based on current database and change log");
334         stream.println("");
335         stream.println("Maintenance Commands");
336         stream.println(" tag <tag string>          'Tags' the current database state for future rollback");
337         stream.println(" status [--verbose]        Outputs count (list if --verbose) of unrun changesets");
338         stream.println(" validate                  Checks changelog for errors");
339         stream.println(" clearCheckSums            Removes all saved checksums from database log.");
340         stream.println("                           Useful for 'MD5Sum Check Failed' errors");
341         stream.println(" changelogSync             Mark all changes as executed in the database");
342         stream.println(" changelogSyncSQL          Writes SQL to mark all changes as executed ");
343         stream.println("                           in the database to STDOUT");
344         stream.println(" markNextChangeSetRan      Mark the next change changes as executed ");
345         stream.println("                           in the database");
346         stream.println(" markNextChangeSetRanSQL   Writes SQL to mark the next change ");
347         stream.println("                           as executed in the database to STDOUT");
348         stream.println(" listLocks                 Lists who currently has locks on the");
349         stream.println("                           database changelog");
350         stream.println(" releaseLocks              Releases all locks on the database changelog");
351         stream.println(" dropAll                   Drop all database objects owned by user");
352         stream.println("");
353         stream.println("Required Parameters:");
354         stream.println(" --changeLogFile=<path and filename>        Migration file");
355         stream.println(" --username=<value>                         Database username");
356         stream.println(" --password=<value>                         Database password");
357         stream.println(" --url=<value>                              Database URL");
358         stream.println("");
359         stream.println("Optional Parameters:");
360         stream.println(" --classpath=<value>                        Classpath containing");
361         stream.println("                                            migration files and JDBC Driver");
362         stream.println(" --driver=<jdbc.driver.ClassName>           Database driver class name");
363         stream.println(" --databaseClass=<database.ClassName>       custom liquibase.database.Database");
364         stream.println("                                            implementation to use");
365         stream.println(" --defaultSchemaName=<name>                 Default database schema to use");
366         stream.println(" --contexts=<value>                         ChangeSet contexts to execute");
367         stream.println(" --defaultsFile=</path/to/file.properties>  File with default option values");
368         stream.println("                                            (default: ./liquibase.properties)");
369         stream.println(" --driverPropertiesFile=</path/to/file.properties>  File with custom properties");
370         stream.println("                                            to be set on the JDBC connection");
371         stream.println("                                            to be created");
372         stream.println(" --includeSystemClasspath=<true|false>      Include the system classpath");
373         stream.println("                                            in the Liquibase classpath");
374         stream.println("                                            (default: true)");
375         stream.println(" --promptForNonLocalDatabase=<true|false>   Prompt if non-localhost");
376         stream.println("                                            databases (default: false)");
377         stream.println(" --logLevel=<level>                         Execution log level");
378         stream.println("                                            (debug, info, warning, severe, off");
379         stream.println(" --logFile=<file>                           Log file");
380         stream.println(" --currentDateTimeFunction=<value>          Overrides current date time function");
381         stream.println("                                            used in SQL.");
382         stream.println("                                            Useful for unsupported databases");
383         stream.println(" --help                                     Prints this message");
384         stream.println(" --version                                  Prints this version information");
385         stream.println("");
386         stream.println("Required Diff Parameters:");
387         stream.println(" --referenceUsername=<value>                Reference Database username");
388         stream.println(" --referencePassword=<value>                Reference Database password");
389         stream.println(" --referenceUrl=<value>                     Reference Database URL");
390         stream.println("");
391         stream.println("Optional Diff Parameters:");
392         stream.println(" --referenceDriver=<jdbc.driver.ClassName>  Reference Database driver class name");
393         stream.println(" --dataOutputDirectory=DIR                  Output data as CSV in the given ");
394         stream.println("                                            directory");
395         stream.println("");
396         stream.println("Change Log Properties:");
397         stream.println(" -D<property.name>=<property.value>         Pass a name/value pair for");
398         stream.println("                                            substitution in the change log(s)");
399         stream.println("");
400         stream.println("Default value for parameters can be stored in a file called");
401         stream.println("'liquibase.properties' that is read from the current working directory.");
402         stream.println("");
403         stream.println("Full documentation is available at");
404         stream.println("http://www.liquibase.org/manual/command_line");
405         stream.println("");
406     }
407 
408     public Main() {
409         // options = createOptions();
410     }
411 
412     protected void parseOptions(String[] args) throws CommandLineParsingException {
413         args = fixupArgs(args);
414 
415         boolean seenCommand = false;
416         for (String arg : args) {
417             if (isCommand(arg)) {
418                 this.command = arg;
419                 if (this.command.equalsIgnoreCase("migrate")) {
420                     this.command = "update";
421                 } else if (this.command.equalsIgnoreCase("migrateSQL")) {
422                     this.command = "updateSQL";
423                 }
424                 seenCommand = true;
425             } else if (seenCommand) {
426                 if (arg.startsWith("-D")) {
427                     String[] splitArg = splitArg(arg);
428 
429                     String attributeName = splitArg[0].replaceFirst("^-D", "");
430                     String value = splitArg[1];
431 
432                     changeLogParameters.put(attributeName, value);
433                 } else {
434                     commandParams.add(arg);
435                 }
436             } else if (arg.startsWith("--")) {
437                 String[] splitArg = splitArg(arg);
438 
439                 String attributeName = splitArg[0];
440                 String value = splitArg[1];
441 
442                 try {
443                     Field field = getClass().getDeclaredField(attributeName);
444                     if (field.getType().equals(Boolean.class)) {
445                         field.set(this, Boolean.valueOf(value));
446                     } else {
447                         field.set(this, value);
448                     }
449                 } catch (Exception e) {
450                     throw new CommandLineParsingException("Unknown parameter: '" + attributeName + "'");
451                 }
452             } else {
453                 throw new CommandLineParsingException("Unexpected value " + arg + ": parameters must start with a '--'");
454             }
455         }
456 
457     }
458 
459     private String[] splitArg(String arg) throws CommandLineParsingException {
460         String[] splitArg = arg.split("=", 2);
461         if (splitArg.length < 2) {
462             throw new CommandLineParsingException("Could not parse '" + arg + "'");
463         }
464 
465         splitArg[0] = splitArg[0].replaceFirst("--", "");
466         return splitArg;
467     }
468 
469     protected void applyDefaults() {
470         if (this.promptForNonLocalDatabase == null) {
471             this.promptForNonLocalDatabase = Boolean.FALSE;
472         }
473         if (this.logLevel == null) {
474             this.logLevel = "info";
475         }
476         if (this.includeSystemClasspath == null) {
477             this.includeSystemClasspath = Boolean.TRUE;
478         }
479 
480     }
481 
482     protected void configureClassLoader() throws CommandLineParsingException {
483         final List<URL> urls = new ArrayList<URL>();
484         if (this.classpath != null) {
485             String[] classpath;
486             if (isWindows()) {
487                 classpath = this.classpath.split(";");
488             } else {
489                 classpath = this.classpath.split(":");
490             }
491 
492             for (String classpathEntry : classpath) {
493                 File classPathFile = new File(classpathEntry);
494                 if (!classPathFile.exists()) {
495                     throw new CommandLineParsingException(classPathFile.getAbsolutePath() + " does not exist");
496                 }
497                 try {
498                     if (classpathEntry.endsWith(".war")) {
499                         addWarFileClasspathEntries(classPathFile, urls);
500                     } else if (classpathEntry.endsWith(".ear")) {
501                         JarFile earZip = new JarFile(classPathFile);
502 
503                         Enumeration<? extends JarEntry> entries = earZip.entries();
504                         while (entries.hasMoreElements()) {
505                             JarEntry entry = entries.nextElement();
506                             if (entry.getName().toLowerCase().endsWith(".jar")) {
507                                 File jar = extract(earZip, entry);
508                                 urls.add(new URL("jar:" + jar.toURL() + "!/"));
509                                 jar.deleteOnExit();
510                             } else if (entry.getName().toLowerCase().endsWith("war")) {
511                                 File warFile = extract(earZip, entry);
512                                 addWarFileClasspathEntries(warFile, urls);
513                             }
514                         }
515 
516                     } else {
517                         urls.add(new File(classpathEntry).toURL());
518                     }
519                 } catch (Exception e) {
520                     throw new CommandLineParsingException(e);
521                 }
522             }
523         }
524         if (includeSystemClasspath) {
525             classLoader = AccessController.doPrivileged(new PrivilegedAction<URLClassLoader>() {
526                 @Override
527                 public URLClassLoader run() {
528                     return new URLClassLoader(urls.toArray(new URL[urls.size()]), Thread.currentThread()
529                             .getContextClassLoader());
530                 }
531             });
532 
533         } else {
534             classLoader = AccessController.doPrivileged(new PrivilegedAction<URLClassLoader>() {
535                 @Override
536                 public URLClassLoader run() {
537                     return new URLClassLoader(urls.toArray(new URL[urls.size()]));
538                 }
539             });
540         }
541 
542         ServiceLocator.getInstance().setResourceAccessor(new ClassLoaderResourceAccessor(classLoader));
543         Thread.currentThread().setContextClassLoader(classLoader);
544     }
545 
546     private void addWarFileClasspathEntries(File classPathFile, List<URL> urls) throws IOException {
547         URL url = new URL("jar:" + classPathFile.toURL() + "!/WEB-INF/classes/");
548         urls.add(url);
549         JarFile warZip = new JarFile(classPathFile);
550         Enumeration<? extends JarEntry> entries = warZip.entries();
551         while (entries.hasMoreElements()) {
552             JarEntry entry = entries.nextElement();
553             if (entry.getName().startsWith("WEB-INF/lib") && entry.getName().toLowerCase().endsWith(".jar")) {
554                 File jar = extract(warZip, entry);
555                 urls.add(new URL("jar:" + jar.toURL() + "!/"));
556                 jar.deleteOnExit();
557             }
558         }
559     }
560 
561     private File extract(JarFile jar, JarEntry entry) throws IOException {
562         // expand to temp dir and add to list
563         File tempFile = File.createTempFile("liquibase.tmp", null);
564         // read from jar and write to the tempJar file
565         BufferedInputStream inStream = null;
566 
567         BufferedOutputStream outStream = null;
568         try {
569             inStream = new BufferedInputStream(jar.getInputStream(entry));
570             outStream = new BufferedOutputStream(new FileOutputStream(tempFile));
571             int status;
572             while ((status = inStream.read()) != -1) {
573                 outStream.write(status);
574             }
575         } finally {
576             if (outStream != null) {
577                 try {
578                     outStream.close();
579                 } catch (IOException ioe) {
580                     ;
581                 }
582             }
583             if (inStream != null) {
584                 try {
585                     inStream.close();
586                 } catch (IOException ioe) {
587                     ;
588                 }
589             }
590         }
591 
592         return tempFile;
593     }
594 
595     protected void doMigration() throws Exception {
596         if ("help".equalsIgnoreCase(command)) {
597             printHelp(System.out);
598             return;
599         }
600 
601         try {
602             if (null != logFile) {
603                 LogFactory.getLogger().setLogLevel(logLevel, logFile);
604             } else {
605                 LogFactory.getLogger().setLogLevel(logLevel);
606             }
607         } catch (IllegalArgumentException e) {
608             throw new CommandLineParsingException(e.getMessage(), e);
609         }
610 
611         FileSystemResourceAccessor fsOpener = new FileSystemResourceAccessor();
612         CommandLineResourceAccessor clOpener = new CommandLineResourceAccessor(classLoader);
613         Database database = CommandLineUtils.createDatabaseObject(classLoader, this.url, this.username, this.password,
614                 this.driver, this.defaultSchemaName, this.databaseClass, this.driverPropertiesFile);
615         try {
616 
617             CompositeResourceAccessor fileOpener = new CompositeResourceAccessor(fsOpener, clOpener);
618 
619             if ("diff".equalsIgnoreCase(command)) {
620                 CommandLineUtils.doDiff(createReferenceDatabaseFromCommandParams(commandParams), database);
621                 return;
622             } else if ("diffChangeLog".equalsIgnoreCase(command)) {
623                 CommandLineUtils.doDiffToChangeLog(changeLogFile,
624                         createReferenceDatabaseFromCommandParams(commandParams), database);
625                 return;
626             } else if ("generateChangeLog".equalsIgnoreCase(command)) {
627                 CommandLineUtils.doGenerateChangeLog(changeLogFile, database, defaultSchemaName,
628                         StringUtils.trimToNull(diffTypes), StringUtils.trimToNull(changeSetAuthor),
629                         StringUtils.trimToNull(changeSetContext), StringUtils.trimToNull(dataDir));
630                 return;
631             }
632 
633             Liquibase liquibase = new Liquibase(changeLogFile, fileOpener, database);
634             liquibase.setCurrentDateTimeFunction(currentDateTimeFunction);
635             for (Map.Entry<String, Object> entry : changeLogParameters.entrySet()) {
636                 liquibase.setChangeLogParameter(entry.getKey(), entry.getValue());
637             }
638 
639             if ("listLocks".equalsIgnoreCase(command)) {
640                 liquibase.reportLocks(System.out);
641                 return;
642             } else if ("releaseLocks".equalsIgnoreCase(command)) {
643                 LockService.getInstance(database).forceReleaseLock();
644                 System.out.println("Successfully released all database change log locks for "
645                         + liquibase.getDatabase().getConnection().getConnectionUserName() + "@"
646                         + liquibase.getDatabase().getConnection().getURL());
647                 return;
648             } else if ("tag".equalsIgnoreCase(command)) {
649                 liquibase.tag(commandParams.iterator().next());
650                 System.out.println("Successfully tagged "
651                         + liquibase.getDatabase().getConnection().getConnectionUserName() + "@"
652                         + liquibase.getDatabase().getConnection().getURL());
653                 return;
654             } else if ("dropAll".equals(command)) {
655                 liquibase.dropAll();
656                 System.out.println("All objects dropped from "
657                         + liquibase.getDatabase().getConnection().getConnectionUserName() + "@"
658                         + liquibase.getDatabase().getConnection().getURL());
659                 return;
660             } else if ("status".equalsIgnoreCase(command)) {
661                 boolean runVerbose = false;
662 
663                 if (commandParams.contains("--verbose")) {
664                     runVerbose = true;
665                 }
666                 liquibase.reportStatus(runVerbose, contexts, getOutputWriter());
667                 return;
668             } else if ("validate".equalsIgnoreCase(command)) {
669                 try {
670                     liquibase.validate();
671                 } catch (ValidationFailedException e) {
672                     e.printDescriptiveError(System.out);
673                     return;
674                 }
675                 System.out.println("No validation errors found");
676                 return;
677             } else if ("clearCheckSums".equalsIgnoreCase(command)) {
678                 liquibase.clearCheckSums();
679                 return;
680             } else if ("dbdoc".equalsIgnoreCase(command)) {
681                 if (commandParams.size() == 0) {
682                     throw new CommandLineParsingException("dbdoc requires an output directory");
683                 }
684                 if (changeLogFile == null) {
685                     throw new CommandLineParsingException("dbdoc requires a changeLog parameter");
686                 }
687                 liquibase.generateDocumentation(commandParams.iterator().next(), contexts);
688                 return;
689             }
690 
691             DateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss");
692             try {
693                 if ("update".equalsIgnoreCase(command)) {
694                     liquibase.update(contexts);
695                 } else if ("changelogSync".equalsIgnoreCase(command)) {
696                     liquibase.changeLogSync(contexts);
697                 } else if ("changelogSyncSQL".equalsIgnoreCase(command)) {
698                     liquibase.changeLogSync(contexts, getOutputWriter());
699                 } else if ("markNextChangeSetRan".equalsIgnoreCase(command)) {
700                     liquibase.markNextChangeSetRan(contexts);
701                 } else if ("markNextChangeSetRanSQL".equalsIgnoreCase(command)) {
702                     liquibase.markNextChangeSetRan(contexts, getOutputWriter());
703                 } else if ("updateCount".equalsIgnoreCase(command)) {
704                     liquibase.update(Integer.parseInt(commandParams.iterator().next()), contexts);
705                 } else if ("updateCountSQL".equalsIgnoreCase(command)) {
706                     liquibase.update(Integer.parseInt(commandParams.iterator().next()), contexts, getOutputWriter());
707                 } else if ("updateSQL".equalsIgnoreCase(command)) {
708                     liquibase.update(contexts, getOutputWriter());
709                 } else if ("rollback".equalsIgnoreCase(command)) {
710                     if (commandParams == null || commandParams.size() == 0) {
711                         throw new CommandLineParsingException("rollback requires a rollback tag");
712                     }
713                     liquibase.rollback(commandParams.iterator().next(), contexts);
714                 } else if ("rollbackToDate".equalsIgnoreCase(command)) {
715                     if (commandParams == null || commandParams.size() == 0) {
716                         throw new CommandLineParsingException("rollback requires a rollback date");
717                     }
718                     liquibase.rollback(dateFormat.parse(commandParams.iterator().next()), contexts);
719                 } else if ("rollbackCount".equalsIgnoreCase(command)) {
720                     liquibase.rollback(Integer.parseInt(commandParams.iterator().next()), contexts);
721 
722                 } else if ("rollbackSQL".equalsIgnoreCase(command)) {
723                     if (commandParams == null || commandParams.size() == 0) {
724                         throw new CommandLineParsingException("rollbackSQL requires a rollback tag");
725                     }
726                     liquibase.rollback(commandParams.iterator().next(), contexts, getOutputWriter());
727                 } else if ("rollbackToDateSQL".equalsIgnoreCase(command)) {
728                     if (commandParams == null || commandParams.size() == 0) {
729                         throw new CommandLineParsingException("rollbackToDateSQL requires a rollback date");
730                     }
731                     liquibase.rollback(dateFormat.parse(commandParams.iterator().next()), contexts, getOutputWriter());
732                 } else if ("rollbackCountSQL".equalsIgnoreCase(command)) {
733                     if (commandParams == null || commandParams.size() == 0) {
734                         throw new CommandLineParsingException("rollbackCountSQL requires a rollback tag");
735                     }
736 
737                     liquibase.rollback(Integer.parseInt(commandParams.iterator().next()), contexts, getOutputWriter());
738                 } else if ("futureRollbackSQL".equalsIgnoreCase(command)) {
739                     liquibase.futureRollbackSQL(contexts, getOutputWriter());
740                 } else if ("updateTestingRollback".equalsIgnoreCase(command)) {
741                     liquibase.updateTestingRollback(contexts);
742                 } else {
743                     throw new CommandLineParsingException("Unknown command: " + command);
744                 }
745             } catch (ParseException e) {
746                 throw new CommandLineParsingException("Unexpected date/time format.  Use 'yyyy-MM-dd'T'HH:mm:ss'");
747             }
748         } finally {
749             try {
750                 database.rollback();
751                 database.close();
752             } catch (DatabaseException e) {
753                 LogFactory.getLogger().warning("problem closing connection", e);
754             }
755         }
756     }
757 
758     private String getCommandParam(String paramName) throws CommandLineParsingException {
759         for (String param : commandParams) {
760             String[] splitArg = splitArg(param);
761 
762             String attributeName = splitArg[0];
763             String value = splitArg[1];
764             if (attributeName.equalsIgnoreCase(paramName)) {
765                 return value;
766             }
767         }
768 
769         return null;
770     }
771 
772     private Database createReferenceDatabaseFromCommandParams(Set<String> commandParams)
773             throws CommandLineParsingException, DatabaseException {
774         String driver = referenceDriver;
775         String url = referenceUrl;
776         String username = referenceUsername;
777         String password = referencePassword;
778         String defaultSchemaName = this.defaultSchemaName;
779 
780         for (String param : commandParams) {
781             String[] splitArg = splitArg(param);
782 
783             String attributeName = splitArg[0];
784             String value = splitArg[1];
785             if ("referenceDriver".equalsIgnoreCase(attributeName)) {
786                 driver = value;
787             } else if ("referenceUrl".equalsIgnoreCase(attributeName)) {
788                 url = value;
789             } else if ("referenceUsername".equalsIgnoreCase(attributeName)) {
790                 username = value;
791             } else if ("referencePassword".equalsIgnoreCase(attributeName)) {
792                 password = value;
793             } else if ("referenceDefaultSchemaName".equalsIgnoreCase(attributeName)) {
794                 defaultSchemaName = value;
795             } else if ("dataOutputDirectory".equalsIgnoreCase(attributeName)) {
796                 dataDir = value;
797             }
798         }
799 
800         // if (driver == null) {
801         // driver = DatabaseFactory.getWriteExecutor().findDefaultDriver(url);
802         // }
803 
804         if (url == null) {
805             throw new CommandLineParsingException("referenceUrl parameter missing");
806         }
807 
808         return CommandLineUtils.createDatabaseObject(classLoader, url, username, password, driver, defaultSchemaName,
809                 null, null);
810         // Driver driverObject;
811         // try {
812         // driverObject = (Driver) Class.forName(driver, true, classLoader).newInstance();
813         // } catch (Exception e) {
814         // throw new RuntimeException("Cannot find database driver: " + e.getMessage());
815         // }
816         //
817         // Properties info = new Properties();
818         // info.put("user", username);
819         // info.put("password", password);
820         //
821         // Connection connection;
822         // try {
823         // connection = driverObject.connect(url, info);
824         // } catch (SQLException e) {
825         // throw new DatabaseException("Connection could not be created to " + url + ": " + e.getMessage(), e);
826         // }
827         // if (connection == null) {
828         // throw new DatabaseException("Connection could not be created to " + url + " with driver " +
829         // driver.getClass().getName() + ".  Possibly the wrong driver for the given database URL");
830         // }
831         //
832         // Database database = DatabaseFactory.getWriteExecutor().findCorrectDatabaseImplementation(connection);
833         // database.setDefaultSchemaName(defaultSchemaName);
834         //
835         // return database;
836     }
837 
838     private Writer getOutputWriter() {
839         return new OutputStreamWriter(System.out);
840     }
841 
842     public boolean isWindows() {
843         return System.getProperty("os.name").startsWith("Windows ");
844     }
845 }