keycloak-aplcache

Merge pull request #2486 from mposolda/1.9.x databaseSchema

4/5/2016 3:48:37 AM

Details

diff --git a/docbook/auth-server-docs/reference/en/en-US/modules/server-installation.xml b/docbook/auth-server-docs/reference/en/en-US/modules/server-installation.xml
index 7f0d631..acc75d2 100755
--- a/docbook/auth-server-docs/reference/en/en-US/modules/server-installation.xml
+++ b/docbook/auth-server-docs/reference/en/en-US/modules/server-installation.xml
@@ -186,7 +186,9 @@ bin/add-user-keycloak.[sh|bat] -r master -u <username> -p <password>
                         <term>databaseSchema</term>
                         <listitem>
                             <para>
-                                Specify if schema should be updated or validated. Valid values are "update" and "validate" ("update is default).
+                                Specify if schema should be updated or validated. Valid values are <literal>update</literal> and <literal>validate</literal>.
+                                Value <literal>update</literal> is default and means that DB schema will be updated to latest version.
+                                Value <literal>validate</literal> won't touch database schema, but it will fail to start if schema is not updated to latest version.
                             </para>
                         </listitem>
                     </varlistentry>
@@ -303,7 +305,11 @@ bin/add-user-keycloak.[sh|bat] -r master -u <username> -p <password>
 },
 
 "user": {
-    "provider": "${keycloak.user.provider:jpa}"
+    "provider": "jpa"
+},
+
+"userSessionPersister": {
+    "provider": "jpa"
 },
 ]]></programlisting>
 
@@ -324,6 +330,10 @@ bin/add-user-keycloak.[sh|bat] -r master -u <username> -p <password>
 "user": {
     "provider": "mongo"
 },
+
+"userSessionPersister": {
+    "provider": "mongo"
+},
 ]]></programlisting>
 
                 And at the end of the file add the snippet like this where you can configure details about your Mongo database:
@@ -333,7 +343,8 @@ bin/add-user-keycloak.[sh|bat] -r master -u <username> -p <password>
         "host": "127.0.0.1",
         "port": "27017",
         "db": "keycloak",
-        "connectionsPerHost": 100
+        "connectionsPerHost": 100,
+        "databaseSchema": "update"
     }
 }
 ]]></programlisting>
@@ -343,6 +354,14 @@ bin/add-user-keycloak.[sh|bat] -r master -u <username> -p <password>
                 if you want authenticate against your MongoDB. If user and password are not specified, Keycloak will connect
                 unauthenticated to your MongoDB.
             </para>
+            <para>
+                Option <literal>databaseSchema</literal> specify if mongo database "schema" should be updated or validated. Valid values are <literal>update</literal> and <literal>validate</literal>.
+                Value <literal>update</literal> means that DB schema will be updated to latest version.
+                Value <literal>validate</literal> won't touch database schema, but it will fail to start if schema is not updated to latest version.
+            </para>
+            <note>
+                Mongo doesn't have real database schema, but 'schema' in this case means to which Keycloak version the data in the Mongo database corresponds.
+            </note>
             <para>Finally there is set of optional configuration options, which can be used to specify connection-pooling capabilities of Mongo client. Supported int options are:
                 <literal>connectionsPerHost</literal>, <literal>threadsAllowedToBlockForConnectionMultiplier</literal>, <literal>maxWaitTime</literal>, <literal>connectTimeout</literal>
                 <literal>socketTimeout</literal>. Supported boolean options are: <literal>socketKeepAlive</literal>, <literal>autoConnectRetry</literal>.
@@ -359,7 +378,8 @@ bin/add-user-keycloak.[sh|bat] -r master -u <username> -p <password>
 "connectionsMongo": {
     "default": {
         "uri": "mongodb://user:password@127.0.0.1/authentication",
-        "db": "keycloak"
+        "db": "keycloak",
+        "databaseSchema": "update"
     }
 }
 ]]></programlisting>
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 cd7c6e3..11630e7 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
@@ -95,8 +95,6 @@ public class DefaultJpaConnectionProviderFactory implements JpaConnectionProvide
 
                     Connection connection = null;
 
-                    String databaseSchema = config.get("databaseSchema");
-
                     Map<String, Object> properties = new HashMap<String, Object>();
 
                     String unitName = "keycloak-default";
@@ -127,14 +125,18 @@ public class DefaultJpaConnectionProviderFactory implements JpaConnectionProvide
                         properties.put(JpaUtils.HIBERNATE_DEFAULT_SCHEMA, schema);
                     }
 
-                    if (databaseSchema != null) {
-                        if (databaseSchema.equals("development-update")) {
-                            properties.put("hibernate.hbm2ddl.auto", "update");
-                            databaseSchema = null;
-                        } else if (databaseSchema.equals("development-validate")) {
-                            properties.put("hibernate.hbm2ddl.auto", "validate");
-                            databaseSchema = null;
-                        }
+
+                    String databaseSchema = config.get("databaseSchema");
+                    if (databaseSchema == null) {
+                        throw new RuntimeException("Property 'databaseSchema' needs to be specified in the configuration");
+                    }
+                    
+                    if (databaseSchema.equals("development-update")) {
+                        properties.put("hibernate.hbm2ddl.auto", "update");
+                        databaseSchema = null;
+                    } else if (databaseSchema.equals("development-validate")) {
+                        properties.put("hibernate.hbm2ddl.auto", "validate");
+                        databaseSchema = null;
                     }
 
                     properties.put("hibernate.show_sql", config.getBoolean("showSql", false));
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 61075c7..4a505a3 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
@@ -21,6 +21,7 @@ import liquibase.Contexts;
 import liquibase.Liquibase;
 import liquibase.changelog.ChangeSet;
 import liquibase.changelog.RanChangeSet;
+import liquibase.exception.LiquibaseException;
 import org.jboss.logging.Logger;
 import org.keycloak.connections.jpa.updater.JpaUpdaterProvider;
 import org.keycloak.connections.jpa.updater.liquibase.conn.LiquibaseConnectionProvider;
@@ -96,16 +97,27 @@ public class LiquibaseJpaUpdaterProvider implements JpaUpdaterProvider {
 
     @Override
     public void validate(Connection connection, String defaultSchema) {
+        logger.debug("Validating if database is updated");
+
         try {
             Liquibase liquibase = getLiquibase(connection, defaultSchema);
 
-            liquibase.validate();
-        } catch (Exception e) {
+            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",
+                        ranChangeSets.get(ranChangeSets.size() - 1).getId(), changeSets.get(changeSets.size() - 1).getId());
+                throw new RuntimeException(errorMessage);
+            } else {
+                logger.debug("Validation passed. Database is up-to-date");
+            }
+
+        } catch (LiquibaseException e) {
             throw new RuntimeException("Failed to validate database", e);
         }
     }
 
-    private Liquibase getLiquibase(Connection connection, String defaultSchema) throws Exception {
+    private Liquibase getLiquibase(Connection connection, String defaultSchema) throws LiquibaseException {
         LiquibaseConnectionProvider liquibaseProvider = session.getProvider(LiquibaseConnectionProvider.class);
         return liquibaseProvider.getLiquibase(connection, defaultSchema);
     }
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 70359df..f66c989 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
@@ -156,13 +156,24 @@ public class DefaultMongoConnectionFactoryProvider implements MongoConnectionPro
     }
 
     private void update(KeycloakSession session) {
-        MongoUpdaterProvider mongoUpdater = session.getProvider(MongoUpdaterProvider.class);
+        String databaseSchema = config.get("databaseSchema");
 
-        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");
+        } else {
+            MongoUpdaterProvider mongoUpdater = session.getProvider(MongoUpdaterProvider.class);
+            if (mongoUpdater == null) {
+                throw new RuntimeException("Can't update database: Mongo updater provider not found");
+            }
 
-        mongoUpdater.update(session, db);
+            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);
+            }
+        }
     }
 
 
diff --git a/model/mongo/src/main/java/org/keycloak/connections/mongo/updater/impl/DefaultMongoUpdaterProvider.java b/model/mongo/src/main/java/org/keycloak/connections/mongo/updater/impl/DefaultMongoUpdaterProvider.java
index 889281c..aa4130d 100755
--- a/model/mongo/src/main/java/org/keycloak/connections/mongo/updater/impl/DefaultMongoUpdaterProvider.java
+++ b/model/mongo/src/main/java/org/keycloak/connections/mongo/updater/impl/DefaultMongoUpdaterProvider.java
@@ -65,38 +65,19 @@ public class DefaultMongoUpdaterProvider implements MongoUpdaterProvider {
         log.debug("Starting database update");
         try {
             boolean changeLogExists = db.collectionExists(CHANGE_LOG_COLLECTION);
-            boolean realmExists = db.collectionExists("realms");
-
             DBCollection changeLog = db.getCollection(CHANGE_LOG_COLLECTION);
 
-            List<String> executed = new LinkedList<String>();
-            if (!changeLogExists && realmExists) {
-                Update1_0_0_Final u = new Update1_0_0_Final();
-                executed.add(u.getId());
-                createLog(changeLog, u, 1);
-            } else if (changeLogExists) {
-                DBCursor cursor = changeLog.find().sort(new BasicDBObject("orderExecuted", 1));
-                while (cursor.hasNext()) {
-                    executed.add((String) cursor.next().get("_id"));
-                }
-            }
-
-            List<Update> updatesToRun = new LinkedList<Update>();
-            for (Class<? extends Update> updateClass : updates) {
-                Update u = updateClass.newInstance();
-                if (!executed.contains(u.getId())) {
-                    updatesToRun.add(u);
-                }
-            }
+            List<String> executed = getExecuted(db, changeLogExists, changeLog);
+            List<Update> updatesToRun = getUpdatesToRun(executed);
 
             if (!updatesToRun.isEmpty()) {
                 if (executed.isEmpty()) {
                     log.info("Initializing database schema");
                 } else {
                     if (log.isDebugEnabled()) {
-                        log.infov("Updating database from {0} to {1}", executed.get(executed.size() - 1), updatesToRun.get(updatesToRun.size() - 1).getId());
+                        log.debugv("Updating database from {0} to {1}", executed.get(executed.size() - 1), updatesToRun.get(updatesToRun.size() - 1).getId());
                     } else {
-                        log.debugv("Updating database");
+                        log.info("Updating database");
                     }
                 }
 
@@ -121,10 +102,66 @@ public class DefaultMongoUpdaterProvider implements MongoUpdaterProvider {
         }
     }
 
+
+    @Override
+    public void validate(KeycloakSession session, DB db) {
+        log.debug("Validating database");
+
+        boolean changeLogExists = db.collectionExists(CHANGE_LOG_COLLECTION);
+        DBCollection changeLog = db.getCollection(CHANGE_LOG_COLLECTION);
+
+        List<String> executed = getExecuted(db, changeLogExists, changeLog);
+        List<Update> updatesToRun = getUpdatesToRun(executed);
+
+        if (!updatesToRun.isEmpty()) {
+            String errorMessage = String.format("Failed to validate Mongo database schema. Schema needs updating database from %s to %s. Please change databaseSchema to 'update'",
+                    executed.get(executed.size() - 1), updatesToRun.get(updatesToRun.size() - 1).getId());
+            throw new RuntimeException(errorMessage);
+        } else {
+            log.debug("Validation passed. Database is up to date");
+        }
+    }
+
+
+    private List<String> getExecuted(DB db, boolean changeLogExists, DBCollection changeLog) {
+        boolean realmExists = db.collectionExists("realms");
+
+        List<String> executed = new LinkedList<>();
+        if (!changeLogExists && realmExists) {
+            Update1_0_0_Final u = new Update1_0_0_Final();
+            executed.add(u.getId());
+            createLog(changeLog, u, 1);
+        } else if (changeLogExists) {
+            DBCursor cursor = changeLog.find().sort(new BasicDBObject("orderExecuted", 1));
+            while (cursor.hasNext()) {
+                executed.add((String) cursor.next().get("_id"));
+            }
+        }
+        return executed;
+    }
+
+
+    private List<Update> getUpdatesToRun(List<String> executed) {
+        try {
+            List<Update> updatesToRun = new LinkedList<>();
+            for (Class<? extends Update> updateClass : updates) {
+                Update u = updateClass.newInstance();
+                if (!executed.contains(u.getId())) {
+                    updatesToRun.add(u);
+                }
+            }
+            return updatesToRun;
+        } catch (Exception e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+
     private void createLog(DBCollection changeLog, Update update, int orderExecuted) {
         changeLog.insert(new BasicDBObject("_id", update.getId()).append("dateExecuted", new Date()).append("orderExecuted", orderExecuted));
     }
 
+
     @Override
     public void close() {
     }
diff --git a/model/mongo/src/main/java/org/keycloak/connections/mongo/updater/MongoUpdaterProvider.java b/model/mongo/src/main/java/org/keycloak/connections/mongo/updater/MongoUpdaterProvider.java
index b5000ec..e7224c9 100644
--- a/model/mongo/src/main/java/org/keycloak/connections/mongo/updater/MongoUpdaterProvider.java
+++ b/model/mongo/src/main/java/org/keycloak/connections/mongo/updater/MongoUpdaterProvider.java
@@ -26,6 +26,8 @@ import org.keycloak.provider.Provider;
  */
 public interface MongoUpdaterProvider extends Provider {
 
-    public void update(KeycloakSession session, DB db);
+    void update(KeycloakSession session, DB db);
+
+    void validate(KeycloakSession session, DB db);
 
 }
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 443f3e1..ec76904 100755
--- a/testsuite/integration/src/test/resources/META-INF/keycloak-server.json
+++ b/testsuite/integration/src/test/resources/META-INF/keycloak-server.json
@@ -73,6 +73,7 @@
             "host": "${keycloak.connectionsMongo.host:127.0.0.1}",
             "port": "${keycloak.connectionsMongo.port:27017}",
             "db": "${keycloak.connectionsMongo.db:keycloak}",
+            "databaseSchema": "${keycloak.connectionsMongo.databaseSchema:update}",
             "connectionsPerHost": "${keycloak.connectionsMongo.connectionsPerHost:100}"
         }
     },
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 c20c075..9208927 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
@@ -94,6 +94,7 @@
             "host": "${keycloak.connectionsMongo.host:127.0.0.1}",
             "port": "${keycloak.connectionsMongo.port:27017}",
             "db": "${keycloak.connectionsMongo.db:keycloak}",
+            "databaseSchema": "${keycloak.connectionsMongo.databaseSchema:update}",
             "connectionsPerHost": "${keycloak.connectionsMongo.connectionsPerHost:100}"
         }
     },