diff --git a/pom.xml b/pom.xml
index 21c0918df..607ed216b 100644
--- a/pom.xml
+++ b/pom.xml
@@ -92,6 +92,12 @@
[1.7.1,1.7.99)
+
+ org.avaje
+ avaje-classpath-scanner
+ 1.1.1
+
+
org.springframework.boot
diff --git a/src/main/java/com/avaje/ebean/config/ClassLoadConfig.java b/src/main/java/com/avaje/ebean/config/ClassLoadConfig.java
index 658d2d65f..f960de5b5 100644
--- a/src/main/java/com/avaje/ebean/config/ClassLoadConfig.java
+++ b/src/main/java/com/avaje/ebean/config/ClassLoadConfig.java
@@ -1,6 +1,10 @@
package com.avaje.ebean.config;
+import java.io.IOException;
+import java.net.URL;
+import java.util.Enumeration;
+
/**
* Helper to find classes taking into account the context class loader.
*/
@@ -70,6 +74,10 @@ public class ClassLoadConfig {
}
}
+ public Enumeration getResources(String name) throws IOException {
+ return context.getResources(name);
+ }
+
/**
* Return true if the given class is present.
*/
@@ -122,6 +130,13 @@ public class ClassLoadConfig {
return (loader != null) ? loader: callerLoader;
}
+ Enumeration getResources(String name) throws IOException {
+ if (preferredLoader != null) {
+ return preferredLoader.getResources(name);
+ }
+ return contextLoader().getResources(name);
+ }
+
Class> forName(String name) throws ClassNotFoundException {
if (preferredLoader != null) {
diff --git a/src/main/java/com/avaje/ebean/config/dbplatform/DatabasePlatform.java b/src/main/java/com/avaje/ebean/config/dbplatform/DatabasePlatform.java
index 26a6191e4..ef378fb55 100644
--- a/src/main/java/com/avaje/ebean/config/dbplatform/DatabasePlatform.java
+++ b/src/main/java/com/avaje/ebean/config/dbplatform/DatabasePlatform.java
@@ -9,6 +9,10 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.sql.DataSource;
+import java.sql.Connection;
+import java.sql.DatabaseMetaData;
+import java.sql.ResultSet;
+import java.sql.SQLException;
import java.sql.Types;
/**
@@ -522,4 +526,25 @@ public class DatabasePlatform {
return disallowBatchOnCascade;
}
+ /**
+ * Return true if the table exists.
+ */
+ public boolean tableExists(Connection connection, String catalog, String schema, String table) throws SQLException {
+
+ DatabaseMetaData metaData = connection.getMetaData();
+ ResultSet tables = metaData.getTables(catalog, schema, table, null);
+ try {
+ return tables.next();
+ } finally {
+ close(tables);
+ }
+ }
+
+ protected void close(ResultSet resultSet) {
+ try {
+ resultSet.close();
+ } catch (SQLException e) {
+ logger.error("Error closing resultSet", e);
+ }
+ }
}
diff --git a/src/main/java/com/avaje/ebean/dbmigration/DdlGenerator.java b/src/main/java/com/avaje/ebean/dbmigration/DdlGenerator.java
index 824ef7e63..ac2e6c856 100644
--- a/src/main/java/com/avaje/ebean/dbmigration/DdlGenerator.java
+++ b/src/main/java/com/avaje/ebean/dbmigration/DdlGenerator.java
@@ -3,6 +3,7 @@ package com.avaje.ebean.dbmigration;
import com.avaje.ebean.config.ServerConfig;
import com.avaje.ebean.dbmigration.model.CurrentModel;
import com.avaje.ebeaninternal.api.SpiEbeanServer;
+import com.avaje.ebeaninternal.util.JdbcClose;
import javax.persistence.PersistenceException;
import java.io.File;
@@ -13,6 +14,7 @@ import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.LineNumberReader;
import java.io.Reader;
+import java.sql.Connection;
/**
* Controls the generation and execution of "Create All" and "Drop All" DDL scripts.
@@ -81,6 +83,21 @@ public class DdlGenerator {
}
}
+ /**
+ * Execute all the DDL statements in the script.
+ */
+ public int runScript(boolean expectErrors, String content, String scriptName) {
+
+ DdlRunner runner = new DdlRunner(expectErrors, scriptName);
+ // get a connection without threadLocal
+ Connection connection = server.createTransaction().getConnection();
+ try {
+ return runner.runAll(content, connection);
+ } finally {
+ JdbcClose.close(connection);
+ }
+ }
+
protected void runDropSql() throws IOException {
if (!createOnly) {
if (dropAllContent == null) {
@@ -110,9 +127,8 @@ public class DdlGenerator {
if (sqlScript != null) {
InputStream is = getClassLoader().getResourceAsStream(sqlScript);
if (is != null) {
- DdlRunner runner = new DdlRunner(false, sqlScript);
String content = readContent(new InputStreamReader(is));
- runner.runAll(content, server);
+ runScript(false, content, sqlScript);
}
}
}
@@ -221,13 +237,4 @@ public class DdlGenerator {
}
}
- /**
- * Execute all the DDL statements in the script.
- */
- public int runScript(boolean expectErrors, String content, String scriptName) {
-
- DdlRunner runner = new DdlRunner(expectErrors, scriptName);
- return runner.runAll(content, server);
- }
-
}
diff --git a/src/main/java/com/avaje/ebean/dbmigration/DdlRunner.java b/src/main/java/com/avaje/ebean/dbmigration/DdlRunner.java
index f1b1d2b58..9b8e3124c 100644
--- a/src/main/java/com/avaje/ebean/dbmigration/DdlRunner.java
+++ b/src/main/java/com/avaje/ebean/dbmigration/DdlRunner.java
@@ -1,7 +1,5 @@
package com.avaje.ebean.dbmigration;
-import com.avaje.ebean.Transaction;
-import com.avaje.ebeaninternal.api.SpiEbeanServer;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -37,29 +35,33 @@ public class DdlRunner {
/**
* Parse the content into sql statements and execute them in a transaction.
*/
- public int runAll(String content, SpiEbeanServer server) {
+ public int runAll(String content, Connection connection) {
List statements = ddlParser.parse(new StringReader(content));
- return runStatements(statements, server);
+ return runStatements(statements, connection);
}
/**
* Execute all the statements in a single transaction.
*/
- public int runStatements(List statements, SpiEbeanServer server) {
+ public int runStatements(List statements, Connection connection) {
- Transaction t = server.createTransaction();
try {
- int statementCount = runStatements(expectErrors, statements, t.getConnection());
- t.commit();
-
+ int statementCount = runStatements(expectErrors, statements, connection);
+ connection.commit();
return statementCount;
} catch (Exception e) {
+ rollback(connection);
throw new PersistenceException("Error: " + e.getMessage(), e);
+ }
+ }
- } finally {
- t.end();
+ private void rollback(Connection connection) {
+ try {
+ connection.rollback();
+ } catch (SQLException e) {
+ logger.error("Error trying to rollback connection", e);
}
}
diff --git a/src/main/java/com/avaje/ebean/dbmigration/MigrationRunner.java b/src/main/java/com/avaje/ebean/dbmigration/MigrationRunner.java
new file mode 100644
index 000000000..c00b4c77f
--- /dev/null
+++ b/src/main/java/com/avaje/ebean/dbmigration/MigrationRunner.java
@@ -0,0 +1,72 @@
+package com.avaje.ebean.dbmigration;
+
+import com.avaje.ebean.EbeanServer;
+import com.avaje.ebean.Transaction;
+import com.avaje.ebean.config.DbMigrationConfig;
+import com.avaje.ebean.config.ServerConfig;
+import com.avaje.ebean.dbmigration.runner.LocalMigrationResource;
+import com.avaje.ebean.dbmigration.runner.LocalMigrationResources;
+import com.avaje.ebean.dbmigration.runner.MigrationTable;
+import com.avaje.ebeaninternal.util.JdbcClose;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.sql.Connection;
+import java.util.List;
+
+/**
+ *
+ */
+public class MigrationRunner {
+
+ private static final Logger logger = LoggerFactory.getLogger(MigrationRunner.class);
+
+ final EbeanServer server;
+ final ServerConfig config;
+
+ private final DbMigrationConfig migrationConfig;
+
+ public MigrationRunner(EbeanServer server) {
+ this.server = server;
+ this.config = server.getPluginApi().getServerConfig();
+ migrationConfig = this.config.getMigrationConfig();
+ }
+
+ public void run() {
+
+ LocalMigrationResources resources = new LocalMigrationResources(config);
+ if (!resources.readResources()) {
+ logger.debug("no migrations to check");
+ return;
+ }
+
+ Transaction transaction = server.createTransaction();
+ Connection connection = transaction.getConnection();
+ try {
+ MigrationTable table = new MigrationTable(server, migrationConfig, connection);
+ table.createIfNeeded();
+
+ LocalMigrationResource priorVersion = null;
+ List localVersions = resources.getVersions();
+ for (int i = 0; i < localVersions.size(); i++) {
+ LocalMigrationResource localVersion = localVersions.get(i);
+ if (i > 0) {
+ priorVersion = localVersions.get(i-1);
+ }
+ if (!table.shouldRun(localVersion, priorVersion)) {
+ break;
+ }
+ }
+
+ table.commit();
+
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+
+ } finally {
+ JdbcClose.close(connection);
+ }
+
+ }
+
+}
diff --git a/src/main/java/com/avaje/ebean/dbmigration/model/MigrationVersion.java b/src/main/java/com/avaje/ebean/dbmigration/model/MigrationVersion.java
index f42e8755c..eadeba9e5 100644
--- a/src/main/java/com/avaje/ebean/dbmigration/model/MigrationVersion.java
+++ b/src/main/java/com/avaje/ebean/dbmigration/model/MigrationVersion.java
@@ -28,6 +28,13 @@ public class MigrationVersion implements Comparable {
this.comment = comment;
}
+ /**
+ * Return the full version.
+ */
+ public String getFull() {
+ return raw;
+ }
+
public String toString() {
return raw;
}
diff --git a/src/main/java/com/avaje/ebean/dbmigration/runner/Checksum.java b/src/main/java/com/avaje/ebean/dbmigration/runner/Checksum.java
new file mode 100644
index 000000000..515bf3606
--- /dev/null
+++ b/src/main/java/com/avaje/ebean/dbmigration/runner/Checksum.java
@@ -0,0 +1,32 @@
+package com.avaje.ebean.dbmigration.runner;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.StringReader;
+import java.util.zip.CRC32;
+
+/**
+ * Calculates the checksum for the given string content.
+ */
+public class Checksum {
+
+ /**
+ * Returns the checksum of this string.
+ */
+ public static int calculate(String str) {
+
+ final CRC32 crc32 = new CRC32();
+
+ BufferedReader bufferedReader = new BufferedReader(new StringReader(str));
+ try {
+ String line;
+ while ((line = bufferedReader.readLine()) != null) {
+ crc32.update(line.getBytes("UTF-8"));
+ }
+ } catch (IOException e) {
+ throw new RuntimeException("Failed to calculate checksum", e);
+ }
+
+ return (int) crc32.getValue();
+ }
+}
diff --git a/src/main/java/com/avaje/ebean/dbmigration/runner/LocalMigrationResource.java b/src/main/java/com/avaje/ebean/dbmigration/runner/LocalMigrationResource.java
new file mode 100644
index 000000000..979552e07
--- /dev/null
+++ b/src/main/java/com/avaje/ebean/dbmigration/runner/LocalMigrationResource.java
@@ -0,0 +1,55 @@
+package com.avaje.ebean.dbmigration.runner;
+
+import com.avaje.ebean.dbmigration.model.MigrationVersion;
+import org.avaje.classpath.scanner.Resource;
+
+/**
+ * A DB migration resource.
+ */
+public class LocalMigrationResource implements Comparable {
+
+ private final MigrationVersion version;
+
+ private final String location;
+
+ private final Resource resource;
+
+ public LocalMigrationResource(MigrationVersion version, String location, Resource resource) {
+ this.version = version;
+ this.location = location;
+ this.resource = resource;
+ }
+
+ public String toString() {
+ return version.toString();
+ }
+
+ /**
+ * Default ordering by version.
+ */
+ @Override
+ public int compareTo(LocalMigrationResource o) {
+ return version.compareTo(o.version);
+ }
+
+ /**
+ * Return the underlying migration version.
+ */
+ public MigrationVersion getVersion() {
+ return version;
+ }
+
+ /**
+ * Return the resource location.
+ */
+ public String getLocation() {
+ return location;
+ }
+
+ /**
+ * Return the content for the migration apply ddl script.
+ */
+ public String getContent() {
+ return resource.loadAsString("UTF-8");
+ }
+}
diff --git a/src/main/java/com/avaje/ebean/dbmigration/runner/LocalMigrationResources.java b/src/main/java/com/avaje/ebean/dbmigration/runner/LocalMigrationResources.java
new file mode 100644
index 000000000..66834159e
--- /dev/null
+++ b/src/main/java/com/avaje/ebean/dbmigration/runner/LocalMigrationResources.java
@@ -0,0 +1,85 @@
+package com.avaje.ebean.dbmigration.runner;
+
+import com.avaje.ebean.config.DbMigrationConfig;
+import com.avaje.ebean.config.ServerConfig;
+import com.avaje.ebean.dbmigration.model.MigrationVersion;
+import org.avaje.classpath.scanner.Location;
+import org.avaje.classpath.scanner.MatchResource;
+import org.avaje.classpath.scanner.Resource;
+import org.avaje.classpath.scanner.Scanner;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+/**
+ *
+ */
+public class LocalMigrationResources {
+
+ private static final Logger logger = LoggerFactory.getLogger(LocalMigrationResources.class);
+
+ private final ServerConfig serverConfig;
+
+ private final DbMigrationConfig migrationConfig;
+
+ private final List versions = new ArrayList();
+
+ public LocalMigrationResources(ServerConfig serverConfig) {
+ this.serverConfig = serverConfig;
+ this.migrationConfig = serverConfig.getMigrationConfig();
+ }
+
+ public boolean readResources() {
+
+ String migrationPath = migrationConfig.getMigrationPath();
+
+ ClassLoader classLoader = serverConfig.getClassLoadConfig().getClassLoader();
+
+ Scanner scanner = new Scanner(classLoader);
+ List resourceList = scanner.scanForResources(migrationPath, new Match(migrationConfig));
+
+ logger.debug("resources: {}", resourceList);
+
+ for (Resource resource : resourceList) {
+ String filename = resource.getFilename();
+ if (filename.endsWith(migrationConfig.getApplySuffix())) {
+ int pos = filename.lastIndexOf(migrationConfig.getApplySuffix());
+ String mainName = filename.substring(0, pos);
+
+ MigrationVersion migrationVersion = MigrationVersion.parse(mainName);
+ LocalMigrationResource res = new LocalMigrationResource(migrationVersion, resource.getLocation(), resource);
+ versions.add(res);
+ }
+ }
+
+ Collections.sort(versions);
+ return !versions.isEmpty();
+ }
+
+ public List getVersions() {
+ return versions;
+ }
+
+
+ static class Match implements MatchResource {
+
+ final DbMigrationConfig migrationConfig;
+
+ Match(DbMigrationConfig migrationConfig) {
+ this.migrationConfig = migrationConfig;
+ }
+
+ @Override
+ public boolean isMatch(String name) {
+
+ return name.endsWith(migrationConfig.getApplySuffix())
+ || name.endsWith(migrationConfig.getModelSuffix())
+ || name.endsWith(migrationConfig.getDropSuffix())
+ || name.endsWith(migrationConfig.getRollbackSuffix());
+
+ }
+ }
+}
diff --git a/src/main/java/com/avaje/ebean/dbmigration/runner/MigrationMetaRow.java b/src/main/java/com/avaje/ebean/dbmigration/runner/MigrationMetaRow.java
new file mode 100644
index 000000000..5a066ac87
--- /dev/null
+++ b/src/main/java/com/avaje/ebean/dbmigration/runner/MigrationMetaRow.java
@@ -0,0 +1,27 @@
+package com.avaje.ebean.dbmigration.runner;
+
+import java.sql.Timestamp;
+
+/**
+ *
+ */
+public class MigrationMetaRow implements Comparable {
+
+ int id;
+ String status;
+ String runVersion;
+ String depVersion;
+ String comment;
+ int checksum;
+ Timestamp runOn;
+ String runBy;
+
+ @Override
+ public int compareTo(MigrationMetaRow o) {
+ return (id < o.id) ? -1 : ((id == o.id) ? 0 : 1);
+ }
+
+
+ //String sql = "insert into ebean_migration (id,status,run_version,dep_version,comment,checksum,run_on,run_by,run_ip) "+
+ // "values (?,?,?,?,?,?,?,?,?)";
+}
diff --git a/src/main/java/com/avaje/ebean/dbmigration/runner/MigrationScriptRunner.java b/src/main/java/com/avaje/ebean/dbmigration/runner/MigrationScriptRunner.java
new file mode 100644
index 000000000..f52ae6caf
--- /dev/null
+++ b/src/main/java/com/avaje/ebean/dbmigration/runner/MigrationScriptRunner.java
@@ -0,0 +1,26 @@
+package com.avaje.ebean.dbmigration.runner;
+
+import com.avaje.ebean.dbmigration.DdlRunner;
+
+import java.sql.Connection;
+
+/**
+ * Created by rob on 5/02/16.
+ */
+public class MigrationScriptRunner {
+
+ final Connection connection;
+
+ public MigrationScriptRunner(Connection connection) {
+ this.connection = connection;
+ }
+
+ /**
+ * Execute all the DDL statements in the script.
+ */
+ public int runScript(boolean expectErrors, String content, String scriptName) {
+
+ DdlRunner runner = new DdlRunner(expectErrors, scriptName);
+ return runner.runAll(content, connection);
+ }
+}
diff --git a/src/main/java/com/avaje/ebean/dbmigration/runner/MigrationTable.java b/src/main/java/com/avaje/ebean/dbmigration/runner/MigrationTable.java
new file mode 100644
index 000000000..c5d15a6ad
--- /dev/null
+++ b/src/main/java/com/avaje/ebean/dbmigration/runner/MigrationTable.java
@@ -0,0 +1,164 @@
+package com.avaje.ebean.dbmigration.runner;
+
+import com.avaje.ebean.EbeanServer;
+import com.avaje.ebean.SqlQuery;
+import com.avaje.ebean.SqlRow;
+import com.avaje.ebean.SqlUpdate;
+import com.avaje.ebean.config.DbMigrationConfig;
+import com.avaje.ebean.config.ServerConfig;
+import com.avaje.ebean.config.dbplatform.DatabasePlatform;
+import com.avaje.ebean.plugin.SpiServer;
+import com.avaje.ebeaninternal.server.transaction.ExternalJdbcTransaction;
+import com.avaje.ebeaninternal.util.IOUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.IOException;
+import java.net.URL;
+import java.sql.Connection;
+import java.sql.SQLException;
+import java.sql.Timestamp;
+import java.util.Enumeration;
+import java.util.List;
+
+/**
+ * Created by rob on 5/02/16.
+ */
+public class MigrationTable {
+
+ private static final Logger logger = LoggerFactory.getLogger(MigrationTable.class);
+
+ private final Connection connection;
+
+ private final EbeanServer server;
+
+ private final DatabasePlatform databasePlatform;
+
+ private final DbMigrationConfig migrationConfig;
+
+ private final String catalog;
+ private final String schema;
+ private final String table;
+ private final ServerConfig serverConfig;
+ private final String envUserName;
+
+ int currentSearchPosition;
+
+ List metaRows;
+
+ public MigrationTable(EbeanServer server, DbMigrationConfig migrationConfig, Connection connection) {
+ this.connection = connection;
+ this.server = server;
+
+ SpiServer pluginApi = server.getPluginApi();
+ this.serverConfig = pluginApi.getServerConfig();
+ this.databasePlatform = pluginApi.getDatabasePlatform();
+ this.migrationConfig = migrationConfig;
+ this.catalog = null;
+ this.schema = null;
+ this.table = "ebean_migration";
+
+ this.envUserName = System.getProperty("user.name");
+ }
+
+
+ public void createIfNeeded() throws SQLException, IOException {
+
+ if (!tableExists(connection)) {
+ createTable(connection);
+ }
+
+ ExternalJdbcTransaction t = new ExternalJdbcTransaction(connection);
+ SqlQuery sqlQuery = server.createSqlQuery("select * from ebean_migration order by id for update");
+ metaRows = server.findList(sqlQuery, t);
+ }
+
+ private void createTable(Connection connection) throws IOException {
+
+ String script = getCreateTableScript();
+ MigrationScriptRunner run = new MigrationScriptRunner(connection);
+ run.runScript(false, script, "create migration tables");
+ }
+
+ private String getCreateTableScript() throws IOException {
+
+ Enumeration resources = serverConfig.getClassLoadConfig().getResources("migration-support/create.sql");
+ if (resources.hasMoreElements()) {
+ URL url = resources.nextElement();
+ return IOUtils.readUtf8(url.openStream());
+ }
+ return null;
+ }
+
+ public boolean tableExists(Connection connection) throws SQLException {
+
+ return databasePlatform.tableExists(connection, catalog, schema, table);
+ }
+
+
+ public boolean shouldRun(LocalMigrationResource localVersion, LocalMigrationResource priorVersion) {
+
+ // if prior != null check previous version installed
+ // if previous version not installed ... error - missing version (prior version)
+
+// if (!priorVersionNotInstalled(priorVersion)) {
+//
+// }
+//
+// // if localVersion installed
+// // check checksum and return ok, or re-installable?
+// // else
+// // install version and continue
+//
+// if (runPosition >= metaRows.size()) {
+// logger.debug("No matching row");
+// runMigration(runPosition, localVersion);
+// return true;
+// }
+
+ return false;
+ }
+
+ private void checkInstalled(LocalMigrationResource priorVersion) {
+ if (priorVersion != null) {
+ searchFor(priorVersion);
+ }
+ }
+
+ private void searchFor(LocalMigrationResource priorVersion) {
+
+ }
+
+ public void commit() throws SQLException {
+ connection.commit();
+ }
+
+ private void runMigration(int runPosition, LocalMigrationResource localVersion) {
+
+ logger.debug("run migration "+localVersion.getLocation());
+
+
+ String script = localVersion.getContent();
+
+ MigrationScriptRunner run = new MigrationScriptRunner(connection);
+ run.runScript(false, script, "run migration version: "+localVersion.getVersion());
+
+ String sql = "insert into ebean_migration (id,status,run_version,dep_version,comment,checksum,run_on,run_by,run_ip) "+
+ "values (?,?,?,?,?,?,?,?,?)";
+
+ SqlUpdate sqlUpdate = server.createSqlUpdate(sql);
+ sqlUpdate.setParameter(1, runPosition+1);
+ sqlUpdate.setParameter(2, "success");
+ sqlUpdate.setParameter(3, localVersion.getVersion().getFull());
+ sqlUpdate.setParameter(4, "na");
+ sqlUpdate.setParameter(5, "comm");
+ sqlUpdate.setParameter(6, 0);
+ sqlUpdate.setParameter(7, new Timestamp(System.currentTimeMillis()));
+ sqlUpdate.setParameter(8, envUserName);
+ sqlUpdate.setParameter(9, "userIp");
+
+ ExternalJdbcTransaction t = new ExternalJdbcTransaction(connection);
+ server.execute(sqlUpdate, t);
+
+ }
+}
diff --git a/src/main/java/com/avaje/ebean/dbmigration/runner/PlaceholderBuilder.java b/src/main/java/com/avaje/ebean/dbmigration/runner/PlaceholderBuilder.java
new file mode 100644
index 000000000..0789b474d
--- /dev/null
+++ b/src/main/java/com/avaje/ebean/dbmigration/runner/PlaceholderBuilder.java
@@ -0,0 +1,7 @@
+package com.avaje.ebean.dbmigration.runner;
+
+/**
+ * Created by rob on 1/04/16.
+ */
+public class PlaceholderBuilder {
+}
diff --git a/src/main/java/com/avaje/ebean/dbmigration/runner/ScriptTransform.java b/src/main/java/com/avaje/ebean/dbmigration/runner/ScriptTransform.java
new file mode 100644
index 000000000..d208527d0
--- /dev/null
+++ b/src/main/java/com/avaje/ebean/dbmigration/runner/ScriptTransform.java
@@ -0,0 +1,7 @@
+package com.avaje.ebean.dbmigration.runner;
+
+/**
+ * Created by rob on 1/04/16.
+ */
+public class ScriptTransform {
+}
diff --git a/src/main/java/com/avaje/ebeaninternal/server/core/DefaultServer.java b/src/main/java/com/avaje/ebeaninternal/server/core/DefaultServer.java
index 0581b97b9..657bbd20c 100644
--- a/src/main/java/com/avaje/ebeaninternal/server/core/DefaultServer.java
+++ b/src/main/java/com/avaje/ebeaninternal/server/core/DefaultServer.java
@@ -14,6 +14,7 @@ import com.avaje.ebean.config.EncryptKeyManager;
import com.avaje.ebean.config.ServerConfig;
import com.avaje.ebean.config.dbplatform.DatabasePlatform;
import com.avaje.ebean.dbmigration.DdlGenerator;
+import com.avaje.ebean.dbmigration.MigrationRunner;
import com.avaje.ebean.event.BeanPersistController;
import com.avaje.ebean.event.readaudit.ReadAuditLogger;
import com.avaje.ebean.event.readaudit.ReadAuditPrepare;
@@ -339,6 +340,9 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
if (migrationConfig != null) {
migrationConfig.generateOnStart(this);
}
+
+ MigrationRunner migrationRunner = new MigrationRunner(this);
+ migrationRunner.run();
}
/**
diff --git a/src/main/java/com/avaje/ebeaninternal/util/EncodeUtil.java b/src/main/java/com/avaje/ebeaninternal/util/EncodeUtil.java
new file mode 100644
index 000000000..e5c45aff9
--- /dev/null
+++ b/src/main/java/com/avaje/ebeaninternal/util/EncodeUtil.java
@@ -0,0 +1,99 @@
+package com.avaje.ebeaninternal.util;
+
+
+import java.io.UnsupportedEncodingException;
+import java.net.URLDecoder;
+import java.net.URLEncoder;
+
+
+/**
+ * Utilities for encoding and decoding strings.
+ */
+public final class EncodeUtil {
+
+ private EncodeUtil() {
+ /* no instances */
+ }
+
+
+ /**
+ * URL-encodes the specified UTF-8 string.
+ */
+ public static String urlEncode(String string) {
+ if (string == null) return null;
+ try {
+ return URLEncoder.encode(string, "UTF-8");
+ } catch (UnsupportedEncodingException e) {
+ throw new IllegalStateException("Support for UTF-8 is mandated by the Java spec", e);
+ }
+ }
+
+ /**
+ * URL-decodes the specified string as a UTF-8 string.
+ */
+ public static String urlDecode(String string) {
+ if (string == null) return null;
+ try {
+ return URLDecoder.decode(string, "UTF-8");
+ } catch (UnsupportedEncodingException e) {
+ throw new IllegalStateException("Support for UTF-8 is mandated by the Java spec", e);
+ }
+ }
+
+ /**
+ * Returns the bytes corresponding to the specified ASCII string.
+ */
+ public static byte[] asciiToBytes(String string) {
+ if (string == null) return null;
+ try {
+ return string.getBytes("US-ASCII");
+ } catch (UnsupportedEncodingException e) {
+ throw new IllegalStateException("Support for US-ASCII is mandated by the Java spec", e);
+ }
+ }
+
+ /**
+ * Returns the ASCII string corresponding to the specified bytes.
+ */
+ public static String bytesToAscii(byte[] data) {
+ try {
+ return new String(data, "US-ASCII");
+ } catch (UnsupportedEncodingException e) {
+ throw new IllegalStateException("Support for US-ASCII is mandated by the Java spec", e);
+ }
+ }
+
+ /**
+ * Returns the bytes corresponding to the specified UTF-8 string.
+ */
+ public static byte[] utf8ToBytes(String string) {
+ if (string == null) return null;
+ try {
+ return string.getBytes("UTF-8");
+ } catch (UnsupportedEncodingException e) {
+ throw new IllegalStateException("Support for UTF-8 is mandated by the Java spec", e);
+ }
+ }
+
+ /**
+ * Returns the UTF-8 string corresponding to the specified bytes.
+ */
+ public static String bytesToUtf8(byte[] data) {
+ try {
+ return new String(data, "UTF-8");
+ } catch (UnsupportedEncodingException e) {
+ throw new IllegalStateException("Support for UTF-8 is mandated by the Java spec", e);
+ }
+ }
+
+ /**
+ * Returns the UTF-8 string corresponding to the specified bytes.
+ */
+ public static String decodeBytes(byte[] data, String encoding) {
+ try {
+ return new String(data, encoding);
+ } catch (UnsupportedEncodingException e) {
+ throw new RuntimeException("Error decoding bytes with "+encoding, e);
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/com/avaje/ebeaninternal/util/IOUtils.java b/src/main/java/com/avaje/ebeaninternal/util/IOUtils.java
new file mode 100644
index 000000000..4d89e28b3
--- /dev/null
+++ b/src/main/java/com/avaje/ebeaninternal/util/IOUtils.java
@@ -0,0 +1,95 @@
+package com.avaje.ebeaninternal.util;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.io.Reader;
+
+/**
+ * Created by rob on 5/02/16.
+ */
+public class IOUtils {
+ /**
+ * Reads the entire contents of the specified input stream and returns them
+ * as a byte array.
+ */
+ public static byte[] read(InputStream in) throws IOException {
+ ByteArrayOutputStream buffer = new ByteArrayOutputStream();
+ pump(in, buffer);
+ return buffer.toByteArray();
+ }
+
+ /**
+ * Reads the entire contents of the specified input stream and returns them
+ * as an ASCII string.
+ */
+ public static String readAscii(InputStream in) throws IOException {
+ return EncodeUtil.bytesToAscii(read(in));
+ }
+
+ /**
+ * Reads the entire contents of the specified input stream and returns them
+ * as UTF-8 string.
+ */
+ public static String readUtf8(InputStream in) throws IOException {
+ return EncodeUtil.bytesToUtf8(read(in));
+ }
+
+ /**
+ * Reads the entire contents of the specified input stream and returns them
+ * as a string using the encoding supplied.
+ */
+ public static String readEncoded(InputStream in, String encoding) throws IOException {
+ return EncodeUtil.decodeBytes(read(in), encoding);
+ }
+
+ public static String read(Reader reader) throws IOException {
+
+ StringBuilder sb = new StringBuilder();
+ try {
+ char[] buffer = new char[4096];
+ for (; ; ) {
+ int len = reader.read(buffer);
+ if (len < 0) {
+ break;
+ }
+ sb.append(buffer, 0, len);
+ }
+ } finally {
+ reader.close();
+ }
+
+ return sb.toString();
+ }
+
+ /**
+ * Reads data from the specified input stream and copies it to the specified
+ * output stream, until the input stream is at EOF. Both streams are then
+ * closed.
+ *
+ * @throws IOException if the input or output stream is null
+ */
+ public static void pump(InputStream in, OutputStream out) throws IOException {
+
+ if (in == null) throw new IOException("Input stream is null");
+ if (out == null) throw new IOException("Output stream is null");
+
+ try {
+ try {
+ byte[] buffer = new byte[4096];
+ for (; ; ) {
+ int bytes = in.read(buffer);
+ if (bytes < 0) {
+ break;
+ }
+ out.write(buffer, 0, bytes);
+ }
+ } finally {
+ in.close();
+ }
+ } finally {
+ out.close();
+ }
+ }
+}
diff --git a/src/main/java/com/avaje/ebeaninternal/util/JdbcClose.java b/src/main/java/com/avaje/ebeaninternal/util/JdbcClose.java
new file mode 100644
index 000000000..bfdb83532
--- /dev/null
+++ b/src/main/java/com/avaje/ebeaninternal/util/JdbcClose.java
@@ -0,0 +1,26 @@
+package com.avaje.ebeaninternal.util;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.sql.Connection;
+import java.sql.SQLException;
+
+/**
+ * Utility for closing raw Jdbc resources.
+ */
+public class JdbcClose {
+
+ private static final Logger logger = LoggerFactory.getLogger(JdbcClose.class);
+
+ /**
+ * Close the connection logging if an error occurs.
+ */
+ public static void close(Connection connection) {
+ try {
+ connection.close();
+ } catch (SQLException e) {
+ logger.warn("Error closing connection", e);
+ }
+ }
+}
diff --git a/src/main/resources/migration-support/default-create-table.sql b/src/main/resources/migration-support/default-create-table.sql
new file mode 100644
index 000000000..e69de29bb
diff --git a/src/test/java/com/avaje/ebean/dbmigration/MigrationRunnerTest.java b/src/test/java/com/avaje/ebean/dbmigration/MigrationRunnerTest.java
new file mode 100644
index 000000000..aafc2da51
--- /dev/null
+++ b/src/test/java/com/avaje/ebean/dbmigration/MigrationRunnerTest.java
@@ -0,0 +1,10 @@
+package com.avaje.ebean.dbmigration;
+
+import static org.junit.Assert.*;
+
+/**
+ * Created by rob on 1/04/16.
+ */
+public class MigrationRunnerTest {
+
+}
\ No newline at end of file
diff --git a/src/test/java/com/avaje/ebean/dbmigration/runner/ChecksumTest.java b/src/test/java/com/avaje/ebean/dbmigration/runner/ChecksumTest.java
new file mode 100644
index 000000000..b268c4444
--- /dev/null
+++ b/src/test/java/com/avaje/ebean/dbmigration/runner/ChecksumTest.java
@@ -0,0 +1,18 @@
+package com.avaje.ebean.dbmigration.runner;
+
+import org.junit.Test;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+public class ChecksumTest {
+
+ @Test
+ public void test_calculate() throws Exception {
+
+ int checkFoo = Checksum.calculate("foo");
+
+ assertThat(Checksum.calculate("foo")).isEqualTo(checkFoo);
+ assertThat(Checksum.calculate("Foo")).isNotEqualTo(checkFoo);
+ }
+
+}
\ No newline at end of file
diff --git a/src/test/java/com/avaje/ebean/dbmigration/runner/PlaceholderBuilderTest.java b/src/test/java/com/avaje/ebean/dbmigration/runner/PlaceholderBuilderTest.java
new file mode 100644
index 000000000..b12c63002
--- /dev/null
+++ b/src/test/java/com/avaje/ebean/dbmigration/runner/PlaceholderBuilderTest.java
@@ -0,0 +1,16 @@
+package com.avaje.ebean.dbmigration.runner;
+
+import org.junit.Test;
+
+import static org.junit.Assert.*;
+
+/**
+ * Created by rob on 1/04/16.
+ */
+public class PlaceholderBuilderTest {
+ @Test
+ public void build() throws Exception {
+
+ }
+
+}
\ No newline at end of file
diff --git a/src/test/java/com/avaje/ebean/dbmigration/runner/ScriptTransformTest.java b/src/test/java/com/avaje/ebean/dbmigration/runner/ScriptTransformTest.java
new file mode 100644
index 000000000..96309c25d
--- /dev/null
+++ b/src/test/java/com/avaje/ebean/dbmigration/runner/ScriptTransformTest.java
@@ -0,0 +1,16 @@
+package com.avaje.ebean.dbmigration.runner;
+
+import org.junit.Test;
+
+import static org.junit.Assert.*;
+
+/**
+ * Created by rob on 1/04/16.
+ */
+public class ScriptTransformTest {
+ @Test
+ public void transform() throws Exception {
+
+ }
+
+}
\ No newline at end of file
diff --git a/src/test/resources/test-dbmigration/1.1.sql b/src/test/resources/test-dbmigration/1.1.sql
new file mode 100644
index 000000000..51c1802d3
--- /dev/null
+++ b/src/test/resources/test-dbmigration/1.1.sql
@@ -0,0 +1 @@
+create table mtest1 (acol varchar(20));
diff --git a/src/test/resources/test-dbmigration/1.2.sql b/src/test/resources/test-dbmigration/1.2.sql
new file mode 100644
index 000000000..6706d9f3e
--- /dev/null
+++ b/src/test/resources/test-dbmigration/1.2.sql
@@ -0,0 +1 @@
+create table mtest2 (acol varchar(20));
diff --git a/src/test/resources/test-dbmigration/2.1.sql b/src/test/resources/test-dbmigration/2.1.sql
new file mode 100644
index 000000000..b9d64094e
--- /dev/null
+++ b/src/test/resources/test-dbmigration/2.1.sql
@@ -0,0 +1 @@
+create table mtest3 (acol varchar(20));