You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@ibatis.apache.org by cb...@apache.org on 2008/08/22 23:48:31 UTC

svn commit: r688202 - in /ibatis/trunk/java/ibatis-3/ibatis-3-core/src/main/java/org/apache/ibatis/migration: ./ commands/

Author: cbegin
Date: Fri Aug 22 14:48:30 2008
New Revision: 688202

URL: http://svn.apache.org/viewvc?rev=688202&view=rev
Log:
Split migrator into command pattern.

Added:
    ibatis/trunk/java/ibatis-3/ibatis-3-core/src/main/java/org/apache/ibatis/migration/commands/
    ibatis/trunk/java/ibatis-3/ibatis-3-core/src/main/java/org/apache/ibatis/migration/commands/BaseCommand.java
    ibatis/trunk/java/ibatis-3/ibatis-3-core/src/main/java/org/apache/ibatis/migration/commands/Command.java
    ibatis/trunk/java/ibatis-3/ibatis-3-core/src/main/java/org/apache/ibatis/migration/commands/InitializeCommand.java
    ibatis/trunk/java/ibatis-3/ibatis-3-core/src/main/java/org/apache/ibatis/migration/commands/NewCommand.java
    ibatis/trunk/java/ibatis-3/ibatis-3-core/src/main/java/org/apache/ibatis/migration/commands/RunCommand.java
    ibatis/trunk/java/ibatis-3/ibatis-3-core/src/main/java/org/apache/ibatis/migration/commands/StatusCommand.java
    ibatis/trunk/java/ibatis-3/ibatis-3-core/src/main/java/org/apache/ibatis/migration/commands/UndoCommand.java
    ibatis/trunk/java/ibatis-3/ibatis-3-core/src/main/java/org/apache/ibatis/migration/commands/VersionCommand.java
Modified:
    ibatis/trunk/java/ibatis-3/ibatis-3-core/src/main/java/org/apache/ibatis/migration/CommandLine.java
    ibatis/trunk/java/ibatis-3/ibatis-3-core/src/main/java/org/apache/ibatis/migration/Migrator.java
    ibatis/trunk/java/ibatis-3/ibatis-3-core/src/main/java/org/apache/ibatis/migration/ScriptRunner.java

Modified: ibatis/trunk/java/ibatis-3/ibatis-3-core/src/main/java/org/apache/ibatis/migration/CommandLine.java
URL: http://svn.apache.org/viewvc/ibatis/trunk/java/ibatis-3/ibatis-3-core/src/main/java/org/apache/ibatis/migration/CommandLine.java?rev=688202&r1=688201&r2=688202&view=diff
==============================================================================
--- ibatis/trunk/java/ibatis-3/ibatis-3-core/src/main/java/org/apache/ibatis/migration/CommandLine.java (original)
+++ ibatis/trunk/java/ibatis-3/ibatis-3-core/src/main/java/org/apache/ibatis/migration/CommandLine.java Fri Aug 22 14:48:30 2008
@@ -1,143 +1,90 @@
 package org.apache.ibatis.migration;
 
+import org.apache.ibatis.migration.commands.*;
+
+import java.io.File;
+import java.io.PrintStream;
 import java.util.Arrays;
-import java.util.Set;
 import java.util.Collections;
 import java.util.HashSet;
-import java.io.File;
-import java.io.PrintStream;
-import java.math.BigInteger;
+import java.util.Set;
 
 public class CommandLine {
 
+  protected static final PrintStream out = System.out;
+
   private static final String PATH_PREFIX = "--path=";
   private static final String ENV_PREFIX = "--env=";
   private static final String FORCE = "--force";
   private static final String HELP = "--help";
-  private static final String STATUS = "status";
+
   private static final String INIT = "init";
   private static final String NEW = "new";
   private static final String RUN = "run";
   private static final String VERSION = "version";
   private static final String UNDO = "undo";
+  private static final String STATUS = "status";
+
   private static final Set<String> KNOWN_COMMANDS = Collections.unmodifiableSet(
       new HashSet<String>(Arrays.asList(INIT, NEW, RUN, VERSION, UNDO, STATUS)));
 
-  private String repository;
+  private File repository;
   private String environment;
+  private boolean force;
+
   private String command;
   private String params;
+
   private String parseError;
   private boolean help;
-  private boolean force;
 
   public CommandLine(String[] args) {
     parse(args);
     validate();
   }
 
-  public String getRepository() {
-    return repository;
-  }
-
-  public String getEnvironment() {
-    return environment;
-  }
-
-  public String getCommand() {
-    return command;
-  }
-
-  public String getParams() {
-    return params;
-  }
-
-  public boolean isHelp() {
-    return help;
-  }
-
-  public boolean isValid() {
-    return parseError == null;
-  }
-
-  public String toString() {
-    return repository + " " + environment + " " + command + " " + (params == null ? "" : params);
-  }
-
   public void execute() {
-    if (isHelp()) {
-      printUsage();
-    } else if (!isValid()) {
-      printError();
-      printUsage();
-    } else {
-      try {
-        runCommand();
-      } catch (MigrationException e) {
-        System.err.println(e.getMessage());
+    try {
+      if (help) {
+        printUsage();
+      } else if (parseError != null) {
+        printError();
         printUsage();
+      } else {
+        try {
+          runCommand();
+        } catch (MigrationException e) {
+          out.println("ERROR: " + e.getMessage());
+          printUsage();
+        }
       }
+    } finally {
+      out.flush();
     }
   }
 
   private void runCommand() {
-    Migrator migrator = new Migrator(repository, environment, force);
     if (INIT.equals(command)) {
-      migrator.initialize();
+     new InitializeCommand(repository,environment,force).execute(params);
     } else if (NEW.equals(command)) {
-      migrator.newMigration(params);
+      new NewCommand(repository,environment,force).execute(params);
     } else if (STATUS.equals(command)) {
-      migrator.printStatus();
+      new StatusCommand(repository,environment,force).execute(params);
     } else if (RUN.equals(command)) {
-      migrator.runPendingMigrations();
+      new RunCommand(repository,environment,force).execute(params);
     } else if (VERSION.equals(command)) {
-      BigInteger version = null;
-      try {
-        version = new BigInteger(params);
-      } catch(Exception e) {
-        System.err.println("Invalid version number specified: " + params);
-        printUsage();
-      }
-      migrator.migrateToVersion(version);
+      new VersionCommand(repository,environment,force).execute(params);
     } else if (UNDO.equals(command)) {
-      migrator.undoLastMigration();
+      new UndoCommand(repository,environment,force).execute(params);
     } else {
       throw new RuntimeException("Attempt to execute unkown command.");
     }
   }
 
-  public void printError() {
-    PrintStream out = System.err;
-    out.println(parseError);
-    out.flush();
-  }
-
-  public void printUsage() {
-    PrintStream out = System.out;
-    out.println();
-    out.println("Usage: migrate command [parameter] [--path=<directory>] [--env=<environment>]");
-    out.println();
-    out.println("--path=<directory>   Path to repository.  Default current working directory.");
-    out.println("--env=<environment>  Environment to configure. Default environment is 'development'.");
-    out.println("--env=<environment>  Environment to configure. Default environment is 'development'.");
-    out.println("--force              Forces script to continue even if SQL errors are encountered.");
-    out.println("--help               Displays this usage message.");
-    out.println();
-    out.println("Commands:");
-    out.println("  init               Creates (if necessary) and initializes a migration path.");
-    out.println("  new <description>  Creates a new migration with the provided description.");
-    out.println("  run                Run all unapplied migrations.");
-    out.println("  version <version>  Migrates the database up or down to the specified version.");
-    out.println("  undo               Undoes the last migration applied to the database.");
-    out.println("  status             Prints the changelog from the database if the changelog table exists.");
-    out.println();
-    out.flush();
-  }
-
   private void parse(String[] args) {
     for (String arg : args) {
       if (arg.startsWith(PATH_PREFIX) && arg.length() > PATH_PREFIX.length()) {
-        repository = arg.split("=")[1];
+        repository = new File(arg.split("=")[1]);
       } else if (arg.startsWith(ENV_PREFIX) && arg.length() > ENV_PREFIX.length()) {
         environment = arg.split("=")[1];
       } else if (arg.startsWith(FORCE)) {
@@ -157,16 +104,15 @@
 
   private void validate() {
     if (repository == null) {
-      repository = "./";
+      repository = new File("./");
     }
     if (environment == null) {
       environment = "development";
     }
-    File f = new File(repository);
-    if (f.exists() && !f.isDirectory()) {
-      parseError = ("Migrations path must be a directory: " + f.getAbsolutePath());
+    if (repository.exists() && !repository.isDirectory()) {
+      parseError = ("Migrations path must be a directory: " + repository.getAbsolutePath());
     } else {
-      repository = f.getAbsolutePath();
+      repository = new File(repository.getAbsolutePath());
       if (command == null) {
         parseError = "No command specified.";
       } else {
@@ -177,4 +123,29 @@
     }
   }
 
+  private void printError() {
+    out.println(parseError);
+    out.flush();
+  }
+
+  private void printUsage() {
+    out.println();
+    out.println("Usage: migrate command [parameter] [--path=<directory>] [--env=<environment>]");
+    out.println();
+    out.println("--path=<directory>   Path to repository.  Default current working directory.");
+    out.println("--env=<environment>  Environment to configure. Default environment is 'development'.");
+    out.println("--force              Forces script to continue even if SQL errors are encountered.");
+    out.println("--help               Displays this usage message.");
+    out.println();
+    out.println("Commands:");
+    out.println("  init               Creates (if necessary) and initializes a migration path.");
+    out.println("  new <description>  Creates a new migration with the provided description.");
+    out.println("  run                Run all unapplied migrations.");
+    out.println("  version <version>  Migrates the database up or down to the specified version.");
+    out.println("  undo               Undoes the last migration applied to the database.");
+    out.println("  status             Prints the changelog from the database if the changelog table exists.");
+    out.println();
+    out.flush();
+  }
+
 }
\ No newline at end of file

Modified: ibatis/trunk/java/ibatis-3/ibatis-3-core/src/main/java/org/apache/ibatis/migration/Migrator.java
URL: http://svn.apache.org/viewvc/ibatis/trunk/java/ibatis-3/ibatis-3-core/src/main/java/org/apache/ibatis/migration/Migrator.java?rev=688202&r1=688201&r2=688202&view=diff
==============================================================================
--- ibatis/trunk/java/ibatis-3/ibatis-3-core/src/main/java/org/apache/ibatis/migration/Migrator.java (original)
+++ ibatis/trunk/java/ibatis-3/ibatis-3-core/src/main/java/org/apache/ibatis/migration/Migrator.java Fri Aug 22 14:48:30 2008
@@ -1,340 +1,9 @@
 package org.apache.ibatis.migration;
 
-import org.apache.ibatis.io.Resources;
-import org.apache.ibatis.adhoc.AdHocExecutor;
-
-import java.math.BigInteger;
-import java.math.BigDecimal;
-import java.io.*;
-import java.text.SimpleDateFormat;
-import java.sql.Date;
-import java.sql.SQLException;
-import java.util.*;
-
 public class Migrator {
 
-  private File repository;
-  private String environment;
-  private boolean force;
-  private PrintStream out = System.out;
-
-  public Migrator(String repository, String environment, boolean force) {
-    this.repository = new File(repository);
-    this.environment = environment;
-    this.force = force;
-  }
-
   public static void main(String[] args) throws Exception {
     new CommandLine(args).execute();
   }
 
-  public void initialize() {
-    createDirectoryIfNecessary(repository);
-    ensureDirectoryIsEmpty(repository);
-    out.println("Initializing: " + repository);
-    copyResourceTo("org/apache/ibatis/migration/template_environment.properties", environmentFile());
-    copyResourceTo("org/apache/ibatis/migration/template_changelog.sql", repositoryFile(getTimestampAsString() + "_create_changelog.sql"));
-    copyResourceTo("org/apache/ibatis/migration/template_migration.sql", repositoryFile(getTimestampAsString() + "_first_migration.sql"));
-    out.println("Done!");
-  }
-
-  public void newMigration(String description) {
-    if (description == null) {
-      throw new MigrationException("No description specified for new migration.");
-    }
-    Map<String, String> variables = new HashMap<String, String>();
-    variables.put("description", description);
-    existingEnvironmentFile();
-    String filename = getTimestampAsString() + "_" + description.replace(' ', '_') + ".sql";
-    copyResourceTo("org/apache/ibatis/migration/template_migration.sql", repositoryFile(filename), variables);
-    out.println("Done!");
-  }
-
-  public void runPendingMigrations() {
-    try {
-      String[] filenames = repository.list();
-      Arrays.sort(filenames);
-      for (String filename : filenames) {
-        if (filename.endsWith(".sql")) {
-          out.println(horizontalLine("Applying: " + filename, 80));
-          ScriptRunner runner = getScriptRunner();
-          runner.runScript(new MigrationReader(new FileReader(repositoryFile(filename)), false));
-          Change change = parseChangeFromFilename(filename);
-          insertChangelog(change);
-        }
-      }
-    } catch (Exception e) {
-      throw new RuntimeException("<Description>.  Cause: " + e, e);
-    }
-  }
-
-  public void migrateToVersion(BigInteger version) {
-    out.println("not implemented");
-  }
-
-  public void undoLastMigration() {
-    try {
-      String[] filenames = repository.list();
-      reverse(filenames);
-      Change lastChange = getLastChange();
-      for (String filename : filenames) {
-        if (filename.endsWith(".sql")) {
-          Change change = parseChangeFromFilename(filename);
-          if (change.getId().equals(lastChange.getId())) {
-            out.println(horizontalLine("Undoing: " + filename, 80));
-            ScriptRunner runner = getScriptRunner();
-            runner.runScript(new MigrationReader(new FileReader(repositoryFile(filename)), true));
-            if (changelogExists()) {
-              deleteChange(change);
-            } else {
-              out.println("Changelog doesn't exist. No further migrations will be undone (normal for the last migration).");
-            }
-            break;            
-          }
-        }
-      }
-    } catch (Exception e) {
-      throw new RuntimeException("<Description>.  Cause: " + e, e);
-    }
-  }
-
-  public void printStatus() {
-    if (changelogExists()) {
-      List<Change> changelog = getChangelog();
-      out.println("ID             TIMESTAMP             DESCRIPTION");
-      out.println(horizontalLine("", 60));
-      for (Change change : changelog) {
-        out.println(change);
-      }
-    } else {
-      out.println("Changelog does not exist.");
-    }
-  }
-
-  private void reverse(Comparable[] comparable) {
-    Arrays.sort(comparable, new Comparator() {
-      public int compare(Object o1, Object o2) {
-        return ((Comparable) o2).compareTo(o1);
-      }
-    });
-  }
-
-  private void insertChangelog(Change change) {
-    AdHocExecutor executor = getAdHocExecutor();
-    try {
-      executor.insert("insert into CHANGELOG (ID, DESCRIPTION) values (?,?)", change.getId(), change.getDescription());
-    } catch (SQLException e) {
-      throw new MigrationException("Error querying last applied migration.  Cause: " + e, e);
-    } finally {
-      executor.closeConnection();
-    }
-  }
-
-  private void deleteChange(Change change) {
-    AdHocExecutor executor = getAdHocExecutor();
-    try {
-      executor.delete("delete from CHANGELOG where id = ?", change.getId());
-    } catch (SQLException e) {
-      throw new MigrationException("Error querying last applied migration.  Cause: " + e, e);
-    } finally {
-      executor.closeConnection();
-    }
-  }
-
-  private Change parseChangeFromFilename(String filename) {
-    try {
-      Change change = new Change();
-      String[] parts = filename.split("\\.")[0].split("_");
-      change.setId(new BigDecimal(parts[0]));
-      StringBuilder builder = new StringBuilder();
-      for (int i = 1; i < parts.length; i++) {
-        if (i > 1) builder.append(" ");
-        builder.append(parts[i]);
-      }
-      change.setDescription(builder.toString());
-      return change;
-    } catch (Exception e) {
-      throw new MigrationException("Error parsing change from file.  Cause: " + e, e);
-    }
-  }
-
-  private List<Change> getChangelog() {
-    AdHocExecutor executor = getAdHocExecutor();
-    try {
-      List<Map<String, Object>> changelog = executor.selectAll("select ID, DESCRIPTION from CHANGELOG order by id");
-      List<Change> changes = new ArrayList<Change>();
-      for (Map<String, Object> change : changelog) {
-        changes.add(new Change(new BigDecimal(change.get("ID").toString()), change.get("DESCRIPTION").toString()));
-      }
-      return changes;
-    } catch (SQLException e) {
-      throw new MigrationException("Error querying last applied migration.  Cause: " + e, e);
-    } finally {
-      executor.closeConnection();
-    }
-  }
-
-  private Change getLastChange() {
-    List<Change> changelog = getChangelog();
-    return changelog.get(changelog.size() - 1);
-  }
-
-  private boolean changelogExists() {
-    AdHocExecutor executor = getAdHocExecutor();
-    try {
-      executor.selectAll("select ID, DESCRIPTION from CHANGELOG");
-      return true;
-    } catch (SQLException e) {
-      return false;
-    } finally {
-      executor.closeConnection();
-    }
-  }
-
-  private String horizontalLine(String caption, int length) {
-    StringBuilder builder = new StringBuilder();
-    builder.append("==========");
-    if (caption.length() > 0) {
-      caption = " " + caption + " ";
-      builder.append(caption);
-    }
-    for (int i = 0; i < length - caption.length(); i++) {
-      builder.append("=");
-    }
-    return builder.toString();
-  }
-
-  private String getTimestampAsString() {
-    try {
-      // Ensure that two subsequent calls are less likely to return the same value.
-      Thread.sleep(1000);
-    } catch (InterruptedException e) {
-      //ignore
-    }
-    return new SimpleDateFormat("yyyyMMddHHmmss").format(new Date(System.currentTimeMillis()));
-  }
-
-  private File repositoryFile(String fileName) {
-    return new File(repository.getAbsolutePath() + File.separator + fileName);
-  }
-
-  private void copyResourceTo(String resource, File toFile) {
-    copyResourceTo(resource, toFile, null);
-  }
-
-  private void copyResourceTo(String resource, File toFile, Map<String, String> variables) {
-    out.println("Creating: " + toFile.getName());
-    try {
-      LineNumberReader reader = new LineNumberReader(Resources.getResourceAsReader(this.getClass().getClassLoader(), resource));
-      try {
-        PrintWriter writer = new PrintWriter(new FileWriter(toFile));
-        try {
-          String line;
-          while ((line = reader.readLine()) != null) {
-            line = parsePlaceholders(line, variables);
-            writer.println(line);
-          }
-        } finally {
-          writer.close();
-        }
-      } finally {
-        reader.close();
-      }
-    } catch (IOException e) {
-      throw new MigrationException("Error copying " + resource + " to " + toFile.getAbsolutePath() + ".  Cause: " + e, e);
-    }
-  }
-
-  private void ensureDirectoryIsEmpty(File path) {
-    String[] list = path.list();
-    if (list.length != 0) {
-      for (String entry : list) {
-        if (!entry.startsWith(".")) {
-          throw new MigrationException("Directory must be empty (.svn etc allowed): " + path.getAbsolutePath());
-        }
-      }
-    }
-  }
-
-  private void createDirectoryIfNecessary(File path) {
-    if (!path.exists()) {
-      File parent = new File(path.getParent());
-      createDirectoryIfNecessary(parent);
-      if (!path.mkdir()) {
-        throw new MigrationException("Could not create directory path for an unknown reason. Make sure you have access to the directory.");
-      }
-    }
-  }
-
-  private String parsePlaceholders(String string, Map<String, String> variables) {
-    final String OPEN = "${";
-    final String CLOSE = "}";
-    String newString = string;
-    if (newString != null && variables != null) {
-      int start = newString.indexOf(OPEN);
-      int end = newString.indexOf(CLOSE);
-
-      while (start > -1 && end > start) {
-        String prepend = newString.substring(0, start);
-        String append = newString.substring(end + CLOSE.length());
-        String propName = newString.substring(start + OPEN.length(), end);
-        String propValue = variables.get(propName);
-        if (propValue == null) {
-          newString = prepend + append;
-        } else {
-          newString = prepend + propValue + append;
-        }
-        start = newString.indexOf(OPEN);
-        end = newString.indexOf(CLOSE);
-      }
-    }
-    return newString;
-  }
-
-  private AdHocExecutor getAdHocExecutor() {
-    Properties props = getEnvironmentProperties();
-    String driver = props.getProperty("driver");
-    String url = props.getProperty("url");
-    String username = props.getProperty("username");
-    String password = props.getProperty("password");
-    AdHocExecutor executor = new AdHocExecutor(driver, url, username, password, false);
-    return executor;
-  }
-
-  private ScriptRunner getScriptRunner() {
-    try {
-      Properties props = getEnvironmentProperties();
-      String driver = props.getProperty("driver");
-      String url = props.getProperty("url");
-      String username = props.getProperty("username");
-      String password = props.getProperty("password");
-      return new ScriptRunner(driver, url, username, password, false, !force);
-    } catch (Exception e) {
-      throw new MigrationException("Error creating ScriptRunner.  Cause: " + e, e);
-    }
-  }
-
-  private File environmentFile() {
-    return repositoryFile(environment + ".properties");
-  }
-
-  private File existingEnvironmentFile() {
-    File envFile = environmentFile();
-    if (!envFile.exists()) {
-      throw new MigrationException("Environment file missing: " + envFile.getAbsolutePath());
-    }
-    return envFile;
-  }
-
-  private Properties getEnvironmentProperties() {
-    try {
-      File file = existingEnvironmentFile();
-      Properties props = new Properties();
-      props.load(new FileInputStream(file));
-      return props;
-    } catch (IOException e) {
-      throw new MigrationException("Error loading environment properties.  Cause: " + e, e);
-    }
-  }
-
 }

Modified: ibatis/trunk/java/ibatis-3/ibatis-3-core/src/main/java/org/apache/ibatis/migration/ScriptRunner.java
URL: http://svn.apache.org/viewvc/ibatis/trunk/java/ibatis-3/ibatis-3-core/src/main/java/org/apache/ibatis/migration/ScriptRunner.java?rev=688202&r1=688201&r2=688202&view=diff
==============================================================================
--- ibatis/trunk/java/ibatis-3/ibatis-3-core/src/main/java/org/apache/ibatis/migration/ScriptRunner.java (original)
+++ ibatis/trunk/java/ibatis-3/ibatis-3-core/src/main/java/org/apache/ibatis/migration/ScriptRunner.java Fri Aug 22 14:48:30 2008
@@ -5,9 +5,6 @@
 import java.io.*;
 import java.sql.*;
 
-/**
- * Utility to run database scripts
- */
 public class ScriptRunner {
 
   private static final String DEFAULT_DELIMITER = ";";

Added: ibatis/trunk/java/ibatis-3/ibatis-3-core/src/main/java/org/apache/ibatis/migration/commands/BaseCommand.java
URL: http://svn.apache.org/viewvc/ibatis/trunk/java/ibatis-3/ibatis-3-core/src/main/java/org/apache/ibatis/migration/commands/BaseCommand.java?rev=688202&view=auto
==============================================================================
--- ibatis/trunk/java/ibatis-3/ibatis-3-core/src/main/java/org/apache/ibatis/migration/commands/BaseCommand.java (added)
+++ ibatis/trunk/java/ibatis-3/ibatis-3-core/src/main/java/org/apache/ibatis/migration/commands/BaseCommand.java Fri Aug 22 14:48:30 2008
@@ -0,0 +1,208 @@
+package org.apache.ibatis.migration.commands;
+
+import org.apache.ibatis.migration.Change;
+import org.apache.ibatis.migration.MigrationException;
+import org.apache.ibatis.migration.ScriptRunner;
+import org.apache.ibatis.adhoc.AdHocExecutor;
+import org.apache.ibatis.io.Resources;
+
+import java.util.*;
+import java.sql.*;
+import java.math.BigDecimal;
+import java.text.SimpleDateFormat;
+import java.io.*;
+
+public abstract class BaseCommand implements Command {
+
+  protected static final PrintStream out = System.out;
+
+  protected File repository;
+  protected String environment;
+  protected boolean force;
+
+  protected BaseCommand(File repository, String environment, boolean force) {
+    this.repository = repository;
+    this.environment = environment;
+    this.force = force;
+  }
+
+
+  protected Change parseChangeFromFilename(String filename) {
+    try {
+      Change change = new Change();
+      String[] parts = filename.split("\\.")[0].split("_");
+      change.setId(new BigDecimal(parts[0]));
+      StringBuilder builder = new StringBuilder();
+      for (int i = 1; i < parts.length; i++) {
+        if (i > 1) builder.append(" ");
+        builder.append(parts[i]);
+      }
+      change.setDescription(builder.toString());
+      return change;
+    } catch (Exception e) {
+      throw new MigrationException("Error parsing change from file.  Cause: " + e, e);
+    }
+  }
+
+  protected List<Change> getChangelog() {
+    AdHocExecutor executor = getAdHocExecutor();
+    try {
+      List<Map<String, Object>> changelog = executor.selectAll("select ID, DESCRIPTION from CHANGELOG order by id");
+      List<Change> changes = new ArrayList<Change>();
+      for (Map<String, Object> change : changelog) {
+        changes.add(new Change(new BigDecimal(change.get("ID").toString()), change.get("DESCRIPTION").toString()));
+      }
+      return changes;
+    } catch (SQLException e) {
+      throw new MigrationException("Error querying last applied migration.  Cause: " + e, e);
+    } finally {
+      executor.closeConnection();
+    }
+  }
+
+  protected Change getLastChange() {
+    List<Change> changelog = getChangelog();
+    return changelog.get(changelog.size() - 1);
+  }
+
+  protected boolean changelogExists() {
+    AdHocExecutor executor = getAdHocExecutor();
+    try {
+      executor.selectAll("select ID, DESCRIPTION from CHANGELOG");
+      return true;
+    } catch (SQLException e) {
+      return false;
+    } finally {
+      executor.closeConnection();
+    }
+  }
+
+  protected String horizontalLine(String caption, int length) {
+    StringBuilder builder = new StringBuilder();
+    builder.append("==========");
+    if (caption.length() > 0) {
+      caption = " " + caption + " ";
+      builder.append(caption);
+    }
+    for (int i = 0; i < length - caption.length(); i++) {
+      builder.append("=");
+    }
+    return builder.toString();
+  }
+
+  protected String getTimestampAsString() {
+    try {
+      // Ensure that two subsequent calls are less likely to return the same value.
+      Thread.sleep(1000);
+    } catch (InterruptedException e) {
+      //ignore
+    }
+    return new SimpleDateFormat("yyyyMMddHHmmss").format(new java.sql.Date(System.currentTimeMillis()));
+  }
+
+  protected void copyResourceTo(String resource, File toFile) {
+    copyResourceTo(resource, toFile, null);
+  }
+
+  protected void copyResourceTo(String resource, File toFile, Map<String, String> variables) {
+    out.println("Creating: " + toFile.getName());
+    try {
+      LineNumberReader reader = new LineNumberReader(Resources.getResourceAsReader(this.getClass().getClassLoader(), resource));
+      try {
+        PrintWriter writer = new PrintWriter(new FileWriter(toFile));
+        try {
+          String line;
+          while ((line = reader.readLine()) != null) {
+            line = parsePlaceholders(line, variables);
+            writer.println(line);
+          }
+        } finally {
+          writer.close();
+        }
+      } finally {
+        reader.close();
+      }
+    } catch (IOException e) {
+      throw new MigrationException("Error copying " + resource + " to " + toFile.getAbsolutePath() + ".  Cause: " + e, e);
+    }
+  }
+
+  protected AdHocExecutor getAdHocExecutor() {
+    Properties props = getEnvironmentProperties();
+    String driver = props.getProperty("driver");
+    String url = props.getProperty("url");
+    String username = props.getProperty("username");
+    String password = props.getProperty("password");
+    return new AdHocExecutor(driver, url, username, password, false);
+  }
+
+  protected ScriptRunner getScriptRunner() {
+    try {
+      Properties props = getEnvironmentProperties();
+      String driver = props.getProperty("driver");
+      String url = props.getProperty("url");
+      String username = props.getProperty("username");
+      String password = props.getProperty("password");
+      ScriptRunner scriptRunner = new ScriptRunner(driver, url, username, password, false, !force);
+      PrintWriter outWriter = new PrintWriter(out);
+      scriptRunner.setLogWriter(outWriter);
+      scriptRunner.setErrorLogWriter(outWriter);
+      return scriptRunner;
+    } catch (Exception e) {
+      throw new MigrationException("Error creating ScriptRunner.  Cause: " + e, e);
+    }
+  }
+
+  protected File repositoryFile(String fileName) {
+    return new File(repository.getAbsolutePath() + File.separator + fileName);
+  }
+
+  protected File environmentFile() {
+    return repositoryFile(environment + ".properties");
+  }
+
+  protected File existingEnvironmentFile() {
+    File envFile = environmentFile();
+    if (!envFile.exists()) {
+      throw new MigrationException("Environment file missing: " + envFile.getAbsolutePath());
+    }
+    return envFile;
+  }
+
+  private Properties getEnvironmentProperties() {
+    try {
+      File file = existingEnvironmentFile();
+      Properties props = new Properties();
+      props.load(new FileInputStream(file));
+      return props;
+    } catch (IOException e) {
+      throw new MigrationException("Error loading environment properties.  Cause: " + e, e);
+    }
+  }
+
+  private String parsePlaceholders(String string, Map<String, String> variables) {
+    final String OPEN = "${";
+    final String CLOSE = "}";
+    String newString = string;
+    if (newString != null && variables != null) {
+      int start = newString.indexOf(OPEN);
+      int end = newString.indexOf(CLOSE);
+
+      while (start > -1 && end > start) {
+        String prepend = newString.substring(0, start);
+        String append = newString.substring(end + CLOSE.length());
+        String propName = newString.substring(start + OPEN.length(), end);
+        String propValue = variables.get(propName);
+        if (propValue == null) {
+          newString = prepend + append;
+        } else {
+          newString = prepend + propValue + append;
+        }
+        start = newString.indexOf(OPEN);
+        end = newString.indexOf(CLOSE);
+      }
+    }
+    return newString;
+  }
+
+}

Added: ibatis/trunk/java/ibatis-3/ibatis-3-core/src/main/java/org/apache/ibatis/migration/commands/Command.java
URL: http://svn.apache.org/viewvc/ibatis/trunk/java/ibatis-3/ibatis-3-core/src/main/java/org/apache/ibatis/migration/commands/Command.java?rev=688202&view=auto
==============================================================================
--- ibatis/trunk/java/ibatis-3/ibatis-3-core/src/main/java/org/apache/ibatis/migration/commands/Command.java (added)
+++ ibatis/trunk/java/ibatis-3/ibatis-3-core/src/main/java/org/apache/ibatis/migration/commands/Command.java Fri Aug 22 14:48:30 2008
@@ -0,0 +1,5 @@
+package org.apache.ibatis.migration.commands;
+
+public interface Command {
+  void execute (String... params);
+}

Added: ibatis/trunk/java/ibatis-3/ibatis-3-core/src/main/java/org/apache/ibatis/migration/commands/InitializeCommand.java
URL: http://svn.apache.org/viewvc/ibatis/trunk/java/ibatis-3/ibatis-3-core/src/main/java/org/apache/ibatis/migration/commands/InitializeCommand.java?rev=688202&view=auto
==============================================================================
--- ibatis/trunk/java/ibatis-3/ibatis-3-core/src/main/java/org/apache/ibatis/migration/commands/InitializeCommand.java (added)
+++ ibatis/trunk/java/ibatis-3/ibatis-3-core/src/main/java/org/apache/ibatis/migration/commands/InitializeCommand.java Fri Aug 22 14:48:30 2008
@@ -0,0 +1,46 @@
+package org.apache.ibatis.migration.commands;
+
+import org.apache.ibatis.migration.MigrationException;
+
+import java.io.File;
+
+public class InitializeCommand extends BaseCommand {
+
+  public InitializeCommand(File repository, String environment, boolean force) {
+    super(repository, environment, force);
+  }
+
+  public void execute(String... args) {
+    createDirectoryIfNecessary(repository);
+    ensureDirectoryIsEmpty(repository);
+    out.println("Initializing: " + repository);
+    copyResourceTo("org/apache/ibatis/migration/template_environment.properties", environmentFile());
+    copyResourceTo("org/apache/ibatis/migration/template_changelog.sql", repositoryFile(getTimestampAsString() + "_create_changelog.sql"));
+    copyResourceTo("org/apache/ibatis/migration/template_migration.sql", repositoryFile(getTimestampAsString() + "_first_migration.sql"));
+    out.println("Done!");
+  }
+
+  protected void ensureDirectoryIsEmpty(File path) {
+    String[] list = path.list();
+    if (list.length != 0) {
+      for (String entry : list) {
+        if (!entry.startsWith(".")) {
+          throw new MigrationException("Directory must be empty (.svn etc allowed): " + path.getAbsolutePath());
+        }
+      }
+    }
+  }
+
+  protected void createDirectoryIfNecessary(File path) {
+    if (!path.exists()) {
+      File parent = new File(path.getParent());
+      createDirectoryIfNecessary(parent);
+      if (!path.mkdir()) {
+        throw new MigrationException("Could not create directory path for an unknown reason. Make sure you have access to the directory.");
+      }
+    }
+  }
+
+  
+
+}

Added: ibatis/trunk/java/ibatis-3/ibatis-3-core/src/main/java/org/apache/ibatis/migration/commands/NewCommand.java
URL: http://svn.apache.org/viewvc/ibatis/trunk/java/ibatis-3/ibatis-3-core/src/main/java/org/apache/ibatis/migration/commands/NewCommand.java?rev=688202&view=auto
==============================================================================
--- ibatis/trunk/java/ibatis-3/ibatis-3-core/src/main/java/org/apache/ibatis/migration/commands/NewCommand.java (added)
+++ ibatis/trunk/java/ibatis-3/ibatis-3-core/src/main/java/org/apache/ibatis/migration/commands/NewCommand.java Fri Aug 22 14:48:30 2008
@@ -0,0 +1,33 @@
+package org.apache.ibatis.migration.commands;
+
+import org.apache.ibatis.migration.MigrationException;
+
+import java.io.File;
+import java.util.HashMap;
+import java.util.Map;
+
+public class NewCommand extends BaseCommand {
+
+  public NewCommand(File repository, String environment, boolean force) {
+    super(repository, environment, force);
+  }
+
+  public void execute (String... params) {
+    if (paramsEmpty(params)) {
+      throw new MigrationException("No description specified for new migration.");
+    }
+    String description = params[0];
+    Map<String, String> variables = new HashMap<String, String>();
+    variables.put("description", description);
+    existingEnvironmentFile();
+    String filename = getTimestampAsString() + "_" + description.replace(' ', '_') + ".sql";
+    copyResourceTo("org/apache/ibatis/migration/template_migration.sql", repositoryFile(filename), variables);
+    out.println("Done!");
+  }
+
+  protected boolean paramsEmpty(String... params) {
+    return params == null || params.length < 1 || params[0] == null || params[0].length() < 1;
+  }
+  
+
+}

Added: ibatis/trunk/java/ibatis-3/ibatis-3-core/src/main/java/org/apache/ibatis/migration/commands/RunCommand.java
URL: http://svn.apache.org/viewvc/ibatis/trunk/java/ibatis-3/ibatis-3-core/src/main/java/org/apache/ibatis/migration/commands/RunCommand.java?rev=688202&view=auto
==============================================================================
--- ibatis/trunk/java/ibatis-3/ibatis-3-core/src/main/java/org/apache/ibatis/migration/commands/RunCommand.java (added)
+++ ibatis/trunk/java/ibatis-3/ibatis-3-core/src/main/java/org/apache/ibatis/migration/commands/RunCommand.java Fri Aug 22 14:48:30 2008
@@ -0,0 +1,55 @@
+package org.apache.ibatis.migration.commands;
+
+import org.apache.ibatis.migration.Change;
+import org.apache.ibatis.migration.MigrationReader;
+import org.apache.ibatis.migration.ScriptRunner;
+import org.apache.ibatis.migration.MigrationException;
+import org.apache.ibatis.adhoc.AdHocExecutor;
+
+import java.io.File;
+import java.io.FileReader;
+import java.util.Arrays;
+import java.sql.SQLException;
+
+public class RunCommand extends BaseCommand {
+
+  public RunCommand(File repository, String environment, boolean force) {
+    super(repository, environment, force);
+  }
+
+  public void execute(String... params) {
+    try {
+      String[] filenames = repository.list();
+      Arrays.sort(filenames);
+      Change lastChange = null; 
+      if (changelogExists()) {
+        lastChange = getLastChange();
+      }
+      for (String filename : filenames) {
+        if (filename.endsWith(".sql")) {
+          Change change = parseChangeFromFilename(filename);
+          if (lastChange == null || change.getId().compareTo(lastChange.getId()) > 0) {
+            out.println(horizontalLine("Applying: " + filename, 80));
+            ScriptRunner runner = getScriptRunner();
+            runner.runScript(new MigrationReader(new FileReader(repositoryFile(filename)), false));
+            insertChangelog(change);
+          }
+        }
+      }
+    } catch (Exception e) {
+      throw new RuntimeException("<Description>.  Cause: " + e, e);
+    }
+  }
+
+  protected void insertChangelog(Change change) {
+    AdHocExecutor executor = getAdHocExecutor();
+    try {
+      executor.insert("insert into CHANGELOG (ID, DESCRIPTION) values (?,?)", change.getId(), change.getDescription());
+    } catch (SQLException e) {
+      throw new MigrationException("Error querying last applied migration.  Cause: " + e, e);
+    } finally {
+      executor.closeConnection();
+    }
+  }  
+
+}

Added: ibatis/trunk/java/ibatis-3/ibatis-3-core/src/main/java/org/apache/ibatis/migration/commands/StatusCommand.java
URL: http://svn.apache.org/viewvc/ibatis/trunk/java/ibatis-3/ibatis-3-core/src/main/java/org/apache/ibatis/migration/commands/StatusCommand.java?rev=688202&view=auto
==============================================================================
--- ibatis/trunk/java/ibatis-3/ibatis-3-core/src/main/java/org/apache/ibatis/migration/commands/StatusCommand.java (added)
+++ ibatis/trunk/java/ibatis-3/ibatis-3-core/src/main/java/org/apache/ibatis/migration/commands/StatusCommand.java Fri Aug 22 14:48:30 2008
@@ -0,0 +1,27 @@
+package org.apache.ibatis.migration.commands;
+
+import org.apache.ibatis.migration.Change;
+
+import java.util.List;
+import java.io.File;
+
+public class StatusCommand extends BaseCommand {
+
+  public StatusCommand(File repository, String environment, boolean force) {
+    super(repository, environment, force);
+  }
+
+  public void execute(String... params) {
+    if (changelogExists()) {
+      List<Change> changelog = getChangelog();
+      out.println("ID             TIMESTAMP             DESCRIPTION");
+      out.println(horizontalLine("", 60));
+      for (Change change : changelog) {
+        out.println(change);
+      }
+    } else {
+      out.println("Changelog does not exist.");
+    }
+  }
+
+}

Added: ibatis/trunk/java/ibatis-3/ibatis-3-core/src/main/java/org/apache/ibatis/migration/commands/UndoCommand.java
URL: http://svn.apache.org/viewvc/ibatis/trunk/java/ibatis-3/ibatis-3-core/src/main/java/org/apache/ibatis/migration/commands/UndoCommand.java?rev=688202&view=auto
==============================================================================
--- ibatis/trunk/java/ibatis-3/ibatis-3-core/src/main/java/org/apache/ibatis/migration/commands/UndoCommand.java (added)
+++ ibatis/trunk/java/ibatis-3/ibatis-3-core/src/main/java/org/apache/ibatis/migration/commands/UndoCommand.java Fri Aug 22 14:48:30 2008
@@ -0,0 +1,68 @@
+package org.apache.ibatis.migration.commands;
+
+import org.apache.ibatis.migration.Change;
+import org.apache.ibatis.migration.MigrationReader;
+import org.apache.ibatis.migration.ScriptRunner;
+import org.apache.ibatis.migration.MigrationException;
+import org.apache.ibatis.adhoc.AdHocExecutor;
+
+import java.io.File;
+import java.io.FileReader;
+import java.util.Arrays;
+import java.util.Comparator;
+import java.sql.SQLException;
+
+public class UndoCommand extends BaseCommand {
+
+  public UndoCommand(File repository, String environment, boolean force) {
+    super(repository, environment, force);
+  }
+
+  public void execute(String... params) {
+    try {
+      String[] filenames = repository.list();
+      reverse(filenames);
+      Change lastChange = getLastChange();
+      for (String filename : filenames) {
+        if (filename.endsWith(".sql")) {
+          Change change = parseChangeFromFilename(filename);
+          if (change.getId().equals(lastChange.getId())) {
+            out.println(horizontalLine("Undoing: " + filename, 80));
+            ScriptRunner runner = getScriptRunner();
+            runner.runScript(new MigrationReader(new FileReader(repositoryFile(filename)), true));
+            if (changelogExists()) {
+              deleteChange(change);
+            } else {
+              out.println("Changelog doesn't exist. No further migrations will be undone (normal for the last migration).");
+            }
+            break;
+          }
+        }
+      }
+    } catch (Exception e) {
+      throw new RuntimeException("<Description>.  Cause: " + e, e);
+    }
+  }
+
+  protected void deleteChange(Change change) {
+    AdHocExecutor executor = getAdHocExecutor();
+    try {
+      executor.delete("delete from CHANGELOG where id = ?", change.getId());
+    } catch (SQLException e) {
+      throw new MigrationException("Error querying last applied migration.  Cause: " + e, e);
+    } finally {
+      executor.closeConnection();
+    }
+  }
+
+  
+
+  protected void reverse(Comparable[] comparable) {
+    Arrays.sort(comparable, new Comparator() {
+      public int compare(Object o1, Object o2) {
+        return ((Comparable) o2).compareTo(o1);
+      }
+    });
+  }
+
+}

Added: ibatis/trunk/java/ibatis-3/ibatis-3-core/src/main/java/org/apache/ibatis/migration/commands/VersionCommand.java
URL: http://svn.apache.org/viewvc/ibatis/trunk/java/ibatis-3/ibatis-3-core/src/main/java/org/apache/ibatis/migration/commands/VersionCommand.java?rev=688202&view=auto
==============================================================================
--- ibatis/trunk/java/ibatis-3/ibatis-3-core/src/main/java/org/apache/ibatis/migration/commands/VersionCommand.java (added)
+++ ibatis/trunk/java/ibatis-3/ibatis-3-core/src/main/java/org/apache/ibatis/migration/commands/VersionCommand.java Fri Aug 22 14:48:30 2008
@@ -0,0 +1,22 @@
+package org.apache.ibatis.migration.commands;
+
+import org.apache.ibatis.migration.Change;
+import org.apache.ibatis.migration.ScriptRunner;
+import org.apache.ibatis.migration.MigrationReader;
+
+import java.math.BigInteger;
+import java.io.FileReader;
+import java.io.File;
+import java.util.List;
+
+public class VersionCommand extends BaseCommand {
+
+  public VersionCommand(File repository, String environment, boolean force) {
+    super(repository, environment, force);
+  }
+
+  public void execute(String... params) {
+    out.println("not implemented");
+  }
+
+}