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
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
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
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
563 File tempFile = File.createTempFile("liquibase.tmp", null);
564
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
801
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
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
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 }