0001: package liquibase.commandline;
0002:
0003: import liquibase.CompositeFileOpener;
0004: import liquibase.FileSystemFileOpener;
0005: import liquibase.database.Database;
0006: import liquibase.database.DatabaseFactory;
0007: import liquibase.diff.Diff;
0008: import liquibase.diff.DiffResult;
0009: import liquibase.diff.DiffStatusListener;
0010: import liquibase.exception.CommandLineParsingException;
0011: import liquibase.exception.JDBCException;
0012: import liquibase.exception.ValidationFailedException;
0013: import liquibase.lock.LockHandler;
0014: import liquibase.log.LogFactory;
0015: import liquibase.Liquibase;
0016: import liquibase.util.LiquibaseUtil;
0017: import liquibase.util.StreamUtil;
0018: import liquibase.util.StringUtils;
0019:
0020: import javax.xml.parsers.ParserConfigurationException;
0021: import java.io.*;
0022: import java.lang.reflect.Field;
0023: import java.net.URL;
0024: import java.net.URLClassLoader;
0025: import java.security.AccessController;
0026: import java.security.PrivilegedAction;
0027: import java.sql.Connection;
0028: import java.sql.Driver;
0029: import java.sql.SQLException;
0030: import java.text.DateFormat;
0031: import java.text.ParseException;
0032: import java.text.SimpleDateFormat;
0033: import java.util.*;
0034: import java.util.jar.JarEntry;
0035: import java.util.jar.JarFile;
0036: import java.util.logging.Level;
0037: import java.util.logging.Logger;
0038:
0039: /**
0040: * Class for executing LiquiBase via the command line.
0041: */
0042: public class Main {
0043: protected ClassLoader classLoader;
0044:
0045: protected String driver;
0046: protected String username;
0047: protected String password;
0048: protected String url;
0049: protected String databaseClass;
0050: protected String defaultSchemaName;
0051: protected String changeLogFile;
0052: protected String classpath;
0053: protected String contexts;
0054: protected Boolean promptForNonLocalDatabase = null;
0055: protected Boolean includeSystemClasspath;
0056: protected String defaultsFile = "liquibase.properties";
0057:
0058: protected String currentDateTimeFunction;
0059:
0060: protected String command;
0061: protected Set<String> commandParams = new HashSet<String>();
0062:
0063: protected String logLevel;
0064:
0065: public static void main(String args[])
0066: throws CommandLineParsingException, IOException {
0067: String shouldRunProperty = System
0068: .getProperty(Liquibase.SHOULD_RUN_SYSTEM_PROPERTY);
0069: if (shouldRunProperty != null
0070: && !Boolean.valueOf(shouldRunProperty)) {
0071: System.out.println("LiquiBase did not run because '"
0072: + Liquibase.SHOULD_RUN_SYSTEM_PROPERTY
0073: + "' system property was set to false");
0074: return;
0075: }
0076:
0077: Main main = new Main();
0078: if (args.length == 1 && "--help".equals(args[0])) {
0079: main.printHelp(System.out);
0080: return;
0081: } else if (args.length == 1 && "--version".equals(args[0])) {
0082: System.out.println("LiquiBase Version: "
0083: + LiquibaseUtil.getBuildVersion()
0084: + StreamUtil.getLineSeparator());
0085: return;
0086: }
0087:
0088: main.parseOptions(args);
0089:
0090: File propertiesFile = new File(main.defaultsFile);
0091:
0092: if (propertiesFile.exists()) {
0093: main
0094: .parsePropertiesFile(new FileInputStream(
0095: propertiesFile));
0096: }
0097: List<String> setupMessages = main.checkSetup();
0098: if (setupMessages.size() > 0) {
0099: main.printHelp(setupMessages, System.out);
0100: return;
0101: }
0102:
0103: try {
0104: main.applyDefaults();
0105: main.configureClassLoader();
0106: main.doMigration();
0107: } catch (Throwable e) {
0108: String message = e.getMessage();
0109: if (e.getCause() != null) {
0110: message = e.getCause().getMessage();
0111: }
0112: if (message == null) {
0113: message = "Unknown Reason";
0114: }
0115:
0116: if (e.getCause() instanceof ValidationFailedException) {
0117: ((ValidationFailedException) e.getCause())
0118: .printDescriptiveError(System.out);
0119: } else {
0120: System.out.println("Migration Failed: " + message
0121: + generateLogLevelWarningMessage());
0122: LogFactory.getLogger().log(Level.SEVERE, message, e);
0123: }
0124: return;
0125: }
0126:
0127: if ("update".equals(main.command)) {
0128: System.out.println("Migration successful");
0129: } else if (main.command.startsWith("rollback")
0130: && !main.command.endsWith("SQL")) {
0131: System.out.println("Rollback successful");
0132: }
0133: }
0134:
0135: private static String generateLogLevelWarningMessage() {
0136: Logger logger = LogFactory.getLogger();
0137: if (logger == null || logger.getLevel() == null
0138: || (logger.getLevel().equals(Level.OFF))) {
0139: return "";
0140: } else {
0141: return ". For more information, use the --logLevel flag)";
0142: }
0143: }
0144:
0145: /**
0146: * On windows machines, it splits args on '=' signs. Put it back like it was.
0147: */
0148: protected String[] fixupArgs(String[] args) {
0149: List<String> fixedArgs = new ArrayList<String>();
0150:
0151: for (int i = 0; i < args.length; i++) {
0152: String arg = args[i];
0153: if (arg.startsWith("--") && !arg.contains("=")) {
0154: String nextArg = null;
0155: if (i + 1 < args.length) {
0156: nextArg = args[i + 1];
0157: }
0158: if (nextArg != null && !nextArg.startsWith("--")
0159: && !isCommand(nextArg)) {
0160: arg = arg + "=" + nextArg;
0161: i++;
0162: }
0163: }
0164: fixedArgs.add(arg);
0165: }
0166:
0167: return fixedArgs.toArray(new String[fixedArgs.size()]);
0168: }
0169:
0170: protected List<String> checkSetup() {
0171: List<String> messages = new ArrayList<String>();
0172: if (command == null) {
0173: messages.add("Command not passed");
0174: } else if (!isCommand(command)) {
0175: messages.add("Unknown command: " + command);
0176: } else {
0177: if (username == null) {
0178: messages.add("--username is required");
0179: }
0180: if (url == null) {
0181: messages.add("--url is required");
0182: }
0183:
0184: if (isChangeLogRequired(command) && changeLogFile == null) {
0185: messages.add("--changeLog is required");
0186: }
0187: }
0188: return messages;
0189: }
0190:
0191: private boolean isChangeLogRequired(String command) {
0192: return command.toLowerCase().startsWith("update")
0193: || command.toLowerCase().startsWith("rollback")
0194: || "validate".equals(command);
0195: }
0196:
0197: private boolean isCommand(String arg) {
0198: return "migrate".equals(arg)
0199: || "migrateSQL".equalsIgnoreCase(arg)
0200: || "update".equalsIgnoreCase(arg)
0201: || "updateSQL".equalsIgnoreCase(arg)
0202: || "updateCount".equalsIgnoreCase(arg)
0203: || "updateCountSQL".equalsIgnoreCase(arg)
0204: || "rollback".equalsIgnoreCase(arg)
0205: || "rollbackToDate".equalsIgnoreCase(arg)
0206: || "rollbackCount".equalsIgnoreCase(arg)
0207: || "rollbackSQL".equalsIgnoreCase(arg)
0208: || "rollbackToDateSQL".equalsIgnoreCase(arg)
0209: || "rollbackCountSQL".equalsIgnoreCase(arg)
0210: || "futureRollbackSQL".equalsIgnoreCase(arg)
0211: || "tag".equalsIgnoreCase(arg)
0212: || "listLocks".equalsIgnoreCase(arg)
0213: || "dropAll".equalsIgnoreCase(arg)
0214: || "releaseLocks".equalsIgnoreCase(arg)
0215: || "status".equalsIgnoreCase(arg)
0216: || "validate".equalsIgnoreCase(arg)
0217: || "help".equalsIgnoreCase(arg)
0218: || "diff".equalsIgnoreCase(arg)
0219: || "diffChangeLog".equalsIgnoreCase(arg)
0220: || "generateChangeLog".equalsIgnoreCase(arg)
0221: || "clearCheckSums".equalsIgnoreCase(arg)
0222: || "dbDoc".equalsIgnoreCase(arg)
0223: || "changelogSync".equalsIgnoreCase(arg)
0224: || "changelogSyncSQL".equalsIgnoreCase(arg);
0225: }
0226:
0227: protected void parsePropertiesFile(InputStream propertiesInputStream)
0228: throws IOException, CommandLineParsingException {
0229: Properties props = new Properties();
0230: props.load(propertiesInputStream);
0231:
0232: for (Map.Entry entry : props.entrySet()) {
0233: try {
0234: Field field = getClass().getDeclaredField(
0235: (String) entry.getKey());
0236: Object currentValue = field.get(this );
0237:
0238: if (currentValue == null) {
0239: String value = entry.getValue().toString().trim();
0240: if (field.getType().equals(Boolean.class)) {
0241: field.set(this , Boolean.valueOf(value));
0242: } else {
0243: field.set(this , value);
0244: }
0245: }
0246: } catch (Exception e) {
0247: throw new CommandLineParsingException(
0248: "Unknown parameter: '" + entry.getKey() + "'");
0249: }
0250: }
0251: }
0252:
0253: protected void printHelp(List<String> errorMessages,
0254: PrintStream stream) {
0255: stream.println("Errors:");
0256: for (String message : errorMessages) {
0257: stream.println(" " + message);
0258: }
0259: stream.println();
0260: printHelp(stream);
0261: }
0262:
0263: protected void printHelp(PrintStream stream) {
0264: stream
0265: .println("Usage: java -jar liquibase.jar [options] [command]");
0266: stream.println("");
0267: stream.println("Standard Commands:");
0268: stream
0269: .println(" update Updates database to current version");
0270: stream
0271: .println(" updateSQL Writes SQL to update database to current");
0272: stream
0273: .println(" version to STDOUT");
0274: stream
0275: .println(" updateCount <num> Applies next NUM changes to the database");
0276: stream
0277: .println(" updateSQL <num> Writes SQL to apply next NUM changes");
0278: stream
0279: .println(" to the database");
0280: stream
0281: .println(" rollback <tag> Rolls back the database to the the state is was");
0282: stream
0283: .println(" when the tag was applied");
0284: stream
0285: .println(" rollbackSQL <tag> Writes SQL to roll back the database to that");
0286: stream
0287: .println(" state it was in when the tag was applied");
0288: stream.println(" to STDOUT");
0289: stream
0290: .println(" rollbackToDate <date/time> Rolls back the database to the the state is was");
0291: stream
0292: .println(" at the given date/time.");
0293: stream
0294: .println(" Date Format: yyyy-MM-dd HH:mm:ss");
0295: stream
0296: .println(" rollbackToDateSQL <date/time> Writes SQL to roll back the database to that");
0297: stream
0298: .println(" state it was in at the given date/time version");
0299: stream.println(" to STDOUT");
0300: stream
0301: .println(" rollbackCount <value> Rolls back the last <value> change sets");
0302: stream
0303: .println(" applied to the database");
0304: stream
0305: .println(" rollbackCountSQL <value> Writes SQL to roll back the last");
0306: stream
0307: .println(" <value> change sets to STDOUT");
0308: stream
0309: .println(" applied to the database");
0310: stream
0311: .println(" futureRollbackSQL Writes SQL to roll back the database to the ");
0312: stream
0313: .println(" current state after the changes in the ");
0314: stream
0315: .println(" changeslog have been applied");
0316: stream
0317: .println(" generateChangeLog Writes Change Log XML to copy the current state");
0318: stream
0319: .println(" of the database to standard out");
0320: stream.println("");
0321: stream.println("Diff Commands");
0322: stream
0323: .println(" diff [diff parameters] Writes description of differences");
0324: stream
0325: .println(" to standard out");
0326: stream
0327: .println(" diffChangeLog [diff parameters] Writes Change Log XML to update");
0328: stream
0329: .println(" the base database");
0330: stream
0331: .println(" to the target database to standard out");
0332: stream.println("");
0333: stream.println("Documentation Commands");
0334: stream
0335: .println(" dbDoc <outputDirectory> Generates Javadoc-like documentation");
0336: stream
0337: .println(" based on current database and change log");
0338: stream.println("");
0339: stream.println("Maintenance Commands");
0340: stream
0341: .println(" tag <tag string> 'Tags' the current database state for future rollback");
0342: stream
0343: .println(" status [--verbose] Outputs count (list if --verbose) of unrun changesets");
0344: stream
0345: .println(" validate Checks changelog for errors");
0346: stream
0347: .println(" clearCheckSums Removes all saved checksums from database log.");
0348: stream
0349: .println(" Useful for 'MD5Sum Check Failed' errors");
0350: stream
0351: .println(" changelogSync Mark all changes as executed in the database");
0352: stream
0353: .println(" changelogSyncSQL Writes SQL to mark all changes as executed ");
0354: stream
0355: .println(" in the database to STDOUT");
0356: stream
0357: .println(" listLocks Lists who currently has locks on the");
0358: stream.println(" database changelog");
0359: stream
0360: .println(" releaseLocks Releases all locks on the database changelog");
0361: stream
0362: .println(" dropAll Drop all database objects owned by user");
0363: stream.println("");
0364: stream.println("Required Parameters:");
0365: stream
0366: .println(" --changeLogFile=<path and filename> Migration file");
0367: stream
0368: .println(" --username=<value> Database username");
0369: stream
0370: .println(" --password=<value> Database password");
0371: stream
0372: .println(" --url=<value> Database URL");
0373: stream.println("");
0374: stream.println("Optional Parameters:");
0375: stream
0376: .println(" --classpath=<value> Classpath containing");
0377: stream
0378: .println(" migration files and JDBC Driver");
0379: stream
0380: .println(" --driver=<jdbc.driver.ClassName> Database driver class name");
0381: stream
0382: .println(" --databaseClass=<database.ClassName> custom liquibase.database.Database");
0383: stream
0384: .println(" implementation to use");
0385: stream
0386: .println(" --defaultSchemaName=<name> Default database schema to use");
0387: stream
0388: .println(" --contexts=<value> ChangeSet contexts to execute");
0389: stream
0390: .println(" --defaultsFile=</path/to/file.properties> File with default option values");
0391: stream
0392: .println(" (default: ./liquibase.properties)");
0393: stream
0394: .println(" --includeSystemClasspath=<true|false> Include the system classpath");
0395: stream
0396: .println(" in the LiquiBase classpath");
0397: stream
0398: .println(" (default: true)");
0399: stream
0400: .println(" --promptForNonLocalDatabase=<true|false> Prompt if non-localhost");
0401: stream
0402: .println(" databases (default: false)");
0403: stream
0404: .println(" --logLevel=<level> Execution log level");
0405: stream
0406: .println(" (finest, finer, fine, info,");
0407: stream
0408: .println(" warning, severe)");
0409: stream
0410: .println(" --currentDateTimeFunction=<value> Overrides current date time function");
0411: stream
0412: .println(" used in SQL.");
0413: stream
0414: .println(" Useful for unsupported databases");
0415: stream
0416: .println(" --help Prints this message");
0417: stream
0418: .println(" --version Prints this version information");
0419: stream.println("");
0420: stream.println("Required Diff Parameters:");
0421: stream
0422: .println(" --baseUsername=<value> Base Database username");
0423: stream
0424: .println(" --basePassword=<value> Base Database password");
0425: stream
0426: .println(" --baseUrl=<value> Base Database URL");
0427: stream.println("");
0428: stream.println("Optional Diff Parameters:");
0429: stream
0430: .println(" --baseDriver=<jdbc.driver.ClassName> Base Database driver class name");
0431: stream.println("");
0432: stream
0433: .println("Default value for parameters can be stored in a file called");
0434: stream
0435: .println("'liquibase.properties' that is read from the current working directory.");
0436: stream.println("");
0437: stream.println("Full documentation is available at");
0438: stream.println("http://www.liquibase.org/manual/command_line");
0439: stream.println("");
0440: }
0441:
0442: public Main() {
0443: // options = createOptions();
0444: }
0445:
0446: protected void parseOptions(String[] args)
0447: throws CommandLineParsingException {
0448: args = fixupArgs(args);
0449:
0450: boolean seenCommand = false;
0451: for (String arg : args) {
0452: if (isCommand(arg)) {
0453: this .command = arg;
0454: if (this .command.equalsIgnoreCase("migrate")) {
0455: this .command = "update";
0456: } else if (this .command.equalsIgnoreCase("migrateSQL")) {
0457: this .command = "updateSQL";
0458: }
0459: seenCommand = true;
0460: } else if (seenCommand) {
0461: commandParams.add(arg);
0462: } else if (arg.startsWith("--")) {
0463: String[] splitArg = splitArg(arg);
0464:
0465: String attributeName = splitArg[0];
0466: String value = splitArg[1];
0467:
0468: try {
0469: Field field = getClass().getDeclaredField(
0470: attributeName);
0471: if (field.getType().equals(Boolean.class)) {
0472: field.set(this , Boolean.valueOf(value));
0473: } else {
0474: field.set(this , value);
0475: }
0476: } catch (Exception e) {
0477: throw new CommandLineParsingException(
0478: "Unknown parameter: '" + attributeName
0479: + "'");
0480: }
0481: } else {
0482: throw new CommandLineParsingException(
0483: "Parameters must start with a '--'");
0484: }
0485: }
0486:
0487: }
0488:
0489: private String[] splitArg(String arg)
0490: throws CommandLineParsingException {
0491: String[] splitArg = arg.split("=");
0492: if (splitArg.length < 2) {
0493: throw new CommandLineParsingException("Could not parse '"
0494: + arg + "'");
0495: } else if (splitArg.length > 2) {
0496: StringBuffer secondHalf = new StringBuffer();
0497: for (int j = 1; j < splitArg.length; j++) {
0498: secondHalf.append(splitArg[j]).append("=");
0499: }
0500:
0501: splitArg = new String[] { splitArg[0],
0502: secondHalf.toString().replaceFirst("=$", "") };
0503: }
0504:
0505: splitArg[0] = splitArg[0].replaceFirst("--", "");
0506: return splitArg;
0507: }
0508:
0509: protected void applyDefaults() {
0510: if (this .promptForNonLocalDatabase == null) {
0511: this .promptForNonLocalDatabase = Boolean.FALSE;
0512: }
0513: if (this .logLevel == null) {
0514: this .logLevel = "off";
0515: }
0516: if (this .includeSystemClasspath == null) {
0517: this .includeSystemClasspath = Boolean.TRUE;
0518: }
0519:
0520: }
0521:
0522: protected void configureClassLoader()
0523: throws CommandLineParsingException {
0524: final List<URL> urls = new ArrayList<URL>();
0525: if (this .classpath != null) {
0526: String[] classpath;
0527: if (isWindows()) {
0528: classpath = this .classpath.split(";");
0529: } else {
0530: classpath = this .classpath.split(":");
0531: }
0532:
0533: for (String classpathEntry : classpath) {
0534: File classPathFile = new File(classpathEntry);
0535: if (!classPathFile.exists()) {
0536: throw new CommandLineParsingException(classPathFile
0537: .getAbsolutePath()
0538: + " does not exist");
0539: }
0540: try {
0541: if (classpathEntry.endsWith(".war")) {
0542: addWarFileClasspathEntries(classPathFile, urls);
0543: } else if (classpathEntry.endsWith(".ear")) {
0544: JarFile earZip = new JarFile(classPathFile);
0545:
0546: Enumeration<? extends JarEntry> entries = earZip
0547: .entries();
0548: while (entries.hasMoreElements()) {
0549: JarEntry entry = entries.nextElement();
0550: if (entry.getName().toLowerCase().endsWith(
0551: ".jar")) {
0552: File jar = extract(earZip, entry);
0553: urls.add(new URL("jar:" + jar.toURL()
0554: + "!/"));
0555: jar.deleteOnExit();
0556: } else if (entry.getName().toLowerCase()
0557: .endsWith("war")) {
0558: File warFile = extract(earZip, entry);
0559: addWarFileClasspathEntries(warFile,
0560: urls);
0561: }
0562: }
0563:
0564: } else {
0565: urls.add(new File(classpathEntry).toURL());
0566: }
0567: } catch (Exception e) {
0568: throw new CommandLineParsingException(e);
0569: }
0570: }
0571: }
0572: if (includeSystemClasspath) {
0573: classLoader = AccessController
0574: .doPrivileged(new PrivilegedAction<URLClassLoader>() {
0575: public URLClassLoader run() {
0576: return new URLClassLoader(urls
0577: .toArray(new URL[urls.size()]),
0578: Thread.currentThread()
0579: .getContextClassLoader());
0580: }
0581: });
0582:
0583: } else {
0584: classLoader = AccessController
0585: .doPrivileged(new PrivilegedAction<URLClassLoader>() {
0586: public URLClassLoader run() {
0587: return new URLClassLoader(urls
0588: .toArray(new URL[urls.size()]));
0589: }
0590: });
0591: }
0592: }
0593:
0594: private void addWarFileClasspathEntries(File classPathFile,
0595: List<URL> urls) throws IOException {
0596: URL url = new URL("jar:" + classPathFile.toURL()
0597: + "!/WEB-INF/classes/");
0598: urls.add(url);
0599: JarFile warZip = new JarFile(classPathFile);
0600: Enumeration<? extends JarEntry> entries = warZip.entries();
0601: while (entries.hasMoreElements()) {
0602: JarEntry entry = entries.nextElement();
0603: if (entry.getName().startsWith("WEB-INF/lib")
0604: && entry.getName().toLowerCase().endsWith(".jar")) {
0605: File jar = extract(warZip, entry);
0606: urls.add(new URL("jar:" + jar.toURL() + "!/"));
0607: jar.deleteOnExit();
0608: }
0609: }
0610: }
0611:
0612: private File extract(JarFile jar, JarEntry entry)
0613: throws IOException {
0614: // expand to temp dir and add to list
0615: File tempFile = File.createTempFile("liquibase.tmp", null);
0616: // read from jar and write to the tempJar file
0617: BufferedInputStream inStream = null;
0618:
0619: BufferedOutputStream outStream = null;
0620: try {
0621: inStream = new BufferedInputStream(jar
0622: .getInputStream(entry));
0623: outStream = new BufferedOutputStream(new FileOutputStream(
0624: tempFile));
0625: int status;
0626: while ((status = inStream.read()) != -1) {
0627: outStream.write(status);
0628: }
0629: } finally {
0630: if (outStream != null) {
0631: try {
0632: outStream.close();
0633: } catch (IOException ioe) {
0634: ;
0635: }
0636: }
0637: if (inStream != null) {
0638: try {
0639: inStream.close();
0640: } catch (IOException ioe) {
0641: ;
0642: }
0643: }
0644: }
0645:
0646: return tempFile;
0647: }
0648:
0649: protected void doMigration() throws Exception {
0650: if ("help".equalsIgnoreCase(command)) {
0651: printHelp(System.out);
0652: return;
0653: }
0654:
0655: if ("finest".equalsIgnoreCase(logLevel)) {
0656: LogFactory.getLogger().setLevel(Level.FINEST);
0657: } else if ("finer".equalsIgnoreCase(logLevel)) {
0658: LogFactory.getLogger().setLevel(Level.FINER);
0659: } else if ("fine".equalsIgnoreCase(logLevel)) {
0660: LogFactory.getLogger().setLevel(Level.FINE);
0661: } else if ("info".equalsIgnoreCase(logLevel)) {
0662: LogFactory.getLogger().setLevel(Level.INFO);
0663: } else if ("warning".equalsIgnoreCase(logLevel)) {
0664: LogFactory.getLogger().setLevel(Level.WARNING);
0665: } else if ("severe".equalsIgnoreCase(logLevel)) {
0666: LogFactory.getLogger().setLevel(Level.SEVERE);
0667: } else if ("off".equalsIgnoreCase(logLevel)) {
0668: LogFactory.getLogger().setLevel(Level.OFF);
0669: } else {
0670: throw new CommandLineParsingException("Unknown log level: "
0671: + logLevel);
0672: }
0673:
0674: FileSystemFileOpener fsOpener = new FileSystemFileOpener();
0675: CommandLineFileOpener clOpener = new CommandLineFileOpener(
0676: classLoader);
0677: Driver driver;
0678: DatabaseFactory databaseFactory = DatabaseFactory.getInstance();
0679: if (this .databaseClass != null) {
0680: databaseFactory.addDatabaseImplementation((Database) Class
0681: .forName(this .databaseClass, true, classLoader)
0682: .newInstance());
0683: }
0684:
0685: try {
0686: if (this .driver == null) {
0687: this .driver = databaseFactory.findDefaultDriver(url);
0688: }
0689:
0690: if (this .driver == null) {
0691: throw new RuntimeException(
0692: "Driver class was not specified and could not be determined from the url");
0693: }
0694:
0695: driver = (Driver) Class.forName(this .driver, true,
0696: classLoader).newInstance();
0697: } catch (Exception e) {
0698: throw new RuntimeException("Cannot find database driver: "
0699: + e.getMessage());
0700: }
0701: Properties info = new Properties();
0702: info.put("user", username);
0703: if (password != null) {
0704: info.put("password", password);
0705: }
0706:
0707: Connection connection = driver.connect(url, info);
0708: if (connection == null) {
0709: throw new JDBCException(
0710: "Connection could not be created to "
0711: + url
0712: + " with driver "
0713: + driver.getClass().getName()
0714: + ". Possibly the wrong driver for the given database URL");
0715: }
0716:
0717: try {
0718: Database database = databaseFactory
0719: .findCorrectDatabaseImplementation(connection);
0720: database.setDefaultSchemaName(StringUtils
0721: .trimToNull(defaultSchemaName));
0722:
0723: if ("diff".equalsIgnoreCase(command)) {
0724: doDiff(database,
0725: createDatabaseFromCommandParams(commandParams));
0726: return;
0727: } else if ("diffChangeLog".equalsIgnoreCase(command)) {
0728: doDiffToChangeLog(database,
0729: createDatabaseFromCommandParams(commandParams));
0730: return;
0731: } else if ("generateChangeLog".equalsIgnoreCase(command)) {
0732: doGenerateChangeLog(database);
0733: return;
0734: }
0735:
0736: Liquibase liquibase = new Liquibase(changeLogFile,
0737: new CompositeFileOpener(fsOpener, clOpener),
0738: database);
0739:
0740: if ("listLocks".equalsIgnoreCase(command)) {
0741: liquibase.reportLocks(System.out);
0742: return;
0743: } else if ("releaseLocks".equalsIgnoreCase(command)) {
0744: LockHandler.getInstance(database).forceReleaseLock();
0745: System.out
0746: .println("Successfully released all database change log locks for "
0747: + liquibase.getDatabase()
0748: .getConnectionUsername()
0749: + "@"
0750: + liquibase.getDatabase()
0751: .getConnectionURL());
0752: return;
0753: } else if ("tag".equalsIgnoreCase(command)) {
0754: liquibase.tag(commandParams.iterator().next());
0755: System.out.println("Successfully tagged "
0756: + liquibase.getDatabase()
0757: .getConnectionUsername() + "@"
0758: + liquibase.getDatabase().getConnectionURL());
0759: return;
0760: } else if ("dropAll".equals(command)) {
0761: liquibase.dropAll();
0762: System.out.println("All objects dropped from "
0763: + liquibase.getDatabase()
0764: .getConnectionUsername() + "@"
0765: + liquibase.getDatabase().getConnectionURL());
0766: return;
0767: } else if ("status".equalsIgnoreCase(command)) {
0768: boolean runVerbose = false;
0769:
0770: if (commandParams.contains("--verbose")) {
0771: runVerbose = true;
0772: }
0773: liquibase.reportStatus(runVerbose, contexts,
0774: getOutputWriter());
0775: return;
0776: } else if ("validate".equalsIgnoreCase(command)) {
0777: try {
0778: liquibase.validate();
0779: } catch (ValidationFailedException e) {
0780: e.printDescriptiveError(System.out);
0781: return;
0782: }
0783: System.out.println("No validation errors found");
0784: return;
0785: } else if ("clearCheckSums".equalsIgnoreCase(command)) {
0786: liquibase.clearCheckSums();
0787: return;
0788: } else if ("dbdoc".equalsIgnoreCase(command)) {
0789: if (commandParams.size() == 0) {
0790: throw new CommandLineParsingException(
0791: "dbdoc requires an output directory");
0792: }
0793: if (changeLogFile == null) {
0794: throw new CommandLineParsingException(
0795: "dbdoc requires a changeLog parameter");
0796: }
0797: liquibase.generateDocumentation(commandParams
0798: .iterator().next());
0799: return;
0800: }
0801:
0802: DateFormat dateFormat = new SimpleDateFormat(
0803: "yyyy-MM-dd HH:mm:ss");
0804: try {
0805: if ("update".equalsIgnoreCase(command)) {
0806: liquibase.update(contexts);
0807: } else if ("changelogSync".equalsIgnoreCase(command)) {
0808: liquibase.changeLogSync(contexts);
0809: } else if ("changelogSyncSQL".equalsIgnoreCase(command)) {
0810: liquibase
0811: .changeLogSync(contexts, getOutputWriter());
0812: } else if ("updateCount".equalsIgnoreCase(command)) {
0813: liquibase.update(Integer.parseInt(commandParams
0814: .iterator().next()), contexts);
0815: } else if ("updateCountSQL".equalsIgnoreCase(command)) {
0816: liquibase.update(Integer.parseInt(commandParams
0817: .iterator().next()), contexts,
0818: getOutputWriter());
0819: } else if ("updateSQL".equalsIgnoreCase(command)) {
0820: liquibase.update(contexts, getOutputWriter());
0821: } else if ("rollback".equalsIgnoreCase(command)) {
0822: if (commandParams == null) {
0823: throw new CommandLineParsingException(
0824: "rollback requires a rollback tag");
0825: }
0826: liquibase.rollback(commandParams.iterator().next(),
0827: contexts);
0828: } else if ("rollbackToDate".equalsIgnoreCase(command)) {
0829: if (commandParams == null) {
0830: throw new CommandLineParsingException(
0831: "rollback requires a rollback date");
0832: }
0833: liquibase.rollback(dateFormat.parse(commandParams
0834: .iterator().next()), contexts);
0835: } else if ("rollbackCount".equalsIgnoreCase(command)) {
0836: liquibase.rollback(Integer.parseInt(commandParams
0837: .iterator().next()), contexts);
0838:
0839: } else if ("rollbackSQL".equalsIgnoreCase(command)) {
0840: if (commandParams == null) {
0841: throw new CommandLineParsingException(
0842: "rollbackSQL requires a rollback tag");
0843: }
0844: liquibase.rollback(commandParams.iterator().next(),
0845: contexts, getOutputWriter());
0846: } else if ("rollbackToDateSQL"
0847: .equalsIgnoreCase(command)) {
0848: if (commandParams == null) {
0849: throw new CommandLineParsingException(
0850: "rollbackToDateSQL requires a rollback date");
0851: }
0852: liquibase.rollback(dateFormat.parse(commandParams
0853: .iterator().next()), contexts,
0854: getOutputWriter());
0855: } else if ("rollbackCountSQL".equalsIgnoreCase(command)) {
0856: if (commandParams == null) {
0857: throw new CommandLineParsingException(
0858: "rollbackCountSQL requires a rollback tag");
0859: }
0860:
0861: liquibase.rollback(Integer.parseInt(commandParams
0862: .iterator().next()), contexts,
0863: getOutputWriter());
0864: } else if ("futureRollbackSQL"
0865: .equalsIgnoreCase(command)) {
0866: liquibase.futureRollbackSQL(contexts,
0867: getOutputWriter());
0868: } else {
0869: throw new CommandLineParsingException(
0870: "Unknown command: " + command);
0871: }
0872: } catch (ParseException e) {
0873: throw new CommandLineParsingException(
0874: "Unexpected date/time format. Use 'yyyy-MM-dd HH:mm:ss'");
0875: }
0876: } finally {
0877: try {
0878: connection.rollback();
0879: connection.close();
0880: } catch (SQLException e) {
0881: LogFactory.getLogger().log(Level.WARNING,
0882: "problem closing connection", e);
0883: }
0884: }
0885: }
0886:
0887: private Database createDatabaseFromCommandParams(
0888: Set<String> commandParams)
0889: throws CommandLineParsingException, JDBCException {
0890: String driver = null;
0891: String url = null;
0892: String username = null;
0893: String password = null;
0894: String defaultSchemaName = this .defaultSchemaName;
0895:
0896: for (String param : commandParams) {
0897: String[] splitArg = splitArg(param);
0898:
0899: String attributeName = splitArg[0];
0900: String value = splitArg[1];
0901: if ("baseDriver".equalsIgnoreCase(attributeName)) {
0902: driver = value;
0903: } else if ("baseUrl".equalsIgnoreCase(attributeName)) {
0904: url = value;
0905: } else if ("baseUsername".equalsIgnoreCase(attributeName)) {
0906: username = value;
0907: } else if ("basePassword".equalsIgnoreCase(attributeName)) {
0908: password = value;
0909: } else if ("baseDefaultSchemaName"
0910: .equalsIgnoreCase(attributeName)) {
0911: defaultSchemaName = value;
0912: }
0913: }
0914:
0915: if (driver == null) {
0916: driver = DatabaseFactory.getInstance().findDefaultDriver(
0917: url);
0918: }
0919:
0920: Driver driverObject;
0921: try {
0922: driverObject = (Driver) Class.forName(driver, true,
0923: classLoader).newInstance();
0924: } catch (Exception e) {
0925: throw new RuntimeException("Cannot find database driver: "
0926: + e.getMessage());
0927: }
0928:
0929: Properties info = new Properties();
0930: info.put("user", username);
0931: info.put("password", password);
0932:
0933: Connection connection;
0934: try {
0935: connection = driverObject.connect(url, info);
0936: } catch (SQLException e) {
0937: throw new JDBCException(
0938: "Connection could not be created to " + url + ": "
0939: + e.getMessage(), e);
0940: }
0941: if (connection == null) {
0942: throw new JDBCException(
0943: "Connection could not be created to "
0944: + url
0945: + " with driver "
0946: + driver.getClass().getName()
0947: + ". Possibly the wrong driver for the given database URL");
0948: }
0949:
0950: Database database = DatabaseFactory.getInstance()
0951: .findCorrectDatabaseImplementation(connection);
0952: database.setDefaultSchemaName(defaultSchemaName);
0953:
0954: return database;
0955: }
0956:
0957: private Writer getOutputWriter() {
0958: return new OutputStreamWriter(System.out);
0959: }
0960:
0961: public boolean isWindows() {
0962: return System.getProperty("os.name").startsWith("Windows ");
0963: }
0964:
0965: private void doDiff(Database baseDatabase, Database targetDatabase)
0966: throws JDBCException {
0967: Diff diff = new Diff(baseDatabase, targetDatabase);
0968: diff.addStatusListener(new OutDiffStatusListener());
0969: DiffResult diffResult = diff.compare();
0970:
0971: System.out.println("");
0972: System.out.println("Diff Results:");
0973: diffResult.printResult(System.out);
0974: }
0975:
0976: private void doDiffToChangeLog(Database baseDatabase,
0977: Database targetDatabase) throws JDBCException, IOException,
0978: ParserConfigurationException {
0979: Diff diff = new Diff(baseDatabase, targetDatabase);
0980: diff.addStatusListener(new OutDiffStatusListener());
0981: DiffResult diffResult = diff.compare();
0982:
0983: diffResult.printChangeLog(System.out, targetDatabase);
0984: }
0985:
0986: private void doGenerateChangeLog(Database originalDatabase)
0987: throws JDBCException, IOException,
0988: ParserConfigurationException {
0989: Diff diff = new Diff(originalDatabase, defaultSchemaName);
0990: diff.addStatusListener(new OutDiffStatusListener());
0991: DiffResult diffResult = diff.compare();
0992:
0993: PrintStream outputStream = System.out;
0994:
0995: if (StringUtils.trimToNull(changeLogFile) != null) {
0996: File changeFile = new File(changeLogFile);
0997: outputStream = new PrintStream(changeFile);
0998: }
0999: diffResult.printChangeLog(outputStream, originalDatabase);
1000: }
1001:
1002: private static class OutDiffStatusListener implements
1003: DiffStatusListener {
1004: public void statusUpdate(String message) {
1005: System.err.println(message);
1006: }
1007: }
1008: }
|