killbill-memoizeit

db-helper: integration with Flyway The actions migrate,

3/25/2016 1:01:58 PM

Details

bin/db-helper 28(+26 -2)

diff --git a/bin/db-helper b/bin/db-helper
index c642b1c..7b9c051 100755
--- a/bin/db-helper
+++ b/bin/db-helper
@@ -60,7 +60,7 @@ eval set -- "${ARGS}"
 
 function usage() {
     echo -n "./db_helper"
-    echo -n " -a|--action <create|clean|dump>"
+    echo -n " -a|--action <create|clean|dump|migrate|dryRunMigrate|repair|info>"
     echo -n " --driver <mysql|postgres> (default = mysql)"
     echo -n " -h|--host host (default = localhost)"
     echo -n " --port port"
@@ -159,6 +159,20 @@ function cleanup() {
     rm -f "/tmp/*.$$"
 }
 
+function flyway() {
+    flyway_bin=util/target/killbill-flyway.jar
+    if [ ! -f "$flyway_bin" ]; then
+        echo "File $flyway_bin does not exists - build util first"
+        usage
+    fi
+
+    locations=
+    for migration_dir in `find */src/main/resources -type d -name migration`; do
+        locations="${locations}filesystem:$migration_dir,"
+    done
+
+    java -jar $flyway_bin -locations=$locations -user=$USER -password=$PWD -url=$URL ${@}
+}
 
 while true; do
   case "$1" in
@@ -178,7 +192,7 @@ done
 
 
 if [ -z $ACTION ]; then
-    echo "Need to specify an action <CREATE|CLEAN|DUMP>"
+    echo "Need to specify an action"
     usage
 fi
 
@@ -195,6 +209,12 @@ fi
 if [ $DRIVER == "postgres" ] && [ -z $PORT ]; then
     PORT=$PORT_POSTGRES
 fi
+if [ $DRIVER == "mysql" ] && [ -z $URL ]; then
+    URL=jdbc:mysql://$HOST:$PORT/$DATABASE
+fi
+if [ $DRIVER == "postgres" ] && [ -z $URL ]; then
+    URL=jdbc:postgresql://$HOST:$PORT/$DATABASE
+fi
 
 
 if [ $ACTION == "dump" ]; then
@@ -233,4 +253,8 @@ if [ $ACTION == "clean" ]; then
     fi
 fi
 
+if [ $ACTION == "migrate" ] || [ $ACTION == "dryRunMigrate" ] || [ $ACTION == "repair" ] || [ $ACTION == "info" ]; then
+    flyway $ACTION
+fi
+
 cleanup

util/pom.xml 24(+24 -0)

diff --git a/util/pom.xml b/util/pom.xml
index 0b7370e..04d9e7b 100644
--- a/util/pom.xml
+++ b/util/pom.xml
@@ -259,6 +259,30 @@
     <build>
         <plugins>
             <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-assembly-plugin</artifactId>
+                <executions>
+                    <execution>
+                        <id>assemble-migrator</id>
+                        <phase>package</phase>
+                        <goals>
+                            <goal>single</goal>
+                        </goals>
+                        <configuration>
+                            <finalName>killbill</finalName>
+                            <archive>
+                                <manifest>
+                                    <mainClass>org.killbill.billing.util.migration.Migrator</mainClass>
+                                </manifest>
+                            </archive>
+                        </configuration>
+                    </execution>
+                </executions>
+                <configuration>
+                    <descriptor>src/main/assembly/migrator.xml</descriptor>
+                </configuration>
+            </plugin>
+            <plugin>
                 <groupId>org.codehaus.mojo</groupId>
                 <artifactId>build-helper-maven-plugin</artifactId>
                 <executions>
diff --git a/util/src/main/assembly/migrator.xml b/util/src/main/assembly/migrator.xml
new file mode 100644
index 0000000..0e7eefe
--- /dev/null
+++ b/util/src/main/assembly/migrator.xml
@@ -0,0 +1,28 @@
+<assembly
+        xmlns="http://maven.apache.org/plugins/maven-assembly-plugin/assembly/1.1.3"
+        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+        xsi:schemaLocation="http://maven.apache.org/plugins/maven-assembly-plugin/assembly/1.1.3 http://maven.apache.org/xsd/assembly-1.1.3.xsd">
+    <id>flyway</id>
+    <formats>
+        <format>jar</format>
+    </formats>
+    <includeBaseDirectory>false</includeBaseDirectory>
+    <dependencySets>
+        <dependencySet>
+            <outputDirectory>/</outputDirectory>
+            <useProjectArtifact>true</useProjectArtifact>
+            <unpack>true</unpack>
+            <scope>test</scope>
+        </dependencySet>
+    </dependencySets>
+    <fileSets>
+        <fileSet>
+            <directory>${project.build.directory}/test-classes</directory>
+            <outputDirectory>/</outputDirectory>
+            <includes>
+                <include>**/*.class</include>
+            </includes>
+            <useDefaultExcludes>true</useDefaultExcludes>
+        </fileSet>
+    </fileSets>
+</assembly>
diff --git a/util/src/test/java/org/flywaydb/core/FlywayWithDryRun.java b/util/src/test/java/org/flywaydb/core/FlywayWithDryRun.java
index a8ae016..0cf27a2 100644
--- a/util/src/test/java/org/flywaydb/core/FlywayWithDryRun.java
+++ b/util/src/test/java/org/flywaydb/core/FlywayWithDryRun.java
@@ -54,7 +54,6 @@ public class FlywayWithDryRun extends Flyway {
                                    final FlywayCallback[] flywayCallbacks) {
                 final Table metaDataDBTable = schemas[0].getTable(getTable());
 
-                @SuppressWarnings("deprecation")
                 final DbMigrateWithDryRun dbMigrate = new DbMigrateWithDryRun(sqlStatements,
                                                                               placeholderReplacer,
                                                                               getEncoding(),
@@ -67,7 +66,7 @@ public class FlywayWithDryRun extends Flyway {
                                                                               migrationResolver,
                                                                               getTarget(),
                                                                               isIgnoreFutureMigrations(),
-                                                                              isIgnoreFailedFutureMigration(),
+                                                                              false,
                                                                               isOutOfOrder(),
                                                                               flywayCallbacks);
                 return dbMigrate.dryRunMigrate();
diff --git a/util/src/test/java/org/killbill/billing/util/migration/Migrator.java b/util/src/test/java/org/killbill/billing/util/migration/Migrator.java
index db378c8..0b5b6e8 100644
--- a/util/src/test/java/org/killbill/billing/util/migration/Migrator.java
+++ b/util/src/test/java/org/killbill/billing/util/migration/Migrator.java
@@ -17,34 +17,524 @@
 
 package org.killbill.billing.util.migration;
 
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FilenameFilter;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.io.StringReader;
+import java.lang.reflect.Method;
+import java.net.URL;
+import java.net.URLClassLoader;
+import java.util.ArrayList;
 import java.util.LinkedList;
 import java.util.List;
+import java.util.Map;
+import java.util.Properties;
 
 import org.flywaydb.core.FlywayWithDryRun;
+import org.flywaydb.core.api.FlywayException;
 import org.flywaydb.core.internal.dbsupport.SqlStatement;
+import org.flywaydb.core.internal.info.MigrationInfoDumper;
+import org.flywaydb.core.internal.util.ClassUtils;
+import org.flywaydb.core.internal.util.FileCopyUtils;
+import org.flywaydb.core.internal.util.StringUtils;
+import org.flywaydb.core.internal.util.VersionPrinter;
+import org.flywaydb.core.internal.util.logging.Log;
+import org.flywaydb.core.internal.util.logging.LogFactory;
+import org.flywaydb.core.internal.util.logging.console.ConsoleLog.Level;
+import org.flywaydb.core.internal.util.logging.console.ConsoleLogCreator;
+import org.flywaydb.core.internal.util.scanner.classpath.ClassPathResource;
 
+// Copied over from org.flywaydb.commandline.Main (not easily extensible unfortunately) to support dry-run
 public class Migrator {
 
+    /**
+     * The property name for the directory containing a list of jars to load on the classpath.
+     */
+    private static final String PROPERTY_JAR_DIRS = "flyway.jarDirs";
+    private static Log LOG;
+
+    /**
+     * Initializes the logging.
+     *
+     * @param level The minimum level to log at.
+     */
+    private static void initLogging(final Level level) {
+        LogFactory.setLogCreator(new ConsoleLogCreator(level));
+        LOG = LogFactory.getLog(Migrator.class);
+    }
+
+    /**
+     * Main method.
+     *
+     * @param args The command-line arguments.
+     */
     public static void main(final String[] args) {
-        final List<SqlStatement> sqlStatements = new LinkedList<SqlStatement>();
+        final Level logLevel = getLogLevel(args);
+        initLogging(logLevel);
+
+        try {
+            if (isPrintVersionAndExit(args)) {
+                printVersion();
+                System.exit(0);
+            }
+
+            final List<String> operations = determineOperations(args);
+            if (operations.isEmpty()) {
+                printUsage();
+                return;
+            }
+
+            final Properties properties = new Properties();
+            initializeDefaults(properties);
+            loadConfiguration(properties, args);
+            overrideConfiguration(properties, args);
+            dumpConfiguration(properties);
+
+            loadJdbcDrivers();
+            loadJavaMigrationsFromJarDirs(properties);
+
+            final List<SqlStatement> sqlStatements = new LinkedList<SqlStatement>();
+            final FlywayWithDryRun flyway = new FlywayWithDryRun(sqlStatements);
+            filterProperties(properties);
+            flyway.configure(properties);
+
+            for (final String operation : operations) {
+                executeOperation(flyway, operation, sqlStatements);
+            }
+        } catch (final Exception e) {
+            if (logLevel == Level.DEBUG) {
+                LOG.error("Unexpected error", e);
+            } else {
+                if (e instanceof FlywayException) {
+                    LOG.error(e.getMessage());
+                } else {
+                    LOG.error(e.toString());
+                }
+            }
+            System.exit(1);
+        }
+    }
+
+    private static boolean isPrintVersionAndExit(final String[] args) {
+        for (final String arg : args) {
+            if ("-v".equals(arg)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    /**
+     * Executes this operation on this Flyway instance.
+     *
+     * @param flyway        The Flyway instance.
+     * @param operation     The operation to execute.
+     * @param sqlStatements The current list of all pending migrations.
+     */
+    private static void executeOperation(final FlywayWithDryRun flyway, final String operation, final Iterable<SqlStatement> sqlStatements) {
+        if ("clean".equals(operation)) {
+            flyway.clean();
+        } else if ("baseline".equals(operation)) {
+            flyway.baseline();
+        } else if ("migrate".equals(operation)) {
+            flyway.migrate();
+        } else if ("dryRunMigrate".equals(operation)) {
+            flyway.dryRunMigrate();
+
+            final StringBuilder stringBuilder = new StringBuilder("BEGIN;\n");
+            for (final SqlStatement sqlStatement : sqlStatements) {
+                stringBuilder.append(sqlStatement.getSql())
+                             .append(";\n");
+            }
+            stringBuilder.append("COMMIT;");
+            LOG.info("\n" + stringBuilder.toString());
+        } else if ("validate".equals(operation)) {
+            flyway.validate();
+        } else if ("info".equals(operation)) {
+            LOG.info("\n" + MigrationInfoDumper.dumpToAsciiTable(flyway.info().all()));
+        } else if ("repair".equals(operation)) {
+            flyway.repair();
+        } else {
+            LOG.error("Invalid operation: " + operation);
+            printUsage();
+            System.exit(1);
+        }
+    }
+
+    /**
+     * Checks the desired log level.
+     *
+     * @param args The command-line arguments.
+     * @return The desired log level.
+     */
+    private static Level getLogLevel(final String[] args) {
+        for (final String arg : args) {
+            if ("-X".equals(arg)) {
+                return Level.DEBUG;
+            }
+            if ("-q".equals(arg)) {
+                return Level.WARN;
+            }
+        }
+        return Level.INFO;
+    }
 
-        final FlywayWithDryRun flyway = new FlywayWithDryRun(sqlStatements);
-        flyway.configure(System.getProperties());
+    /**
+     * Initializes the properties with the default configuration for the command-line tool.
+     *
+     * @param properties The properties object to initialize.
+     */
+    private static void initializeDefaults(final Properties properties) {
+        properties.put("flyway.locations", "filesystem:" + new File(getInstallationDir(), "sql").getAbsolutePath());
+        properties.put(PROPERTY_JAR_DIRS, new File(getInstallationDir(), "jars").getAbsolutePath());
+    }
+
+    /**
+     * Filters there properties to remove the Flyway Commandline-specific ones.
+     *
+     * @param properties The properties to filter.
+     */
+    private static void filterProperties(final Properties properties) {
+        properties.remove(PROPERTY_JAR_DIRS);
+        properties.remove("flyway.configFile");
+        properties.remove("flyway.configFileEncoding");
+    }
 
-        flyway.dryRunMigrate();
-        // Flush logs
-        System.out.flush();
+    /**
+     * Prints the version number on the console.
+     *
+     * @throws IOException when the version could not be read.
+     */
+    private static void printVersion() throws IOException {
+        final String version = new ClassPathResource("org/flywaydb/core/internal/version.txt", VersionPrinter.class.getClassLoader()).loadAsString("UTF-8");
+        LOG.info("Flyway " + version + " for Kill Bill");
 
-        if (sqlStatements.isEmpty()) {
+        LOG.debug("Java " + System.getProperty("java.version") + " (" + System.getProperty("java.vendor") + ")");
+        LOG.debug(System.getProperty("os.name") + " " + System.getProperty("os.version") + " " + System.getProperty("os.arch") + "\n");
+    }
+
+    /**
+     * Prints the usage instructions on the console.
+     */
+    private static void printUsage() {
+        LOG.info("Usage");
+        LOG.info("=====");
+        LOG.info("");
+        LOG.info("flyway [options] command");
+        LOG.info("");
+        LOG.info("By default, the configuration will be read from conf/flyway.conf.");
+        LOG.info("Options passed from the command-line override the configuration.");
+        LOG.info("");
+        LOG.info("Commands");
+        LOG.info("--------");
+        LOG.info("migrate        : Migrates the database");
+        LOG.info("dryRunMigrate  : Migrates the database (dry-run)");
+        LOG.info("clean          : Drops all objects in the configured schemas");
+        LOG.info("info           : Prints the information about applied, current and pending migrations");
+        LOG.info("validate       : Validates the applied migrations against the ones on the classpath");
+        LOG.info("baseline       : Baselines an existing database at the baselineVersion");
+        LOG.info("repair         : Repairs the metadata table");
+        LOG.info("");
+        LOG.info("Options (Format: -key=value)");
+        LOG.info("-------");
+        LOG.info("driver                       : Fully qualified classname of the jdbc driver");
+        LOG.info("url                          : Jdbc url to use to connect to the database");
+        LOG.info("user                         : User to use to connect to the database");
+        LOG.info("password                     : Password to use to connect to the database");
+        LOG.info("schemas                      : Comma-separated list of the schemas managed by Flyway");
+        LOG.info("table                        : Name of Flyway's metadata table");
+        LOG.info("locations                    : Classpath locations to scan recursively for migrations");
+        LOG.info("resolvers                    : Comma-separated list of custom MigrationResolvers");
+        LOG.info("skipDefaultResolvers         : Skips default resolvers (jdbc, sql and Spring-jdbc)");
+        LOG.info("sqlMigrationPrefix           : File name prefix for sql migrations");
+        LOG.info("repeatableSqlMigrationPrefix : File name prefix for repeatable sql migrations");
+        LOG.info("sqlMigrationSeparator        : File name separator for sql migrations");
+        LOG.info("sqlMigrationSuffix           : File name suffix for sql migrations");
+        LOG.info("encoding                     : Encoding of sql migrations");
+        LOG.info("placeholderReplacement       : Whether placeholders should be replaced");
+        LOG.info("placeholders                 : Placeholders to replace in sql migrations");
+        LOG.info("placeholderPrefix            : Prefix of every placeholder");
+        LOG.info("placeholderSuffix            : Suffix of every placeholder");
+        LOG.info("target                       : Target version up to which Flyway should use migrations");
+        LOG.info("outOfOrder                   : Allows migrations to be run \"out of order\"");
+        LOG.info("callbacks                    : Comma-separated list of FlywayCallback classes");
+        LOG.info("skipDefaultCallbacks         : Skips default callbacks (sql)");
+        LOG.info("validateOnMigrate            : Validate when running migrate");
+        LOG.info("ignoreFutureMigrations       : Allow future migrations when validating");
+        LOG.info("cleanOnValidationError       : Automatically clean on a validation error");
+        LOG.info("cleanDisabled                : Whether to disable clean");
+        LOG.info("baselineVersion              : Version to tag schema with when executing baseline");
+        LOG.info("baselineDescription          : Description to tag schema with when executing baseline");
+        LOG.info("baselineOnMigrate            : Baseline on migrate against uninitialized non-empty schema");
+        LOG.info("configFile                   : Config file to use (default: conf/flyway.properties)");
+        LOG.info("configFileEncoding           : Encoding of the config file (default: UTF-8)");
+        LOG.info("jarDirs                      : Dirs for Jdbc drivers & Java migrations (default: jars)");
+        LOG.info("");
+        LOG.info("Add -X to print debug output");
+        LOG.info("Add -q to suppress all output, except for errors and warnings");
+        LOG.info("Add -v to print the Flyway version and exit");
+        LOG.info("");
+        LOG.info("Example");
+        LOG.info("-------");
+        LOG.info("flyway -user=myuser -password=s3cr3t -url=jdbc:h2:mem -placeholders.abc=def migrate");
+        LOG.info("");
+        LOG.info("More info at https://flywaydb.org/documentation/commandline");
+    }
+
+    /**
+     * Loads all the driver jars contained in the drivers folder. (For Jdbc drivers)
+     *
+     * @throws IOException When the jars could not be loaded.
+     */
+    private static void loadJdbcDrivers() throws IOException {
+        final File driversDir = new File(getInstallationDir(), "drivers");
+        final File[] files = driversDir.listFiles(new FilenameFilter() {
+            public boolean accept(final File dir, final String name) {
+                return name.endsWith(".jar");
+            }
+        });
+
+        // see javadoc of listFiles(): null if given path is not a real directory
+        if (files == null) {
             return;
         }
 
-        final StringBuilder stringBuffer = new StringBuilder("BEGIN;\n");
-        for (final SqlStatement sqlStatement : sqlStatements) {
-            stringBuffer.append(sqlStatement.getSql())
-                        .append(";\n");
+        for (final File file : files) {
+            addJarOrDirectoryToClasspath(file.getPath());
+        }
+    }
+
+    /**
+     * Loads all the jars contained in the jars folder. (For Java Migrations)
+     * This will also indirectly load custom driver jars.
+     *
+     * @param properties The configured properties.
+     * @throws IOException When the jars could not be loaded.
+     */
+    private static void loadJavaMigrationsFromJarDirs(final Properties properties) throws IOException {
+        String jarDirs = properties.getProperty(PROPERTY_JAR_DIRS);
+        if (!StringUtils.hasLength(jarDirs)) {
+            return;
+        }
+
+        jarDirs = jarDirs.replace(File.pathSeparator, ",");
+        final String[] dirs = StringUtils.tokenizeToStringArray(jarDirs, ",");
+
+        for (final String dirName : dirs) {
+            final File dir = new File(dirName);
+            final File[] files = dir.listFiles(new FilenameFilter() {
+                public boolean accept(final File dir, final String name) {
+                    return name.endsWith(".jar");
+                }
+            });
+
+            // see javadoc of listFiles(): null if given path is not a real directory
+            if (files == null) {
+                continue;
+            }
+
+            for (final File file : files) {
+                addJarOrDirectoryToClasspath(file.getPath());
+            }
+        }
+    }
+
+    /**
+     * Adds a jar or a directory with this name to the classpath.
+     *
+     * @param name The name of the jar or directory to add.
+     * @throws IOException when the jar or directory could not be found.
+     */
+    private static void addJarOrDirectoryToClasspath(final String name) throws IOException {
+        LOG.debug("Adding location to classpath: " + name);
+
+        try {
+            final URL url = new File(name).toURI().toURL();
+            final URLClassLoader sysloader = (URLClassLoader) ClassLoader.getSystemClassLoader();
+            final Method method = URLClassLoader.class.getDeclaredMethod("addURL", URL.class);
+            method.setAccessible(true);
+            method.invoke(sysloader, url);
+        } catch (final Exception e) {
+            throw new FlywayException("Unable to load " + name, e);
+        }
+    }
+
+    /**
+     * Loads the configuration from the various possible locations.
+     *
+     * @param properties The properties object to load to configuration into.
+     * @param args       The command-line arguments passed in.
+     */
+    private static void loadConfiguration(final Properties properties, final String[] args) {
+        final String encoding = determineConfigurationFileEncoding(args);
+
+        loadConfigurationFile(properties, getInstallationDir() + "/conf/flyway.conf", encoding, false);
+        loadConfigurationFile(properties, System.getProperty("user.home") + "/flyway.conf", encoding, false);
+        loadConfigurationFile(properties, "flyway.conf", encoding, false);
+
+        final String configFile = determineConfigurationFileArgument(args);
+        if (configFile != null) {
+            loadConfigurationFile(properties, configFile, encoding, true);
+        }
+    }
+
+    /**
+     * Loads the configuration from the configuration file. If a configuration file is specified using the -configfile
+     * argument it will be used, otherwise the default config file (conf/flyway.properties) will be loaded.
+     *
+     * @param properties    The properties object to load to configuration into.
+     * @param file          The configuration file to load.
+     * @param encoding      The encoding of the configuration file.
+     * @param failIfMissing Whether to fail if the file is missing.
+     * @return Whether the file was loaded successfully.
+     * @throws FlywayException when the configuration file could not be loaded.
+     */
+    private static boolean loadConfigurationFile(final Properties properties, final String file, final String encoding, final boolean failIfMissing) throws FlywayException {
+        final File configFile = new File(file);
+        final String errorMessage = "Unable to load config file: " + configFile.getAbsolutePath();
+
+        if (!configFile.isFile() || !configFile.canRead()) {
+            if (!failIfMissing) {
+                LOG.debug(errorMessage);
+                return false;
+            }
+            throw new FlywayException(errorMessage);
+        }
+
+        LOG.debug("Loading config file: " + configFile.getAbsolutePath());
+        try {
+            final String contents = FileCopyUtils.copyToString(new InputStreamReader(new FileInputStream(configFile), encoding));
+            properties.load(new StringReader(contents.replace("\\", "\\\\")));
+            return true;
+        } catch (final IOException e) {
+            throw new FlywayException(errorMessage, e);
+        }
+    }
+
+    /**
+     * Dumps the configuration to the console when debug output is activated.
+     *
+     * @param properties The configured properties.
+     */
+    private static void dumpConfiguration(final Properties properties) {
+        LOG.debug("Using configuration:");
+        for (final Map.Entry<Object, Object> entry : properties.entrySet()) {
+            String value = entry.getValue().toString();
+            value = "flyway.password".equals(entry.getKey()) ? StringUtils.trimOrPad("", value.length(), '*') : value;
+            LOG.debug(entry.getKey() + " -> " + value);
+        }
+    }
+
+    /**
+     * Determines the file to use for loading the configuration.
+     *
+     * @param args The command-line arguments passed in.
+     * @return The path of the configuration file on disk.
+     */
+    private static String determineConfigurationFileArgument(final String[] args) {
+        for (final String arg : args) {
+            if (isPropertyArgument(arg) && "configFile".equals(getArgumentProperty(arg))) {
+                return getArgumentValue(arg);
+            }
+        }
+
+        return null;
+    }
+
+    /**
+     * @return The installation directory of the Flyway Command-line tool.
+     */
+    @SuppressWarnings("ConstantConditions")
+    private static String getInstallationDir() {
+        final String path = ClassUtils.getLocationOnDisk(Migrator.class);
+        return new File(path).getParentFile().getParentFile().getAbsolutePath();
+    }
+
+    /**
+     * Determines the encoding to use for loading the configuration.
+     *
+     * @param args The command-line arguments passed in.
+     * @return The encoding. (default: UTF-8)
+     */
+    private static String determineConfigurationFileEncoding(final String[] args) {
+        for (final String arg : args) {
+            if (isPropertyArgument(arg) && "configFileEncoding".equals(getArgumentProperty(arg))) {
+                return getArgumentValue(arg);
+            }
+        }
+
+        return "UTF-8";
+    }
+
+    /**
+     * Overrides the configuration from the config file with the properties passed in directly from the command-line.
+     *
+     * @param properties The properties to override.
+     * @param args       The command-line arguments that were passed in.
+     */
+    private static void overrideConfiguration(final Properties properties, final String[] args) {
+        for (final String arg : args) {
+            if (isPropertyArgument(arg)) {
+                properties.put("flyway." + getArgumentProperty(arg), getArgumentValue(arg));
+            }
         }
-        stringBuffer.append("COMMIT;");
-        System.out.println(stringBuffer.toString());
+    }
+
+    /**
+     * Checks whether this command-line argument tries to set a property.
+     *
+     * @param arg The command-line argument to check.
+     * @return {@code true} if it does, {@code false} if not.
+     */
+    private static boolean isPropertyArgument(final String arg) {
+        return arg.startsWith("-") && arg.contains("=");
+    }
+
+    /**
+     * Retrieves the property this command-line argument tries to assign.
+     *
+     * @param arg The command-line argument to check, typically in the form -key=value.
+     * @return The property.
+     */
+    private static String getArgumentProperty(final String arg) {
+        final int index = arg.indexOf("=");
+
+        return arg.substring(1, index);
+    }
+
+    /**
+     * Retrieves the value this command-line argument tries to assign.
+     *
+     * @param arg The command-line argument to check, typically in the form -key=value.
+     * @return The value or an empty string if no value is assigned.
+     */
+    private static String getArgumentValue(final String arg) {
+        final int index = arg.indexOf("=");
+
+        if ((index < 0) || (index == arg.length())) {
+            return "";
+        }
+
+        return arg.substring(index + 1);
+    }
+
+    /**
+     * Determine the operations Flyway should execute.
+     *
+     * @param args The command-line arguments passed in.
+     * @return The operations. An empty list if none.
+     */
+    private static List<String> determineOperations(final String[] args) {
+        final List<String> operations = new ArrayList<String>();
+
+        for (final String arg : args) {
+            if (!arg.startsWith("-")) {
+                operations.add(arg);
+            }
+        }
+
+        return operations;
     }
 }