keycloak-uncached

Merge pull request #3159 from stianst/master KEYCLOAK-3447

8/23/2016 7:38:28 AM

Details

diff --git a/distribution/feature-packs/server-feature-pack/src/main/resources/content/standalone/configuration/keycloak-server.json b/distribution/feature-packs/server-feature-pack/src/main/resources/content/standalone/configuration/keycloak-server.json
index b2f1067..4097362 100755
--- a/distribution/feature-packs/server-feature-pack/src/main/resources/content/standalone/configuration/keycloak-server.json
+++ b/distribution/feature-packs/server-feature-pack/src/main/resources/content/standalone/configuration/keycloak-server.json
@@ -60,7 +60,9 @@
     "connectionsJpa": {
         "default": {
             "dataSource": "java:jboss/datasources/KeycloakDS",
-            "databaseSchema": "update"
+            "initializeEmpty": true,
+            "migrationStrategy": "update",
+            "migrationExport": "${jboss.home.dir}/keycloak-database-update.sql"
         }
     },
 
diff --git a/model/jpa/src/main/java/org/keycloak/connections/jpa/DefaultJpaConnectionProviderFactory.java b/model/jpa/src/main/java/org/keycloak/connections/jpa/DefaultJpaConnectionProviderFactory.java
index 4272fb8..3c74264 100755
--- a/model/jpa/src/main/java/org/keycloak/connections/jpa/DefaultJpaConnectionProviderFactory.java
+++ b/model/jpa/src/main/java/org/keycloak/connections/jpa/DefaultJpaConnectionProviderFactory.java
@@ -17,6 +17,7 @@
 
 package org.keycloak.connections.jpa;
 
+import java.io.File;
 import java.sql.Connection;
 import java.sql.DatabaseMetaData;
 import java.sql.DriverManager;
@@ -42,6 +43,7 @@ import org.keycloak.models.dblock.DBLockProvider;
 import org.keycloak.models.utils.KeycloakModelUtils;
 import org.keycloak.provider.ServerInfoAwareProviderFactory;
 import org.keycloak.models.dblock.DBLockManager;
+import org.keycloak.ServerStartupError;
 import org.keycloak.timer.TimerProvider;
 
 /**
@@ -51,6 +53,10 @@ public class DefaultJpaConnectionProviderFactory implements JpaConnectionProvide
 
     private static final Logger logger = Logger.getLogger(DefaultJpaConnectionProviderFactory.class);
 
+    enum MigrationStrategy {
+        UPDATE, VALIDATE, MANUAL
+    }
+
     private volatile EntityManagerFactory emf;
 
     private Config.Scope config;
@@ -125,22 +131,9 @@ public class DefaultJpaConnectionProviderFactory implements JpaConnectionProvide
                         properties.put(JpaUtils.HIBERNATE_DEFAULT_SCHEMA, schema);
                     }
 
-
-                    String databaseSchema;
-                    String databaseSchemaConf = config.get("databaseSchema");
-                    if (databaseSchemaConf == null) {
-                        throw new RuntimeException("Property 'databaseSchema' needs to be specified in the configuration");
-                    }
-                    
-                    if (databaseSchemaConf.equals("development-update")) {
-                        properties.put("hibernate.hbm2ddl.auto", "update");
-                        databaseSchema = null;
-                    } else if (databaseSchemaConf.equals("development-validate")) {
-                        properties.put("hibernate.hbm2ddl.auto", "validate");
-                        databaseSchema = null;
-                    } else {
-                        databaseSchema = databaseSchemaConf;
-                    }
+                    MigrationStrategy migrationStrategy = getMigrationStrategy();
+                    boolean initializeEmpty = config.getBoolean("initializeEmpty", true);
+                    File databaseUpdateFile = getDatabaseUpdateFile();
 
                     properties.put("hibernate.show_sql", config.getBoolean("showSql", false));
                     properties.put("hibernate.format_sql", config.getBoolean("formatSql", true));
@@ -153,39 +146,8 @@ public class DefaultJpaConnectionProviderFactory implements JpaConnectionProvide
                         if (driverDialect != null) {
                             properties.put("hibernate.dialect", driverDialect);
                         }
-	                    
-	                    if (databaseSchema != null) {
-	                        logger.trace("Updating database");
-	
-	                        JpaUpdaterProvider updater = session.getProvider(JpaUpdaterProvider.class);
-	                        if (updater == null) {
-	                            throw new RuntimeException("Can't update database: JPA updater provider not found");
-	                        }
 
-                            // Check if having DBLock before trying to initialize hibernate
-                            DBLockProvider dbLock = new DBLockManager(session).getDBLock();
-                            if (dbLock.hasLock()) {
-                                updateOrValidateDB(databaseSchema, connection, updater, schema);
-                            } else {
-                                logger.trace("Don't have DBLock retrieved before upgrade. Needs to acquire lock first in separate transaction");
-
-                                KeycloakModelUtils.runJobInTransaction(session.getKeycloakSessionFactory(), new KeycloakSessionTask() {
-
-                                    @Override
-                                    public void run(KeycloakSession lockSession) {
-                                        DBLockManager dbLockManager = new DBLockManager(lockSession);
-                                        DBLockProvider dbLock2 = dbLockManager.getDBLock();
-                                        dbLock2.waitForLock();
-                                        try {
-                                            updateOrValidateDB(databaseSchema, connection, updater, schema);
-                                        } finally {
-                                            dbLock2.releaseLock();
-                                        }
-                                    }
-
-                                });
-                            }
-	                    }
+                        migration(migrationStrategy, initializeEmpty, schema, databaseUpdateFile, connection, session);
 
                         int globalStatsInterval = config.getInt("globalStatsInterval", -1);
                         if (globalStatsInterval != -1) {
@@ -199,18 +161,6 @@ public class DefaultJpaConnectionProviderFactory implements JpaConnectionProvide
                         if (globalStatsInterval != -1) {
                             startGlobalStats(session, globalStatsInterval);
                         }
-
-                    } catch (Exception e) {
-                        // Safe rollback
-                        if (connection != null) {
-                            try {
-                                connection.rollback();
-                            } catch (SQLException e2) {
-                                logger.warn("Can't rollback connection", e2);
-                            }
-                        }
-
-                        throw e;
                     } finally {
 	                    // Close after creating EntityManagerFactory to prevent in-mem databases from closing
 	                    if (connection != null) {
@@ -226,6 +176,11 @@ public class DefaultJpaConnectionProviderFactory implements JpaConnectionProvide
         }
     }
 
+    private File getDatabaseUpdateFile() {
+        String databaseUpdateFile = config.get("migrationExport", "keycloak-database-update.sql");
+        return new File(databaseUpdateFile);
+    }
+
     protected void prepareOperationalInfo(Connection connection) {
   		try {
   			operationalInfo = new LinkedHashMap<>();
@@ -282,20 +237,82 @@ public class DefaultJpaConnectionProviderFactory implements JpaConnectionProvide
         timer.scheduleTask(new HibernateStatsReporter(emf), globalStatsIntervalSecs * 1000, "ReportHibernateGlobalStats");
     }
 
+    public void migration(MigrationStrategy strategy, boolean initializeEmpty, String schema, File databaseUpdateFile, Connection connection, KeycloakSession session) {
+        JpaUpdaterProvider updater = session.getProvider(JpaUpdaterProvider.class);
+
+        JpaUpdaterProvider.Status status = updater.validate(connection, schema);
+        if (status == JpaUpdaterProvider.Status.VALID) {
+            logger.debug("Database is up-to-date");
+        } else if (status == JpaUpdaterProvider.Status.EMPTY) {
+            if (initializeEmpty) {
+                update(connection, schema, session, updater);
+            } else {
+                switch (strategy) {
+                    case UPDATE:
+                        update(connection, schema, session, updater);
+                        break;
+                    case MANUAL:
+                        export(connection, schema, databaseUpdateFile, session, updater);
+                        throw new ServerStartupError("Database not initialized, please initialize database with " + databaseUpdateFile.getAbsolutePath(), false);
+                    case VALIDATE:
+                        throw new ServerStartupError("Database not initialized, please enable database initialization", false);
+                }
+            }
+        } else {
+            switch (strategy) {
+                case UPDATE:
+                    update(connection, schema, session, updater);
+                    break;
+                case MANUAL:
+                    export(connection, schema, databaseUpdateFile, session, updater);
+                    throw new ServerStartupError("Database not up-to-date, please migrate database with " + databaseUpdateFile.getAbsolutePath(), false);
+                case VALIDATE:
+                    throw new ServerStartupError("Database not up-to-date, please enable database migration", false);
+            }
+        }
+    }
 
-    // Needs to be called with acquired DBLock
-    protected void updateOrValidateDB(String databaseSchema, Connection connection, JpaUpdaterProvider updater, String schema) {
-        if (databaseSchema.equals("update")) {
+    protected void update(Connection connection, String schema, KeycloakSession session, JpaUpdaterProvider updater) {
+        DBLockProvider dbLock = new DBLockManager(session).getDBLock();
+        if (dbLock.hasLock()) {
             updater.update(connection, schema);
-            logger.trace("Database update completed");
-        } else if (databaseSchema.equals("validate")) {
-            updater.validate(connection, schema);
-            logger.trace("Database validation completed");
         } else {
-            throw new RuntimeException("Invalid value for databaseSchema: " + databaseSchema);
+            KeycloakModelUtils.runJobInTransaction(session.getKeycloakSessionFactory(), new KeycloakSessionTask() {
+                @Override
+                public void run(KeycloakSession lockSession) {
+                    DBLockManager dbLockManager = new DBLockManager(lockSession);
+                    DBLockProvider dbLock2 = dbLockManager.getDBLock();
+                    dbLock2.waitForLock();
+                    try {
+                        updater.update(connection, schema);
+                    } finally {
+                        dbLock2.releaseLock();
+                    }
+                }
+            });
         }
     }
 
+    protected void export(Connection connection, String schema, File databaseUpdateFile, KeycloakSession session, JpaUpdaterProvider updater) {
+        DBLockProvider dbLock = new DBLockManager(session).getDBLock();
+        if (dbLock.hasLock()) {
+            updater.export(connection, schema, databaseUpdateFile);
+        } else {
+            KeycloakModelUtils.runJobInTransaction(session.getKeycloakSessionFactory(), new KeycloakSessionTask() {
+                @Override
+                public void run(KeycloakSession lockSession) {
+                    DBLockManager dbLockManager = new DBLockManager(lockSession);
+                    DBLockProvider dbLock2 = dbLockManager.getDBLock();
+                    dbLock2.waitForLock();
+                    try {
+                        updater.export(connection, schema, databaseUpdateFile);
+                    } finally {
+                        dbLock2.releaseLock();
+                    }
+                }
+            });
+        }
+    }
 
     @Override
     public Connection getConnection() {
@@ -323,4 +340,18 @@ public class DefaultJpaConnectionProviderFactory implements JpaConnectionProvide
   		return operationalInfo;
   	}
 
+    private MigrationStrategy getMigrationStrategy() {
+        String migrationStrategy = config.get("migrationStrategy");
+        if (migrationStrategy == null) {
+            // Support 'databaseSchema' for backwards compatibility
+            migrationStrategy = config.get("databaseSchema");
+        }
+
+        if (migrationStrategy != null) {
+            return MigrationStrategy.valueOf(migrationStrategy.toUpperCase());
+        } else {
+            return MigrationStrategy.UPDATE;
+        }
+    }
+
 }
diff --git a/model/jpa/src/main/java/org/keycloak/connections/jpa/updater/JpaUpdaterProvider.java b/model/jpa/src/main/java/org/keycloak/connections/jpa/updater/JpaUpdaterProvider.java
index b087535..37228e3 100755
--- a/model/jpa/src/main/java/org/keycloak/connections/jpa/updater/JpaUpdaterProvider.java
+++ b/model/jpa/src/main/java/org/keycloak/connections/jpa/updater/JpaUpdaterProvider.java
@@ -19,6 +19,7 @@ package org.keycloak.connections.jpa.updater;
 
 import org.keycloak.provider.Provider;
 
+import java.io.File;
 import java.sql.Connection;
 
 /**
@@ -26,10 +27,14 @@ import java.sql.Connection;
  */
 public interface JpaUpdaterProvider extends Provider {
 
-    public String FIRST_VERSION = "1.0.0.Final";
+    enum Status {
+        VALID, EMPTY, OUTDATED
+    }
 
-    public void update(Connection connection, String defaultSchema);
+    void update(Connection connection, String defaultSchema);
 
-    public void validate(Connection connection, String defaultSchema);
+    Status validate(Connection connection, String defaultSchema);
+
+    void export(Connection connection, String defaultSchema, File file);
 
 }
diff --git a/model/jpa/src/main/java/org/keycloak/connections/jpa/updater/liquibase/LiquibaseJpaUpdaterProvider.java b/model/jpa/src/main/java/org/keycloak/connections/jpa/updater/liquibase/LiquibaseJpaUpdaterProvider.java
index 2610174..b4a50a9 100755
--- a/model/jpa/src/main/java/org/keycloak/connections/jpa/updater/liquibase/LiquibaseJpaUpdaterProvider.java
+++ b/model/jpa/src/main/java/org/keycloak/connections/jpa/updater/liquibase/LiquibaseJpaUpdaterProvider.java
@@ -30,6 +30,9 @@ import org.keycloak.connections.jpa.updater.liquibase.conn.LiquibaseConnectionPr
 import org.keycloak.connections.jpa.util.JpaUtils;
 import org.keycloak.models.KeycloakSession;
 
+import java.io.File;
+import java.io.FileWriter;
+import java.io.IOException;
 import java.lang.reflect.Method;
 import java.sql.Connection;
 import java.util.List;
@@ -53,6 +56,15 @@ public class LiquibaseJpaUpdaterProvider implements JpaUpdaterProvider {
 
     @Override
     public void update(Connection connection, String defaultSchema) {
+        update(connection, null, defaultSchema);
+    }
+
+    @Override
+    public void export(Connection connection, String defaultSchema, File file) {
+        update(connection, file, defaultSchema);
+    }
+
+    private void update(Connection connection, File file, String defaultSchema) {
         logger.debug("Starting database update");
 
         // Need ThreadLocal as liquibase doesn't seem to have API to inject custom objects into tasks
@@ -61,7 +73,7 @@ public class LiquibaseJpaUpdaterProvider implements JpaUpdaterProvider {
         try {
             // Run update with keycloak master changelog first
             Liquibase liquibase = getLiquibaseForKeycloakUpdate(connection, defaultSchema);
-            updateChangeSet(liquibase, liquibase.getChangeLogFile());
+            updateChangeSet(liquibase, liquibase.getChangeLogFile(), file);
 
             // Run update for each custom JpaEntityProvider
             Set<JpaEntityProvider> jpaProviders = session.getAllProviders(JpaEntityProvider.class);
@@ -71,7 +83,7 @@ public class LiquibaseJpaUpdaterProvider implements JpaUpdaterProvider {
                     String factoryId = jpaProvider.getFactoryId();
                     String changelogTableName = JpaUtils.getCustomChangelogTableName(factoryId);
                     liquibase = getLiquibaseForCustomProviderUpdate(connection, defaultSchema, customChangelog, jpaProvider.getClass().getClassLoader(), changelogTableName);
-                    updateChangeSet(liquibase, liquibase.getChangeLogFile());
+                    updateChangeSet(liquibase, liquibase.getChangeLogFile(), file);
                 }
             }
         } catch (Exception e) {
@@ -81,7 +93,8 @@ public class LiquibaseJpaUpdaterProvider implements JpaUpdaterProvider {
         }
     }
 
-    protected void updateChangeSet(Liquibase liquibase, String changelog) throws LiquibaseException {
+
+    protected void updateChangeSet(Liquibase liquibase, String changelog, File exportFile) throws LiquibaseException, IOException {
         List<ChangeSet> changeSets = liquibase.listUnrunChangeSets((Contexts) null);
         if (!changeSets.isEmpty()) {
             List<RanChangeSet> ranChangeSets = liquibase.getDatabase().getRanChangeSetList();
@@ -95,7 +108,12 @@ public class LiquibaseJpaUpdaterProvider implements JpaUpdaterProvider {
                 }
             }
 
-            liquibase.update((Contexts) null);
+            if (exportFile != null) {
+                liquibase.update((Contexts) null, new FileWriter(exportFile));
+            } else {
+                liquibase.update((Contexts) null);
+            }
+
             logger.debugv("Completed database update for changelog {0}", changelog);
         } else {
             logger.debugv("Database is up to date for changelog {0}", changelog);
@@ -107,13 +125,18 @@ public class LiquibaseJpaUpdaterProvider implements JpaUpdaterProvider {
     }
 
     @Override
-    public void validate(Connection connection, String defaultSchema) {
+    public Status validate(Connection connection, String defaultSchema) {
         logger.debug("Validating if database is updated");
+        ThreadLocalSessionContext.setCurrentSession(session);
 
         try {
             // Validate with keycloak master changelog first
             Liquibase liquibase = getLiquibaseForKeycloakUpdate(connection, defaultSchema);
-            validateChangeSet(liquibase, liquibase.getChangeLogFile());
+
+            Status status = validateChangeSet(liquibase, liquibase.getChangeLogFile());
+            if (status != Status.VALID) {
+                return status;
+            }
 
             // Validate each custom JpaEntityProvider
             Set<JpaEntityProvider> jpaProviders = session.getAllProviders(JpaEntityProvider.class);
@@ -123,24 +146,30 @@ public class LiquibaseJpaUpdaterProvider implements JpaUpdaterProvider {
                     String factoryId = jpaProvider.getFactoryId();
                     String changelogTableName = JpaUtils.getCustomChangelogTableName(factoryId);
                     liquibase = getLiquibaseForCustomProviderUpdate(connection, defaultSchema, customChangelog, jpaProvider.getClass().getClassLoader(), changelogTableName);
-                    validateChangeSet(liquibase, liquibase.getChangeLogFile());
+                    if (validateChangeSet(liquibase, liquibase.getChangeLogFile()) != Status.VALID) {
+                        return Status.OUTDATED;
+                    }
                 }
             }
-
         } catch (LiquibaseException e) {
             throw new RuntimeException("Failed to validate database", e);
         }
+
+        return Status.VALID;
     }
 
-    protected void validateChangeSet(Liquibase liquibase, String changelog) throws LiquibaseException {
+    protected Status validateChangeSet(Liquibase liquibase, String changelog) throws LiquibaseException {
         List<ChangeSet> changeSets = liquibase.listUnrunChangeSets((Contexts) null);
         if (!changeSets.isEmpty()) {
-            List<RanChangeSet> ranChangeSets = liquibase.getDatabase().getRanChangeSetList();
-            String errorMessage = String.format("Failed to validate database schema. Schema needs updating database from %s to %s. Please change databaseSchema to 'update' or use other database. Used changelog was %s",
-                    ranChangeSets.get(ranChangeSets.size() - 1).getId(), changeSets.get(changeSets.size() - 1).getId(), changelog);
-            throw new RuntimeException(errorMessage);
+            if (changeSets.size() == liquibase.getDatabaseChangeLog().getChangeSets().size()) {
+                return Status.EMPTY;
+            } else {
+                logger.debugf("Validation failed. Database is not up-to-date for changelog %s", changelog);
+                return Status.OUTDATED;
+            }
         } else {
             logger.debugf("Validation passed. Database is up-to-date for changelog %s", changelog);
+            return Status.VALID;
         }
     }
 
diff --git a/model/mongo/src/main/java/org/keycloak/connections/mongo/DefaultMongoConnectionFactoryProvider.java b/model/mongo/src/main/java/org/keycloak/connections/mongo/DefaultMongoConnectionFactoryProvider.java
index b899e0d..ce09046 100755
--- a/model/mongo/src/main/java/org/keycloak/connections/mongo/DefaultMongoConnectionFactoryProvider.java
+++ b/model/mongo/src/main/java/org/keycloak/connections/mongo/DefaultMongoConnectionFactoryProvider.java
@@ -51,6 +51,10 @@ import com.mongodb.ServerAddress;
  */
 public class DefaultMongoConnectionFactoryProvider implements MongoConnectionProviderFactory, ServerInfoAwareProviderFactory {
 
+    enum MigrationStrategy {
+        UPDATE, VALIDATE
+    }
+
     // TODO Make it dynamic
     private String[] entities = new String[]{
             "org.keycloak.models.mongo.keycloak.entities.MongoRealmEntity",
@@ -165,46 +169,34 @@ public class DefaultMongoConnectionFactoryProvider implements MongoConnectionPro
     }
 
     private void update(KeycloakSession session) {
-        String databaseSchema = config.get("databaseSchema");
+        MigrationStrategy strategy = getMigrationStrategy();
+
+        MongoUpdaterProvider mongoUpdater = session.getProvider(MongoUpdaterProvider.class);
+        if (mongoUpdater == null) {
+            throw new RuntimeException("Can't update database: Mongo updater provider not found");
+        }
 
-        if (databaseSchema == null) {
-            throw new RuntimeException("Property 'databaseSchema' needs to be specified in the configuration of mongo connections");
+        DBLockProvider dbLock = new DBLockManager(session).getDBLock();
+        if (dbLock.hasLock()) {
+            updateOrValidateDB(strategy, session, mongoUpdater);
         } else {
-            MongoUpdaterProvider mongoUpdater = session.getProvider(MongoUpdaterProvider.class);
-            if (mongoUpdater == null) {
-                throw new RuntimeException("Can't update database: Mongo updater provider not found");
-            }
+            logger.trace("Don't have DBLock retrieved before upgrade. Needs to acquire lock first in separate transaction");
 
-            DBLockProvider dbLock = new DBLockManager(session).getDBLock();
-            if (dbLock.hasLock()) {
-                updateOrValidateDB(databaseSchema, session, mongoUpdater);
-            } else {
-                logger.trace("Don't have DBLock retrieved before upgrade. Needs to acquire lock first in separate transaction");
-
-                KeycloakModelUtils.runJobInTransaction(session.getKeycloakSessionFactory(), new KeycloakSessionTask() {
-
-                    @Override
-                    public void run(KeycloakSession lockSession) {
-                        DBLockManager dbLockManager = new DBLockManager(lockSession);
-                        DBLockProvider dbLock2 = dbLockManager.getDBLock();
-                        dbLock2.waitForLock();
-                        try {
-                            updateOrValidateDB(databaseSchema, session, mongoUpdater);
-                        } finally {
-                            dbLock2.releaseLock();
-                        }
-                    }
+            KeycloakModelUtils.runJobInTransaction(session.getKeycloakSessionFactory(), new KeycloakSessionTask() {
 
-                });
-            }
+                @Override
+                public void run(KeycloakSession lockSession) {
+                    DBLockManager dbLockManager = new DBLockManager(lockSession);
+                    DBLockProvider dbLock2 = dbLockManager.getDBLock();
+                    dbLock2.waitForLock();
+                    try {
+                        updateOrValidateDB(strategy, session, mongoUpdater);
+                    } finally {
+                        dbLock2.releaseLock();
+                    }
+                }
 
-            if (databaseSchema.equals("update")) {
-                mongoUpdater.update(session, db);
-            } else if (databaseSchema.equals("validate")) {
-                mongoUpdater.validate(session, db);
-            } else {
-                throw new RuntimeException("Invalid value for databaseSchema: " + databaseSchema);
-            }
+            });
         }
     }
 
@@ -217,13 +209,14 @@ public class DefaultMongoConnectionFactoryProvider implements MongoConnectionPro
         return entityClasses;
     }
 
-    protected void updateOrValidateDB(String databaseSchema, KeycloakSession session, MongoUpdaterProvider mongoUpdater) {
-        if (databaseSchema.equals("update")) {
-            mongoUpdater.update(session, db);
-        } else if (databaseSchema.equals("validate")) {
-            mongoUpdater.validate(session, db);
-        } else {
-            throw new RuntimeException("Invalid value for databaseSchema: " + databaseSchema);
+    protected void updateOrValidateDB(MigrationStrategy strategy, KeycloakSession session, MongoUpdaterProvider mongoUpdater) {
+        switch (strategy) {
+            case UPDATE:
+                mongoUpdater.update(session, db);
+                break;
+            case VALIDATE:
+                mongoUpdater.validate(session, db);
+                break;
         }
     }
 
@@ -345,4 +338,18 @@ public class DefaultMongoConnectionFactoryProvider implements MongoConnectionPro
   		return operationalInfo;
   	}
 
+    private MigrationStrategy getMigrationStrategy() {
+        String migrationStrategy = config.get("migrationStrategy");
+        if (migrationStrategy == null) {
+            // Support 'databaseSchema' for backwards compatibility
+            migrationStrategy = config.get("databaseSchema");
+        }
+
+        if (migrationStrategy != null) {
+            return MigrationStrategy.valueOf(migrationStrategy.toUpperCase());
+        } else {
+            return MigrationStrategy.UPDATE;
+        }
+    }
+
 }
diff --git a/server-spi/src/main/java/org/keycloak/ServerStartupError.java b/server-spi/src/main/java/org/keycloak/ServerStartupError.java
new file mode 100644
index 0000000..967e66f
--- /dev/null
+++ b/server-spi/src/main/java/org/keycloak/ServerStartupError.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright 2016 Red Hat, Inc. and/or its affiliates
+ * and other contributors as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.keycloak;
+
+/**
+ * Non-recoverable error thrown during server startup
+ *
+ * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
+ */
+public class ServerStartupError extends Error {
+
+    private final boolean fillStackTrace;
+
+    public ServerStartupError(String message) {
+        super(message);
+        fillStackTrace = true;
+    }
+
+    public ServerStartupError(String message, boolean fillStackTrace) {
+        super(message);
+        this.fillStackTrace = fillStackTrace;
+    }
+
+    @Override
+    public synchronized Throwable fillInStackTrace() {
+        if (fillStackTrace) {
+            return super.fillInStackTrace();
+        } else {
+            return this;
+        }
+    }
+
+}
diff --git a/services/src/main/java/org/keycloak/services/resources/KeycloakApplication.java b/services/src/main/java/org/keycloak/services/resources/KeycloakApplication.java
index 5e1cd9f..dbfc99c 100644
--- a/services/src/main/java/org/keycloak/services/resources/KeycloakApplication.java
+++ b/services/src/main/java/org/keycloak/services/resources/KeycloakApplication.java
@@ -72,69 +72,73 @@ public class KeycloakApplication extends Application {
     protected String contextPath;
 
     public KeycloakApplication(@Context ServletContext context, @Context Dispatcher dispatcher) {
-        loadConfig();
+        try {
+            loadConfig();
 
-        this.contextPath = context.getContextPath();
-        this.sessionFactory = createSessionFactory();
+            this.contextPath = context.getContextPath();
+            this.sessionFactory = createSessionFactory();
 
-        dispatcher.getDefaultContextObjects().put(KeycloakApplication.class, this);
-        ResteasyProviderFactory.pushContext(KeycloakApplication.class, this); // for injection
-        context.setAttribute(KeycloakSessionFactory.class.getName(), this.sessionFactory);
+            dispatcher.getDefaultContextObjects().put(KeycloakApplication.class, this);
+            ResteasyProviderFactory.pushContext(KeycloakApplication.class, this); // for injection
+            context.setAttribute(KeycloakSessionFactory.class.getName(), this.sessionFactory);
 
-        singletons.add(new ServerVersionResource());
-        singletons.add(new RobotsResource());
-        singletons.add(new RealmsResource());
-        singletons.add(new AdminRoot());
-        classes.add(ThemeResource.class);
-        classes.add(JsResource.class);
+            singletons.add(new ServerVersionResource());
+            singletons.add(new RobotsResource());
+            singletons.add(new RealmsResource());
+            singletons.add(new AdminRoot());
+            classes.add(ThemeResource.class);
+            classes.add(JsResource.class);
 
-        classes.add(KeycloakTransactionCommitter.class);
+            classes.add(KeycloakTransactionCommitter.class);
 
-        singletons.add(new ObjectMapperResolver(Boolean.parseBoolean(System.getProperty("keycloak.jsonPrettyPrint", "false"))));
+            singletons.add(new ObjectMapperResolver(Boolean.parseBoolean(System.getProperty("keycloak.jsonPrettyPrint", "false"))));
 
-        ExportImportManager[] exportImportManager = new ExportImportManager[1];
+            ExportImportManager[] exportImportManager = new ExportImportManager[1];
 
-        KeycloakModelUtils.runJobInTransaction(sessionFactory, new KeycloakSessionTask() {
+            KeycloakModelUtils.runJobInTransaction(sessionFactory, new KeycloakSessionTask() {
 
-            @Override
-            public void run(KeycloakSession lockSession) {
-                DBLockManager dbLockManager = new DBLockManager(lockSession);
-                dbLockManager.checkForcedUnlock();
-                DBLockProvider dbLock = dbLockManager.getDBLock();
-                dbLock.waitForLock();
-                try {
-                    exportImportManager[0] = migrateAndBootstrap();
-                } finally {
-                    dbLock.releaseLock();
+                @Override
+                public void run(KeycloakSession lockSession) {
+                    DBLockManager dbLockManager = new DBLockManager(lockSession);
+                    dbLockManager.checkForcedUnlock();
+                    DBLockProvider dbLock = dbLockManager.getDBLock();
+                    dbLock.waitForLock();
+                    try {
+                        exportImportManager[0] = migrateAndBootstrap();
+                    } finally {
+                        dbLock.releaseLock();
+                    }
                 }
-            }
 
-        });
+            });
 
 
-        if (exportImportManager[0].isRunExport()) {
-            exportImportManager[0].runExport();
-        }
+            if (exportImportManager[0].isRunExport()) {
+                exportImportManager[0].runExport();
+            }
 
-        boolean bootstrapAdminUser = false;
-        KeycloakSession session = sessionFactory.create();
-        try {
-            session.getTransactionManager().begin();
-            bootstrapAdminUser = new ApplianceBootstrap(session).isNoMasterUser();
+            boolean bootstrapAdminUser = false;
+            KeycloakSession session = sessionFactory.create();
+            try {
+                session.getTransactionManager().begin();
+                bootstrapAdminUser = new ApplianceBootstrap(session).isNoMasterUser();
 
-            session.getTransactionManager().commit();
-        } finally {
-            session.close();
-        }
+                session.getTransactionManager().commit();
+            } finally {
+                session.close();
+            }
 
-        sessionFactory.publish(new PostMigrationEvent());
+            sessionFactory.publish(new PostMigrationEvent());
 
-        singletons.add(new WelcomeResource(bootstrapAdminUser));
+            singletons.add(new WelcomeResource(bootstrapAdminUser));
 
-        setupScheduledTasks(sessionFactory);
+            setupScheduledTasks(sessionFactory);
+        } catch (Throwable t) {
+            exit(1);
+            throw t;
+        }
     }
 
-
     // Migrate model, bootstrap master realm, import realms and create admin user. This is done with acquired dbLock
     protected ExportImportManager migrateAndBootstrap() {
         ExportImportManager exportImportManager;
@@ -185,7 +189,6 @@ public class KeycloakApplication extends Application {
             session.getTransactionManager().commit();
         } catch (Exception e) {
             session.getTransactionManager().rollback();
-            logger.migrationFailure(e);
             throw e;
         } finally {
             session.close();
@@ -386,4 +389,13 @@ public class KeycloakApplication extends Application {
         }
     }
 
+    private void exit(int status) {
+        new Thread() {
+            @Override
+            public void run() {
+                System.exit(status);
+            }
+        }.start();
+    }
+
 }
diff --git a/testsuite/integration/src/test/resources/META-INF/keycloak-server.json b/testsuite/integration/src/test/resources/META-INF/keycloak-server.json
index d3f87c9..b676b99 100755
--- a/testsuite/integration/src/test/resources/META-INF/keycloak-server.json
+++ b/testsuite/integration/src/test/resources/META-INF/keycloak-server.json
@@ -65,7 +65,8 @@
             "driverDialect": "${keycloak.connectionsJpa.driverDialect:}",
             "user": "${keycloak.connectionsJpa.user:sa}",
             "password": "${keycloak.connectionsJpa.password:}",
-            "databaseSchema": "${keycloak.connectionsJpa.databaseSchema:update}",
+            "initializeEmpty": true,
+            "migrationStrategy": "update",
             "showSql": "${keycloak.connectionsJpa.showSql:false}",
             "formatSql": "${keycloak.connectionsJpa.formatSql:true}",
             "globalStatsInterval": "${keycloak.connectionsJpa.globalStatsInterval:-1}"
diff --git a/testsuite/integration-arquillian/tests/base/src/test/resources/META-INF/keycloak-server.json b/testsuite/integration-arquillian/tests/base/src/test/resources/META-INF/keycloak-server.json
index 99e8614..d8c4dc1 100755
--- a/testsuite/integration-arquillian/tests/base/src/test/resources/META-INF/keycloak-server.json
+++ b/testsuite/integration-arquillian/tests/base/src/test/resources/META-INF/keycloak-server.json
@@ -90,7 +90,8 @@
             "driverDialect": "${keycloak.connectionsJpa.driverDialect:}",
             "user": "${keycloak.connectionsJpa.user:sa}",
             "password": "${keycloak.connectionsJpa.password:}",
-            "databaseSchema": "${keycloak.connectionsJpa.databaseSchema:update}",
+            "initializeEmpty": true,
+            "migrationStrategy": "update",
             "showSql": "${keycloak.connectionsJpa.showSql:false}",
             "formatSql": "${keycloak.connectionsJpa.formatSql:true}",
             "globalStatsInterval": "${keycloak.connectionsJpa.globalStatsInterval:-1}"