keycloak-aplcache

Merge pull request #2224 from patriot1burke/master cache

2/12/2016 3:33:55 PM

Changes

model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/counter/RevisionedCacheRealmProvider.java 401(+0 -401)

model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/counter/RevisionedCacheRealmProviderFactory.java 161(+0 -161)

model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/counter/RevisionedConnectionProviderFactory.java 47(+0 -47)

model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/counter/RevisionedRealmCache.java 255(+0 -255)

model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/skewed/RepeatableReadWriteSkewCacheRealmProviderFactory.java 160(+0 -160)

model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/skewed/RepeatableReadWriteSkewConnectionProviderFactory.java 164(+0 -164)

model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/skewed/RepeatableReadWriteSkewRealmCache.java 269(+0 -269)

model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/skewed/RepeatableReadWriteSkewRealmCacheProvider.java 472(+0 -472)

Details

diff --git a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/DefaultCacheRealmProvider.java b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/DefaultCacheRealmProvider.java
index d80b817..b998b8c 100755
--- a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/DefaultCacheRealmProvider.java
+++ b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/DefaultCacheRealmProvider.java
@@ -120,7 +120,7 @@ public class DefaultCacheRealmProvider implements CacheRealmProvider {
 
     protected void runInvalidations() {
         for (String id : realmInvalidations) {
-            cache.invalidateCachedRealmById(id);
+            cache.invalidateRealmById(id);
         }
         for (String id : roleInvalidations) {
             cache.invalidateRoleById(id);
@@ -129,10 +129,10 @@ public class DefaultCacheRealmProvider implements CacheRealmProvider {
             cache.invalidateGroupById(id);
         }
         for (String id : appInvalidations) {
-            cache.invalidateCachedApplicationById(id);
+            cache.invalidateClientById(id);
         }
         for (String id : clientTemplateInvalidations) {
-            cache.invalidateCachedClientTemplateById(id);
+            cache.invalidateClientTemplateById(id);
         }
     }
 
@@ -193,13 +193,13 @@ public class DefaultCacheRealmProvider implements CacheRealmProvider {
 
     @Override
     public RealmModel getRealm(String id) {
-        CachedRealm cached = cache.getCachedRealm(id);
+        CachedRealm cached = cache.getRealm(id);
         if (cached == null) {
             RealmModel model = getDelegate().getRealm(id);
             if (model == null) return null;
             if (realmInvalidations.contains(id)) return model;
             cached = new CachedRealm(cache, this, model);
-            cache.addCachedRealm(cached);
+            cache.addRealm(cached);
         } else if (realmInvalidations.contains(id)) {
             return getDelegate().getRealm(id);
         } else if (managedRealms.containsKey(id)) {
@@ -212,13 +212,13 @@ public class DefaultCacheRealmProvider implements CacheRealmProvider {
 
     @Override
     public RealmModel getRealmByName(String name) {
-        CachedRealm cached = cache.getCachedRealmByName(name);
+        CachedRealm cached = cache.getRealmByName(name);
         if (cached == null) {
             RealmModel model = getDelegate().getRealmByName(name);
             if (model == null) return null;
             if (realmInvalidations.contains(model.getId())) return model;
             cached = new CachedRealm(cache, this, model);
-            cache.addCachedRealm(cached);
+            cache.addRealm(cached);
         } else if (realmInvalidations.contains(cached.getId())) {
             return getDelegate().getRealmByName(name);
         } else if (managedRealms.containsKey(cached.getId())) {
@@ -245,7 +245,7 @@ public class DefaultCacheRealmProvider implements CacheRealmProvider {
 
     @Override
     public boolean removeRealm(String id) {
-        cache.invalidateCachedRealmById(id);
+        cache.invalidateRealmById(id);
 
         RealmModel realm = getDelegate().getRealm(id);
         Set<RoleModel> realmRoles = null;
@@ -287,7 +287,7 @@ public class DefaultCacheRealmProvider implements CacheRealmProvider {
             } else {
                 cached = new CachedRealmRole(model, realm);
             }
-            cache.addCachedRole(cached);
+            cache.addRole(cached);
 
         } else if (roleInvalidations.contains(id)) {
             return getDelegate().getRoleById(id, realm);
@@ -311,7 +311,7 @@ public class DefaultCacheRealmProvider implements CacheRealmProvider {
             if (model == null) return null;
             if (groupInvalidations.contains(id)) return model;
             cached = new CachedGroup(realm, model);
-            cache.addCachedGroup(cached);
+            cache.addGroup(cached);
 
         } else if (groupInvalidations.contains(id)) {
             return getDelegate().getGroupById(id, realm);
@@ -325,7 +325,7 @@ public class DefaultCacheRealmProvider implements CacheRealmProvider {
 
     @Override
     public ClientModel getClientById(String id, RealmModel realm) {
-        CachedClient cached = cache.getApplication(id);
+        CachedClient cached = cache.getClient(id);
         if (cached != null && !cached.getRealm().equals(realm.getId())) {
             cached = null;
         }
@@ -335,7 +335,7 @@ public class DefaultCacheRealmProvider implements CacheRealmProvider {
             if (model == null) return null;
             if (appInvalidations.contains(id)) return model;
             cached = new CachedClient(cache, getDelegate(), realm, model);
-            cache.addCachedClient(cached);
+            cache.addClient(cached);
         } else if (appInvalidations.contains(id)) {
             return getDelegate().getClientById(id, realm);
         } else if (managedApplications.containsKey(id)) {
@@ -345,6 +345,31 @@ public class DefaultCacheRealmProvider implements CacheRealmProvider {
         managedApplications.put(id, adapter);
         return adapter;
     }
+
+    @Override
+    public ClientModel getClientByClientId(String clientId, RealmModel realm) {
+        return getDelegate().getClientByClientId(clientId, realm);
+    }
+
+    @Override
+    public boolean removeClient(String id, RealmModel realm) {
+        ClientModel client = getClientById(id, realm);
+        if (client == null) return false;
+        registerApplicationInvalidation(id);
+        registerRealmInvalidation(realm.getId());
+        cache.invalidateClientById(id);
+        cache.invalidateRealmById(realm.getId());
+
+
+
+        Set<RoleModel> roles = client.getRoles();
+        for (RoleModel role : roles) {
+            registerRoleInvalidation(role.getId());
+        }
+        return getDelegate().removeClient(id, realm);
+    }
+
+
     @Override
     public ClientTemplateModel getClientTemplateById(String id, RealmModel realm) {
         CachedClientTemplate cached = cache.getClientTemplate(id);
@@ -357,7 +382,7 @@ public class DefaultCacheRealmProvider implements CacheRealmProvider {
             if (model == null) return null;
             if (clientTemplateInvalidations.contains(id)) return model;
             cached = new CachedClientTemplate(cache, getDelegate(), realm, model);
-            cache.addCachedClientTemplate(cached);
+            cache.addClientTemplate(cached);
         } else if (clientTemplateInvalidations.contains(id)) {
             return getDelegate().getClientTemplateById(id, realm);
         } else if (managedClientTemplates.containsKey(id)) {
diff --git a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/InfinispanCacheRealmProviderFactory.java b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/InfinispanCacheRealmProviderFactory.java
index 3c65733..ac8f373 100755
--- a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/InfinispanCacheRealmProviderFactory.java
+++ b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/InfinispanCacheRealmProviderFactory.java
@@ -138,11 +138,11 @@ public class InfinispanCacheRealmProviderFactory implements CacheRealmProviderFa
                 realmLookup.remove(realm.getName());
 
                 for (String r : realm.getRealmRoles().values()) {
-                    realmCache.evictCachedRoleById(r);
+                    realmCache.evictRoleById(r);
                 }
 
                 for (String c : realm.getClients().values()) {
-                    realmCache.evictCachedApplicationById(c);
+                    realmCache.evictClientById(c);
                 }
 
                 log.tracev("Realm removed realm={0}", realm.getName());
@@ -150,7 +150,7 @@ public class InfinispanCacheRealmProviderFactory implements CacheRealmProviderFa
                 CachedClient client = (CachedClient) object;
 
                 for (String r : client.getRoles().values()) {
-                    realmCache.evictCachedRoleById(r);
+                    realmCache.evictRoleById(r);
                 }
 
                 log.tracev("Client removed client={0}", client.getId());
diff --git a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/InfinispanRealmCache.java b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/InfinispanRealmCache.java
index 4f89237..8dfe923 100755
--- a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/InfinispanRealmCache.java
+++ b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/InfinispanRealmCache.java
@@ -53,61 +53,61 @@ public class InfinispanRealmCache implements RealmCache {
     }
 
     @Override
-    public CachedRealm getCachedRealm(String id) {
+    public CachedRealm getRealm(String id) {
         return get(id, CachedRealm.class);
     }
 
     @Override
-    public void invalidateCachedRealm(CachedRealm realm) {
+    public void invalidateRealm(CachedRealm realm) {
         logger.tracev("Invalidating realm {0}", realm.getId());
         cache.remove(realm.getId());
         realmLookup.remove(realm.getName());
     }
 
     @Override
-    public void invalidateCachedRealmById(String id) {
+    public void invalidateRealmById(String id) {
         CachedRealm cached = (CachedRealm) cache.remove(id);
         if (cached != null) realmLookup.remove(cached.getName());
     }
 
     @Override
-    public void addCachedRealm(CachedRealm realm) {
+    public void addRealm(CachedRealm realm) {
         logger.tracev("Adding realm {0}", realm.getId());
         cache.putForExternalRead(realm.getId(), realm);
         realmLookup.put(realm.getName(), realm.getId());
     }
 
     @Override
-    public CachedRealm getCachedRealmByName(String name) {
+    public CachedRealm getRealmByName(String name) {
         String id = realmLookup.get(name);
-        return id != null ? getCachedRealm(id) : null;
+        return id != null ? getRealm(id) : null;
     }
 
     @Override
-    public CachedClient getApplication(String id) {
+    public CachedClient getClient(String id) {
         return get(id, CachedClient.class);
     }
 
     @Override
-    public void invalidateApplication(CachedClient app) {
+    public void invalidateClient(CachedClient app) {
         logger.tracev("Removing application {0}", app.getId());
         cache.remove(app.getId());
     }
 
     @Override
-    public void addCachedClient(CachedClient app) {
+    public void addClient(CachedClient app) {
         logger.tracev("Adding application {0}", app.getId());
         cache.putForExternalRead(app.getId(), app);
     }
 
     @Override
-    public void invalidateCachedApplicationById(String id) {
+    public void invalidateClientById(String id) {
         logger.tracev("Removing application {0}", id);
         cache.remove(id);
     }
 
     @Override
-    public void evictCachedApplicationById(String id) {
+    public void evictClientById(String id) {
         logger.tracev("Evicting application {0}", id);
         cache.evict(id);
     }
@@ -124,19 +124,12 @@ public class InfinispanRealmCache implements RealmCache {
     }
 
     @Override
-    public void addCachedGroup(CachedGroup role) {
+    public void addGroup(CachedGroup role) {
         logger.tracev("Adding group {0}", role.getId());
         cache.putForExternalRead(role.getId(), role);
     }
 
     @Override
-    public void invalidateCachedGroupById(String id) {
-        logger.tracev("Removing group {0}", id);
-        cache.remove(id);
-
-    }
-
-    @Override
     public void invalidateGroupById(String id) {
         logger.tracev("Removing group {0}", id);
         cache.remove(id);
@@ -160,23 +153,17 @@ public class InfinispanRealmCache implements RealmCache {
     }
 
     @Override
-    public void evictCachedRoleById(String id) {
+    public void evictRoleById(String id) {
         logger.tracev("Evicting role {0}", id);
         cache.evict(id);
     }
 
     @Override
-    public void addCachedRole(CachedRole role) {
+    public void addRole(CachedRole role) {
         logger.tracev("Adding role {0}", role.getId());
         cache.putForExternalRead(role.getId(), role);
     }
 
-    @Override
-    public void invalidateCachedRoleById(String id) {
-        logger.tracev("Removing role {0}", id);
-        cache.remove(id);
-    }
-
     private <T> T get(String id, Class<T> type) {
         Object o = cache.get(id);
         return o != null && type.isInstance(o) ? type.cast(o) : null;
@@ -194,19 +181,19 @@ public class InfinispanRealmCache implements RealmCache {
     }
 
     @Override
-    public void addCachedClientTemplate(CachedClientTemplate app) {
+    public void addClientTemplate(CachedClientTemplate app) {
         logger.tracev("Adding client template {0}", app.getId());
         cache.putForExternalRead(app.getId(), app);
     }
 
     @Override
-    public void invalidateCachedClientTemplateById(String id) {
+    public void invalidateClientTemplateById(String id) {
         logger.tracev("Removing client template {0}", id);
         cache.remove(id);
     }
 
     @Override
-    public void evictCachedClientTemplateById(String id) {
+    public void evictClientTemplateById(String id) {
         logger.tracev("Evicting client template {0}", id);
         cache.evict(id);
     }
diff --git a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/locking/LockingCacheRealmProvider.java b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/locking/LockingCacheRealmProvider.java
index ea451b0..4edb046 100755
--- a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/locking/LockingCacheRealmProvider.java
+++ b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/locking/LockingCacheRealmProvider.java
@@ -38,12 +38,12 @@ import org.keycloak.models.cache.infinispan.ClientTemplateAdapter;
 import org.keycloak.models.cache.infinispan.GroupAdapter;
 import org.keycloak.models.cache.infinispan.RealmAdapter;
 import org.keycloak.models.cache.infinispan.RoleAdapter;
-import org.keycloak.models.cache.infinispan.counter.entities.RevisionedCachedClient;
-import org.keycloak.models.cache.infinispan.counter.entities.RevisionedCachedClientRole;
-import org.keycloak.models.cache.infinispan.counter.entities.RevisionedCachedClientTemplate;
-import org.keycloak.models.cache.infinispan.counter.entities.RevisionedCachedGroup;
-import org.keycloak.models.cache.infinispan.counter.entities.RevisionedCachedRealm;
-import org.keycloak.models.cache.infinispan.counter.entities.RevisionedCachedRealmRole;
+import org.keycloak.models.cache.infinispan.locking.entities.RevisionedCachedClient;
+import org.keycloak.models.cache.infinispan.locking.entities.RevisionedCachedClientRole;
+import org.keycloak.models.cache.infinispan.locking.entities.RevisionedCachedClientTemplate;
+import org.keycloak.models.cache.infinispan.locking.entities.RevisionedCachedGroup;
+import org.keycloak.models.cache.infinispan.locking.entities.RevisionedCachedRealm;
+import org.keycloak.models.cache.infinispan.locking.entities.RevisionedCachedRealmRole;
 
 import java.util.Collections;
 import java.util.HashMap;
@@ -105,6 +105,10 @@ public class LockingCacheRealmProvider implements CacheRealmProvider {
         return delegate;
     }
 
+    public LockingRealmCache getCache() {
+        return cache;
+    }
+
     @Override
     public void registerRealmInvalidation(String id) {
         realmInvalidations.add(id);
@@ -132,7 +136,7 @@ public class LockingCacheRealmProvider implements CacheRealmProvider {
 
     protected void runInvalidations() {
         for (String id : realmInvalidations) {
-            cache.invalidateCachedRealmById(id);
+            cache.invalidateRealmById(id);
         }
         for (String id : roleInvalidations) {
             cache.invalidateRoleById(id);
@@ -141,10 +145,10 @@ public class LockingCacheRealmProvider implements CacheRealmProvider {
             cache.invalidateGroupById(id);
         }
         for (String id : appInvalidations) {
-            cache.invalidateCachedApplicationById(id);
+            cache.invalidateClientById(id);
         }
         for (String id : clientTemplateInvalidations) {
-            cache.invalidateCachedClientTemplateById(id);
+            cache.invalidateClientTemplateById(id);
         }
     }
 
@@ -271,17 +275,18 @@ public class LockingCacheRealmProvider implements CacheRealmProvider {
 
     @Override
     public RealmModel getRealm(String id) {
-        CachedRealm cached = cache.getCachedRealm(id);
+        CachedRealm cached = cache.getRealm(id);
         if (cached != null) {
             logger.tracev("by id cache hit: {0}", cached.getName());
         }
         if (cached == null) {
             Long loaded = cache.getCurrentRevision(id);
+            if (loaded == null) loaded = UpdateCounter.current();
             RealmModel model = getDelegate().getRealm(id);
             if (model == null) return null;
             if (realmInvalidations.contains(id)) return model;
             cached = new RevisionedCachedRealm(loaded, cache, this, model);
-            cache.addCachedRealm(cached);
+            cache.addRealm(cached);
         } else if (realmInvalidations.contains(id)) {
             return getDelegate().getRealm(id);
         } else if (managedRealms.containsKey(id)) {
@@ -294,16 +299,17 @@ public class LockingCacheRealmProvider implements CacheRealmProvider {
 
     @Override
     public RealmModel getRealmByName(String name) {
-        CachedRealm cached = cache.getCachedRealmByName(name);
+        CachedRealm cached = cache.getRealmByName(name);
         if (cached != null) {
             logger.tracev("by name cache hit: {0}", cached.getName());
         }
         if (cached == null) {
+            Long loaded = UpdateCounter.current();
             RealmModel model = getDelegate().getRealmByName(name);
             if (model == null) return null;
             if (realmInvalidations.contains(model.getId())) return model;
-            cached = new RevisionedCachedRealm(null, cache, this, model);
-            cache.addCachedRealm(cached);
+            cached = new RevisionedCachedRealm(loaded, cache, this, model);
+            cache.addRealm(cached);
         } else if (realmInvalidations.contains(cached.getId())) {
             return getDelegate().getRealmByName(name);
         } else if (managedRealms.containsKey(cached.getId())) {
@@ -330,7 +336,7 @@ public class LockingCacheRealmProvider implements CacheRealmProvider {
 
     @Override
     public boolean removeRealm(String id) {
-        cache.invalidateCachedRealmById(id);
+        cache.invalidateRealmById(id);
 
         RealmModel realm = getDelegate().getRealm(id);
         Set<RoleModel> realmRoles = null;
@@ -352,6 +358,25 @@ public class LockingCacheRealmProvider implements CacheRealmProvider {
     }
 
     @Override
+    public boolean removeClient(String id, RealmModel realm) {
+        ClientModel client = getClientById(id, realm);
+        if (client == null) return false;
+
+        registerApplicationInvalidation(id);
+        registerRealmInvalidation(realm.getId());
+        cache.invalidateClientById(id);
+        cache.invalidateRealmById(realm.getId());
+
+
+
+        Set<RoleModel> roles = client.getRoles();
+        for (RoleModel role : roles) {
+            registerRoleInvalidation(role.getId());
+        }
+        return getDelegate().removeClient(id, realm);
+    }
+
+    @Override
     public void close() {
         if (delegate != null) delegate.close();
     }
@@ -365,6 +390,7 @@ public class LockingCacheRealmProvider implements CacheRealmProvider {
 
         if (cached == null) {
             Long loaded = cache.getCurrentRevision(id);
+            if (loaded == null) loaded = UpdateCounter.current();
             RoleModel model = getDelegate().getRoleById(id, realm);
             if (model == null) return null;
             if (roleInvalidations.contains(id)) return model;
@@ -373,7 +399,7 @@ public class LockingCacheRealmProvider implements CacheRealmProvider {
             } else {
                 cached = new RevisionedCachedRealmRole(loaded, model, realm);
             }
-            cache.addCachedRole(cached);
+            cache.addRole(cached);
 
         } else if (roleInvalidations.contains(id)) {
             return getDelegate().getRoleById(id, realm);
@@ -394,11 +420,12 @@ public class LockingCacheRealmProvider implements CacheRealmProvider {
 
         if (cached == null) {
             Long loaded = cache.getCurrentRevision(id);
+            if (loaded == null) loaded = UpdateCounter.current();
             GroupModel model = getDelegate().getGroupById(id, realm);
             if (model == null) return null;
             if (groupInvalidations.contains(id)) return model;
             cached = new RevisionedCachedGroup(loaded, realm, model);
-            cache.addCachedGroup(cached);
+            cache.addGroup(cached);
 
         } else if (groupInvalidations.contains(id)) {
             return getDelegate().getGroupById(id, realm);
@@ -412,21 +439,23 @@ public class LockingCacheRealmProvider implements CacheRealmProvider {
 
     @Override
     public ClientModel getClientById(String id, RealmModel realm) {
-        CachedClient cached = cache.getApplication(id);
+        CachedClient cached = cache.getClient(id);
         if (cached != null && !cached.getRealm().equals(realm.getId())) {
             cached = null;
         }
-        if (cached != null && cached.getClientId().equals("client")) {
+        if (cached != null) {
             logger.tracev("client by id cache hit: {0}", cached.getClientId());
         }
 
         if (cached == null) {
             Long loaded = cache.getCurrentRevision(id);
+            if (loaded == null) loaded = UpdateCounter.current();
             ClientModel model = getDelegate().getClientById(id, realm);
             if (model == null) return null;
             if (appInvalidations.contains(id)) return model;
             cached = new RevisionedCachedClient(loaded, cache, getDelegate(), realm, model);
-            cache.addCachedClient(cached);
+            logger.tracev("adding client by id cache miss: {0}", cached.getClientId());
+            cache.addClient(cached);
         } else if (appInvalidations.contains(id)) {
             return getDelegate().getClientById(id, realm);
         } else if (managedApplications.containsKey(id)) {
@@ -436,6 +465,36 @@ public class LockingCacheRealmProvider implements CacheRealmProvider {
         managedApplications.put(id, adapter);
         return adapter;
     }
+
+    @Override
+    public ClientModel getClientByClientId(String clientId, RealmModel realm) {
+        CachedClient cached = cache.getClientByClientId(realm, clientId);
+        if (cached != null && !cached.getRealm().equals(realm.getId())) {
+            cached = null;
+        }
+        if (cached != null) {
+            logger.tracev("client by name cache hit: {0}", cached.getClientId());
+        }
+
+        if (cached == null) {
+            Long loaded = UpdateCounter.current();
+            if (loaded == null) loaded = UpdateCounter.current();
+            ClientModel model = getDelegate().getClientByClientId(clientId, realm);
+            if (model == null) return null;
+            if (appInvalidations.contains(model.getId())) return model;
+            cached = new RevisionedCachedClient(loaded, cache, getDelegate(), realm, model);
+            logger.tracev("adding client by name cache miss: {0}", cached.getClientId());
+            cache.addClient(cached);
+        } else if (appInvalidations.contains(cached.getId())) {
+            return getDelegate().getClientById(cached.getId(), realm);
+        } else if (managedApplications.containsKey(cached.getId())) {
+            return managedApplications.get(cached.getId());
+        }
+        ClientAdapter adapter = new ClientAdapter(realm, cached, this, cache);
+        managedApplications.put(cached.getId(), adapter);
+        return adapter;
+    }
+
     @Override
     public ClientTemplateModel getClientTemplateById(String id, RealmModel realm) {
         CachedClientTemplate cached = cache.getClientTemplate(id);
@@ -445,11 +504,12 @@ public class LockingCacheRealmProvider implements CacheRealmProvider {
 
         if (cached == null) {
             Long loaded = cache.getCurrentRevision(id);
+            if (loaded == null) loaded = UpdateCounter.current();
             ClientTemplateModel model = getDelegate().getClientTemplateById(id, realm);
             if (model == null) return null;
             if (clientTemplateInvalidations.contains(id)) return model;
             cached = new RevisionedCachedClientTemplate(loaded, cache, getDelegate(), realm, model);
-            cache.addCachedClientTemplate(cached);
+            cache.addClientTemplate(cached);
         } else if (clientTemplateInvalidations.contains(id)) {
             return getDelegate().getClientTemplateById(id, realm);
         } else if (managedClientTemplates.containsKey(id)) {
diff --git a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/locking/LockingCacheRealmProviderFactory.java b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/locking/LockingCacheRealmProviderFactory.java
index f143ea3..23f8e53 100755
--- a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/locking/LockingCacheRealmProviderFactory.java
+++ b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/locking/LockingCacheRealmProviderFactory.java
@@ -37,8 +37,6 @@ import org.keycloak.models.cache.CacheRealmProviderFactory;
 import org.keycloak.models.cache.entities.CachedClient;
 import org.keycloak.models.cache.entities.CachedRealm;
 
-import java.util.concurrent.ConcurrentHashMap;
-
 /**
  * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
  * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
@@ -49,8 +47,6 @@ public class LockingCacheRealmProviderFactory implements CacheRealmProviderFacto
 
     protected volatile LockingRealmCache realmCache;
 
-    protected final ConcurrentHashMap<String, String> realmLookup = new ConcurrentHashMap<>();
-
     @Override
     public CacheRealmProvider create(KeycloakSession session) {
         lazyInit(session);
@@ -64,7 +60,7 @@ public class LockingCacheRealmProviderFactory implements CacheRealmProviderFacto
                     Cache<String, Object> cache = session.getProvider(InfinispanConnectionProvider.class).getCache(InfinispanConnectionProvider.REALM_CACHE_NAME);
                     Cache<String, Long> counterCache = session.getProvider(InfinispanConnectionProvider.class).getCache(LockingConnectionProviderFactory.VERSION_CACHE_NAME);
                     cache.addListener(new CacheListener());
-                    realmCache = new LockingRealmCache(cache, counterCache, realmLookup);
+                    realmCache = new LockingRealmCache(cache, counterCache);
                 }
             }
         }
@@ -98,7 +94,7 @@ public class LockingCacheRealmProviderFactory implements CacheRealmProviderFacto
                 if (object != null) {
                     if (object instanceof CachedRealm) {
                         CachedRealm realm = (CachedRealm) object;
-                        realmLookup.put(realm.getName(), realm.getId());
+                        realmCache.getRealmLookup().put(realm.getName(), realm.getId());
                         log.tracev("Realm added realm={0}", realm.getName());
                     }
                 }
@@ -136,22 +132,24 @@ public class LockingCacheRealmProviderFactory implements CacheRealmProviderFacto
             if (object instanceof CachedRealm) {
                 CachedRealm realm = (CachedRealm) object;
 
-                realmLookup.remove(realm.getName());
+                realmCache.getRealmLookup().remove(realm.getName());
 
                 for (String r : realm.getRealmRoles().values()) {
-                    realmCache.evictCachedRoleById(r);
+                    realmCache.evictRoleById(r);
                 }
 
                 for (String c : realm.getClients().values()) {
-                    realmCache.evictCachedApplicationById(c);
+                    realmCache.evictClientById(c);
                 }
 
                 log.tracev("Realm removed realm={0}", realm.getName());
             } else if (object instanceof CachedClient) {
                 CachedClient client = (CachedClient) object;
 
+                realmCache.getClientLookup().remove(client.getRealm() + "." + client.getClientId());
+
                 for (String r : client.getRoles().values()) {
-                    realmCache.evictCachedRoleById(r);
+                    realmCache.evictRoleById(r);
                 }
 
                 log.tracev("Client removed client={0}", client.getId());
diff --git a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/locking/LockingRealmCache.java b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/locking/LockingRealmCache.java
index 9c8e784..a77c59b 100755
--- a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/locking/LockingRealmCache.java
+++ b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/locking/LockingRealmCache.java
@@ -19,16 +19,15 @@ package org.keycloak.models.cache.infinispan.locking;
 
 import org.infinispan.Cache;
 import org.jboss.logging.Logger;
+import org.keycloak.models.RealmModel;
 import org.keycloak.models.cache.RealmCache;
 import org.keycloak.models.cache.entities.CachedClient;
 import org.keycloak.models.cache.entities.CachedClientTemplate;
 import org.keycloak.models.cache.entities.CachedGroup;
 import org.keycloak.models.cache.entities.CachedRealm;
 import org.keycloak.models.cache.entities.CachedRole;
-import org.keycloak.models.cache.infinispan.counter.Revisioned;
 
 import java.util.concurrent.ConcurrentHashMap;
-import java.util.concurrent.atomic.AtomicLong;
 
 /**
  * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
@@ -39,17 +38,12 @@ public class LockingRealmCache implements RealmCache {
 
     protected final Cache<String, Long> revisions;
     protected final Cache<String, Object> cache;
-    final AtomicLong realmCounter = new AtomicLong();
-    final AtomicLong clientCounter = new AtomicLong();
-    final AtomicLong clientTemplateCounter = new AtomicLong();
-    final AtomicLong roleCounter = new AtomicLong();
-    final AtomicLong groupCounter = new AtomicLong();
 
-    protected final ConcurrentHashMap<String, String> realmLookup;
+    protected final ConcurrentHashMap<String, String> realmLookup = new ConcurrentHashMap<>();
+    protected final ConcurrentHashMap<String, String> clientLookup = new ConcurrentHashMap<>();
 
-    public LockingRealmCache(Cache<String, Object> cache, Cache<String, Long> revisions, ConcurrentHashMap<String, String> realmLookup) {
+    public LockingRealmCache(Cache<String, Object> cache, Cache<String, Long> revisions) {
         this.cache = cache;
-        this.realmLookup = realmLookup;
         this.revisions = revisions;
     }
 
@@ -61,8 +55,16 @@ public class LockingRealmCache implements RealmCache {
         return revisions;
     }
 
-    public void startRevisionBatch() {
-        revisions.startBatch();
+    public ConcurrentHashMap<String, String> getRealmLookup() {
+        return realmLookup;
+    }
+
+    public ConcurrentHashMap<String, String> getClientLookup() {
+        return clientLookup;
+    }
+
+    public Long getCurrentRevision(String id) {
+        return revisions.get(id);
     }
 
     public void endRevisionBatch() {
@@ -91,33 +93,39 @@ public class LockingRealmCache implements RealmCache {
         return o != null && type.isInstance(o) ? type.cast(o) : null;
     }
 
-    protected Object invalidateObject(String id, AtomicLong counter) {
+    protected Object invalidateObject(String id) {
         Object removed = cache.remove(id);
-        revisions.put(id, counter.incrementAndGet());
+        revisions.put(id, UpdateCounter.next());
         return removed;
     }
 
-    protected void addRevisioned(String id, Revisioned object, AtomicLong counter) {
+    protected void addRevisioned(String id, Revisioned object) {
         //startRevisionBatch();
         try {
             //revisions.getAdvancedCache().lock(id);
             Long rev = revisions.get(id);
             if (rev == null) {
-                rev = counter.incrementAndGet();
+                rev = UpdateCounter.current();
                 revisions.put(id, rev);
-                return;
             }
             revisions.startBatch();
-            revisions.getAdvancedCache().lock(id);
+            if (!revisions.getAdvancedCache().lock(id)) {
+                logger.trace("Could not obtain version lock");
+            }
             rev = revisions.get(id);
             if (rev == null) {
-                rev = counter.incrementAndGet();
-                revisions.put(id, rev);
                 return;
             }
             if (rev.equals(object.getRevision())) {
                 cache.putForExternalRead(id, object);
+                return;
+            }
+            if (rev > object.getRevision()) { // revision is ahead, don't cache
+                return;
             }
+            // revisions cache has a lower value than the object.revision, so update revision and add it to cache
+            revisions.put(id, object.getRevision());
+            cache.putForExternalRead(id, object);
         } finally {
             endRevisionBatch();
         }
@@ -127,71 +135,82 @@ public class LockingRealmCache implements RealmCache {
 
 
 
-    public Long getCurrentRevision(String id) {
-        return revisions.get(id);
-    }
     @Override
     public void clear() {
         cache.clear();
     }
 
     @Override
-    public CachedRealm getCachedRealm(String id) {
+    public CachedRealm getRealm(String id) {
         return get(id, CachedRealm.class);
     }
 
     @Override
-    public void invalidateCachedRealm(CachedRealm realm) {
+    public void invalidateRealm(CachedRealm realm) {
         logger.tracev("Invalidating realm {0}", realm.getId());
-        invalidateObject(realm.getId(), realmCounter);
+        invalidateObject(realm.getId());
         realmLookup.remove(realm.getName());
     }
 
     @Override
-    public void invalidateCachedRealmById(String id) {
-        CachedRealm cached = (CachedRealm) invalidateObject(id, realmCounter);
+    public void invalidateRealmById(String id) {
+        CachedRealm cached = (CachedRealm) invalidateObject(id);
         if (cached != null) realmLookup.remove(cached.getName());
     }
 
     @Override
-    public void addCachedRealm(CachedRealm realm) {
+    public void addRealm(CachedRealm realm) {
         logger.tracev("Adding realm {0}", realm.getId());
-        addRevisioned(realm.getId(), (Revisioned) realm, realmCounter);
+        addRevisioned(realm.getId(), (Revisioned) realm);
         realmLookup.put(realm.getName(), realm.getId());
     }
 
 
     @Override
-    public CachedRealm getCachedRealmByName(String name) {
+    public CachedRealm getRealmByName(String name) {
         String id = realmLookup.get(name);
-        return id != null ? getCachedRealm(id) : null;
+        return id != null ? getRealm(id) : null;
     }
 
     @Override
-    public CachedClient getApplication(String id) {
+    public CachedClient getClient(String id) {
         return get(id, CachedClient.class);
     }
 
+    public CachedClient getClientByClientId(RealmModel realm, String clientId) {
+        String id = clientLookup.get(realm.getId() + "." + clientId);
+        return id != null ? getClient(id) : null;
+    }
+
     @Override
-    public void invalidateApplication(CachedClient app) {
+    public void invalidateClient(CachedClient app) {
         logger.tracev("Removing application {0}", app.getId());
-        invalidateObject(app.getId(), clientCounter);
+        invalidateObject(app.getId());
+        clientLookup.remove(getClientIdKey(app));
     }
 
     @Override
-    public void addCachedClient(CachedClient app) {
+    public void addClient(CachedClient app) {
         logger.tracev("Adding application {0}", app.getId());
-        addRevisioned(app.getId(), (Revisioned) app, clientCounter);
+        addRevisioned(app.getId(), (Revisioned) app);
+        clientLookup.put(getClientIdKey(app), app.getId());
     }
 
     @Override
-    public void invalidateCachedApplicationById(String id) {
-        CachedClient client = (CachedClient)invalidateObject(id, clientCounter);
-        if (client != null) logger.tracev("Removing application {0}", client.getClientId());
+    public void invalidateClientById(String id) {
+        CachedClient client = (CachedClient)invalidateObject(id);
+        if (client != null) {
+            logger.tracev("Removing application {0}", client.getClientId());
+            clientLookup.remove(getClientIdKey(client));
+        }
+    }
+
+    protected String getClientIdKey(CachedClient client) {
+        return client.getRealm() + "." + client.getClientId();
     }
 
     @Override
-    public void evictCachedApplicationById(String id) {
+    public void evictClientById(String id) {
         logger.tracev("Evicting application {0}", id);
         cache.evict(id);
     }
@@ -204,26 +223,19 @@ public class LockingRealmCache implements RealmCache {
     @Override
     public void invalidateGroup(CachedGroup role) {
         logger.tracev("Removing group {0}", role.getId());
-        invalidateObject(role.getId(), groupCounter);
+        invalidateObject(role.getId());
     }
 
     @Override
-    public void addCachedGroup(CachedGroup role) {
+    public void addGroup(CachedGroup role) {
         logger.tracev("Adding group {0}", role.getId());
-        addRevisioned(role.getId(), (Revisioned) role, groupCounter);
-    }
-
-    @Override
-    public void invalidateCachedGroupById(String id) {
-        logger.tracev("Removing group {0}", id);
-        invalidateObject(id, groupCounter);
-
+        addRevisioned(role.getId(), (Revisioned) role);
     }
 
     @Override
     public void invalidateGroupById(String id) {
         logger.tracev("Removing group {0}", id);
-        invalidateObject(id, groupCounter);
+        invalidateObject(id);
     }
 
     @Override
@@ -234,31 +246,25 @@ public class LockingRealmCache implements RealmCache {
     @Override
     public void invalidateRole(CachedRole role) {
         logger.tracev("Removing role {0}", role.getId());
-        invalidateObject(role.getId(), roleCounter);
+        invalidateObject(role.getId());
     }
 
     @Override
     public void invalidateRoleById(String id) {
         logger.tracev("Removing role {0}", id);
-        invalidateObject(id, roleCounter);
+        invalidateObject(id);
     }
 
     @Override
-    public void evictCachedRoleById(String id) {
+    public void evictRoleById(String id) {
         logger.tracev("Evicting role {0}", id);
         cache.evict(id);
     }
 
     @Override
-    public void addCachedRole(CachedRole role) {
+    public void addRole(CachedRole role) {
         logger.tracev("Adding role {0}", role.getId());
-        addRevisioned(role.getId(), (Revisioned) role, roleCounter);
-    }
-
-    @Override
-    public void invalidateCachedRoleById(String id) {
-        logger.tracev("Removing role {0}", id);
-        invalidateObject(id, roleCounter);
+        addRevisioned(role.getId(), (Revisioned) role);
     }
 
     @Override
@@ -269,23 +275,23 @@ public class LockingRealmCache implements RealmCache {
     @Override
     public void invalidateClientTemplate(CachedClientTemplate app) {
         logger.tracev("Removing client template {0}", app.getId());
-        invalidateObject(app.getId(), clientTemplateCounter);
+        invalidateObject(app.getId());
     }
 
     @Override
-    public void addCachedClientTemplate(CachedClientTemplate app) {
+    public void addClientTemplate(CachedClientTemplate app) {
         logger.tracev("Adding client template {0}", app.getId());
-        addRevisioned(app.getId(), (Revisioned) app, clientTemplateCounter);
+        addRevisioned(app.getId(), (Revisioned) app);
     }
 
     @Override
-    public void invalidateCachedClientTemplateById(String id) {
+    public void invalidateClientTemplateById(String id) {
         logger.tracev("Removing client template {0}", id);
-        invalidateObject(id, clientTemplateCounter);
+        invalidateObject(id);
     }
 
     @Override
-    public void evictCachedClientTemplateById(String id) {
+    public void evictClientTemplateById(String id) {
         logger.tracev("Evicting client template {0}", id);
         cache.evict(id);
     }
diff --git a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/RealmAdapter.java b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/RealmAdapter.java
index 41dc8d8..14e2850 100755
--- a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/RealmAdapter.java
+++ b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/RealmAdapter.java
@@ -415,6 +415,9 @@ public class RealmAdapter implements RealmModel {
 
     @Override
     public PublicKey getPublicKey() {
+        if (updated != null) return updated.getPublicKey();
+        if (publicKey != null) return publicKey;
+        publicKey = cached.getPublicKey();
         if (publicKey != null) return publicKey;
         publicKey = KeycloakModelUtils.getPublicKey(getPublicKeyPem());
         return publicKey;
@@ -429,6 +432,9 @@ public class RealmAdapter implements RealmModel {
 
     @Override
     public X509Certificate getCertificate() {
+        if (updated != null) return updated.getCertificate();
+        if (certificate != null) return certificate;
+        certificate = cached.getCertificate();
         if (certificate != null) return certificate;
         certificate = KeycloakModelUtils.getCertificate(getCertificatePem());
         return certificate;
@@ -456,7 +462,14 @@ public class RealmAdapter implements RealmModel {
 
     @Override
     public PrivateKey getPrivateKey() {
-        if (privateKey != null) return privateKey;
+        if (updated != null) return updated.getPrivateKey();
+        if (privateKey != null) {
+            return privateKey;
+        }
+        privateKey = cached.getPrivateKey();
+        if (privateKey != null) {
+            return privateKey;
+        }
         privateKey = KeycloakModelUtils.getPrivateKey(getPrivateKeyPem());
         return privateKey;
     }
@@ -635,10 +648,7 @@ public class RealmAdapter implements RealmModel {
 
     @Override
     public ClientModel getClientByClientId(String clientId) {
-        if (updated != null) return updated.getClientByClientId(clientId);
-        String id = cached.getClients().get(clientId);
-        if (id == null) return null;
-        return getClientById(id);
+        return cacheSession.getClientByClientId(clientId, this);
     }
 
     @Override
diff --git a/model/infinispan/src/main/resources/META-INF/services/org.keycloak.connections.infinispan.InfinispanConnectionProviderFactory b/model/infinispan/src/main/resources/META-INF/services/org.keycloak.connections.infinispan.InfinispanConnectionProviderFactory
index ef880ec..299da00 100755
--- a/model/infinispan/src/main/resources/META-INF/services/org.keycloak.connections.infinispan.InfinispanConnectionProviderFactory
+++ b/model/infinispan/src/main/resources/META-INF/services/org.keycloak.connections.infinispan.InfinispanConnectionProviderFactory
@@ -16,5 +16,4 @@
 #
 
 org.keycloak.connections.infinispan.DefaultInfinispanConnectionProviderFactory
-org.keycloak.models.cache.infinispan.counter.RevisionedConnectionProviderFactory
 org.keycloak.models.cache.infinispan.locking.LockingConnectionProviderFactory
\ No newline at end of file
diff --git a/model/infinispan/src/main/resources/META-INF/services/org.keycloak.models.cache.CacheRealmProviderFactory b/model/infinispan/src/main/resources/META-INF/services/org.keycloak.models.cache.CacheRealmProviderFactory
index 6554765..40ced18 100755
--- a/model/infinispan/src/main/resources/META-INF/services/org.keycloak.models.cache.CacheRealmProviderFactory
+++ b/model/infinispan/src/main/resources/META-INF/services/org.keycloak.models.cache.CacheRealmProviderFactory
@@ -16,5 +16,4 @@
 #
 
 org.keycloak.models.cache.infinispan.InfinispanCacheRealmProviderFactory
-org.keycloak.models.cache.infinispan.counter.RevisionedCacheRealmProviderFactory
 org.keycloak.models.cache.infinispan.locking.LockingCacheRealmProviderFactory
\ No newline at end of file
diff --git a/model/jpa/src/main/java/org/keycloak/models/jpa/entities/ClientEntity.java b/model/jpa/src/main/java/org/keycloak/models/jpa/entities/ClientEntity.java
index 15544c6..5466a77 100755
--- a/model/jpa/src/main/java/org/keycloak/models/jpa/entities/ClientEntity.java
+++ b/model/jpa/src/main/java/org/keycloak/models/jpa/entities/ClientEntity.java
@@ -50,6 +50,8 @@ import java.util.Set;
 @Table(name="CLIENT", uniqueConstraints = {@UniqueConstraint(columnNames = {"REALM_ID", "CLIENT_ID"})})
 @NamedQueries({
         @NamedQuery(name="getClientsByRealm", query="select client from ClientEntity client where client.realm = :realm"),
+        @NamedQuery(name="findClientIdByClientId", query="select client.id from ClientEntity client where client.clientId = :clientId and client.realm.id = :realm"),
+        @NamedQuery(name="findClientByClientId", query="select client from ClientEntity client where client.clientId = :clientId and client.realm.id = :realm"),
 })
 public class ClientEntity {
 
diff --git a/model/jpa/src/main/java/org/keycloak/models/jpa/entities/RealmEntity.java b/model/jpa/src/main/java/org/keycloak/models/jpa/entities/RealmEntity.java
index 76aa238..684d102 100755
--- a/model/jpa/src/main/java/org/keycloak/models/jpa/entities/RealmEntity.java
+++ b/model/jpa/src/main/java/org/keycloak/models/jpa/entities/RealmEntity.java
@@ -191,7 +191,7 @@ public class RealmEntity {
     @Column(name="ADMIN_EVENTS_DETAILS_ENABLED")
     protected boolean adminEventsDetailsEnabled;
     
-    @OneToOne
+    @OneToOne(fetch = FetchType.LAZY)
     @JoinColumn(name="MASTER_ADMIN_CLIENT")
     protected ClientEntity masterAdminClient;
 
diff --git a/model/jpa/src/main/java/org/keycloak/models/jpa/JpaRealmProvider.java b/model/jpa/src/main/java/org/keycloak/models/jpa/JpaRealmProvider.java
index 86bc5a2..ce245de 100755
--- a/model/jpa/src/main/java/org/keycloak/models/jpa/JpaRealmProvider.java
+++ b/model/jpa/src/main/java/org/keycloak/models/jpa/JpaRealmProvider.java
@@ -17,6 +17,7 @@
 
 package org.keycloak.models.jpa;
 
+import org.jboss.logging.Logger;
 import org.keycloak.migration.MigrationModel;
 import org.keycloak.models.ClientModel;
 import org.keycloak.models.ClientTemplateModel;
@@ -43,6 +44,7 @@ import java.util.List;
  * @version $Revision: 1 $
  */
 public class JpaRealmProvider implements RealmProvider {
+    protected static final Logger logger = Logger.getLogger(JpaRealmProvider.class);
     private final KeycloakSession session;
     protected EntityManager em;
 
@@ -115,7 +117,6 @@ public class JpaRealmProvider implements RealmProvider {
         if (realm == null) {
             return false;
         }
-
         RealmAdapter adapter = new RealmAdapter(session, em, realm);
         session.users().preRemove(adapter);
         int num = em.createNamedQuery("deleteGroupRoleMappingsByRealm")
@@ -144,6 +145,11 @@ public class JpaRealmProvider implements RealmProvider {
 
         em.flush();
         em.clear();
+        realm = em.find(RealmEntity.class, id);
+        if (realm != null) {
+            logger.error("WTF is the realm still there after a removal????????");
+        }
+
         return true;
     }
 
@@ -177,6 +183,42 @@ public class JpaRealmProvider implements RealmProvider {
     }
 
     @Override
+    public ClientModel getClientByClientId(String clientId, RealmModel realm) {
+        TypedQuery<ClientEntity> query = em.createNamedQuery("findClientByClientId", ClientEntity.class);
+        query.setParameter("clientId", clientId);
+        query.setParameter("realm", realm.getId());
+        List<ClientEntity> results = query.getResultList();
+        if (results.isEmpty()) return null;
+        ClientEntity entity = results.get(0);
+        return new ClientAdapter(realm, em, session, entity);
+    }
+
+    @Override
+    public boolean removeClient(String id, RealmModel realm) {
+        ClientModel client = getClientById(id, realm);
+        if (client == null) return false;
+
+        session.users().preRemove(realm, client);
+
+        for (RoleModel role : client.getRoles()) {
+            client.removeRole(role);
+        }
+
+
+        ClientEntity clientEntity = ((ClientAdapter)client).getEntity();
+        em.createNamedQuery("deleteScopeMappingByClient").setParameter("client", clientEntity).executeUpdate();
+        em.flush();
+        em.remove(clientEntity);  // i have no idea why, but this needs to come before deleteScopeMapping
+        try {
+            em.flush();
+        } catch (RuntimeException e) {
+            logger.errorv("Unable to delete client entity: {0} from realm {1}", client.getClientId(), realm.getName());
+            throw e;
+        }
+        return true;
+    }
+
+    @Override
     public ClientTemplateModel getClientTemplateById(String id, RealmModel realm) {
         ClientTemplateEntity app = em.find(ClientTemplateEntity.class, id);
 
diff --git a/model/jpa/src/main/java/org/keycloak/models/jpa/RealmAdapter.java b/model/jpa/src/main/java/org/keycloak/models/jpa/RealmAdapter.java
index 4d632ec..da66a3b 100755
--- a/model/jpa/src/main/java/org/keycloak/models/jpa/RealmAdapter.java
+++ b/model/jpa/src/main/java/org/keycloak/models/jpa/RealmAdapter.java
@@ -17,6 +17,7 @@
 
 package org.keycloak.models.jpa;
 
+import org.jboss.logging.Logger;
 import org.keycloak.connections.jpa.util.JpaUtils;
 import org.keycloak.common.enums.SslRequired;
 import org.keycloak.models.AuthenticationExecutionModel;
@@ -65,6 +66,7 @@ import java.util.Set;
  * @version $Revision: 1 $
  */
 public class RealmAdapter implements RealmModel {
+    protected static final Logger logger = Logger.getLogger(RealmAdapter.class);
     protected RealmEntity realm;
     protected EntityManager em;
     protected volatile transient PublicKey publicKey;
@@ -774,34 +776,7 @@ public class RealmAdapter implements RealmModel {
         if (id == null) return false;
         ClientModel client = getClientById(id);
         if (client == null) return false;
-
-        session.users().preRemove(this, client);
-
-        for (RoleModel role : client.getRoles()) {
-            client.removeRole(role);
-        }
-
-        ClientEntity clientEntity = null;
-        Iterator<ClientEntity> it = realm.getClients().iterator();
-        while (it.hasNext()) {
-            ClientEntity ae = it.next();
-            if (ae.getId().equals(id)) {
-                clientEntity = ae;
-                it.remove();
-                break;
-            }
-        }
-        for (ClientEntity a : realm.getClients()) {
-            if (a.getId().equals(id)) {
-                clientEntity = a;
-            }
-        }
-        if (clientEntity == null) return false;
-        em.createNamedQuery("deleteScopeMappingByClient").setParameter("client", clientEntity).executeUpdate();
-        em.remove(clientEntity);
-        em.flush();
-
-        return true;
+        return session.realms().removeClient(id, this);
     }
 
     @Override
@@ -811,7 +786,7 @@ public class RealmAdapter implements RealmModel {
 
     @Override
     public ClientModel getClientByClientId(String clientId) {
-        return getClientNameMap().get(clientId);
+        return session.realms().getClientByClientId(clientId, this);
     }
 
     private static final String BROWSER_HEADER_PREFIX = "_browser_header.";
diff --git a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/MongoRealmProvider.java b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/MongoRealmProvider.java
index 323bbbe..c5d70fb 100755
--- a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/MongoRealmProvider.java
+++ b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/MongoRealmProvider.java
@@ -163,6 +163,28 @@ public class MongoRealmProvider implements RealmProvider {
     }
 
     @Override
+    public boolean removeClient(String id, RealmModel realm) {
+        if (id == null) return false;
+        ClientModel client = getClientById(id, realm);
+        if (client == null) return false;
+
+        session.users().preRemove(realm, client);
+
+        return getMongoStore().removeEntity(MongoClientEntity.class, id, invocationContext);
+    }
+
+    @Override
+    public ClientModel getClientByClientId(String clientId, RealmModel realm) {
+        DBObject query = new QueryBuilder()
+                .and("realmId").is(realm.getId())
+                .and("clientId").is(clientId)
+                .get();
+        MongoClientEntity appEntity = getMongoStore().loadSingleEntity(MongoClientEntity.class, query, invocationContext);
+        return appEntity == null ? null : new ClientAdapter(session, realm, appEntity, invocationContext);
+
+    }
+
+    @Override
     public ClientTemplateModel getClientTemplateById(String id, RealmModel realm) {
         MongoClientTemplateEntity appData = getMongoStore().loadEntity(MongoClientTemplateEntity.class, id, invocationContext);
 
diff --git a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/RealmAdapter.java b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/RealmAdapter.java
index bae4589..701cced 100755
--- a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/RealmAdapter.java
+++ b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/RealmAdapter.java
@@ -807,12 +807,7 @@ public class RealmAdapter extends AbstractMongoAdapter<MongoRealmEntity> impleme
 
     @Override
     public ClientModel getClientByClientId(String clientId) {
-        DBObject query = new QueryBuilder()
-                .and("realmId").is(getId())
-                .and("clientId").is(clientId)
-                .get();
-        MongoClientEntity appEntity = getMongoStore().loadSingleEntity(MongoClientEntity.class, query, invocationContext);
-        return appEntity == null ? null : new ClientAdapter(session, this, appEntity, invocationContext);
+        return session.realms().getClientByClientId(clientId, this);
     }
 
     @Override
@@ -873,10 +868,7 @@ public class RealmAdapter extends AbstractMongoAdapter<MongoRealmEntity> impleme
         if (id == null) return false;
         ClientModel client = getClientById(id);
         if (client == null) return false;
-
-        session.users().preRemove(this, client);
-
-        return getMongoStore().removeEntity(MongoClientEntity.class, id, invocationContext);
+        return session.realms().removeClient(id, this);
     }
 
     @Override
diff --git a/server-spi/src/main/java/org/keycloak/models/cache/entities/CachedClient.java b/server-spi/src/main/java/org/keycloak/models/cache/entities/CachedClient.java
index ca3e35b..727cabc 100755
--- a/server-spi/src/main/java/org/keycloak/models/cache/entities/CachedClient.java
+++ b/server-spi/src/main/java/org/keycloak/models/cache/entities/CachedClient.java
@@ -127,7 +127,7 @@ public class CachedClient implements Serializable {
     protected void cacheRoles(RealmCache cache, RealmModel realm, ClientModel model) {
         for (RoleModel role : model.getRoles()) {
             roles.put(role.getName(), role.getId());
-            cache.addCachedRole(new CachedClientRole(id, role, realm));
+            cache.addRole(new CachedClientRole(id, role, realm));
         }
     }
 
diff --git a/server-spi/src/main/java/org/keycloak/models/cache/entities/CachedRealm.java b/server-spi/src/main/java/org/keycloak/models/cache/entities/CachedRealm.java
index 42ed3bc..29b9331 100755
--- a/server-spi/src/main/java/org/keycloak/models/cache/entities/CachedRealm.java
+++ b/server-spi/src/main/java/org/keycloak/models/cache/entities/CachedRealm.java
@@ -39,6 +39,10 @@ import org.keycloak.models.cache.RealmCache;
 import org.keycloak.common.util.MultivaluedHashMap;
 
 import java.io.Serializable;
+import java.security.PrivateKey;
+import java.security.PublicKey;
+import java.security.cert.Certificate;
+import java.security.cert.X509Certificate;
 import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.HashSet;
@@ -89,8 +93,11 @@ public class CachedRealm implements Serializable {
     protected PasswordPolicy passwordPolicy;
     protected OTPPolicy otpPolicy;
 
+    protected transient PublicKey publicKey;
     protected String publicKeyPem;
+    protected transient PrivateKey privateKey;
     protected String privateKeyPem;
+    protected transient X509Certificate certificate;
     protected String certificatePem;
     protected String codeSecret;
 
@@ -179,8 +186,11 @@ public class CachedRealm implements Serializable {
         otpPolicy = model.getOTPPolicy();
 
         publicKeyPem = model.getPublicKeyPem();
+        publicKey = model.getPublicKey();
         privateKeyPem = model.getPrivateKeyPem();
+        privateKey = model.getPrivateKey();
         certificatePem = model.getCertificatePem();
+        certificate = model.getCertificate();
         codeSecret = model.getCodeSecret();
 
         loginTheme = model.getLoginTheme();
@@ -265,7 +275,7 @@ public class CachedRealm implements Serializable {
         for (ClientTemplateModel template : model.getClientTemplates()) {
             clientTemplates.add(template.getId());
             CachedClientTemplate cachedClient = new CachedClientTemplate(cache, delegate, model, template);
-            cache.addCachedClientTemplate(cachedClient);
+            cache.addClientTemplate(cachedClient);
         }
     }
 
@@ -273,7 +283,7 @@ public class CachedRealm implements Serializable {
         for (ClientModel client : model.getClients()) {
             clients.put(client.getClientId(), client.getId());
             CachedClient cachedClient = new CachedClient(cache, delegate, model, client);
-            cache.addCachedClient(cachedClient);
+            cache.addClient(cachedClient);
         }
     }
 
@@ -281,7 +291,7 @@ public class CachedRealm implements Serializable {
         for (RoleModel role : model.getRoles()) {
             realmRoles.put(role.getName(), role.getId());
             CachedRole cachedRole = new CachedRealmRole(role, model);
-            cache.addCachedRole(cachedRole);
+            cache.addRole(cachedRole);
         }
     }
 
@@ -584,4 +594,16 @@ public class CachedRealm implements Serializable {
     public List<String> getClientTemplates() {
         return clientTemplates;
     }
+
+    public PublicKey getPublicKey() {
+        return publicKey;
+    }
+
+    public PrivateKey getPrivateKey() {
+        return privateKey;
+    }
+
+    public X509Certificate getCertificate() {
+        return certificate;
+    }
 }
diff --git a/server-spi/src/main/java/org/keycloak/models/cache/RealmCache.java b/server-spi/src/main/java/org/keycloak/models/cache/RealmCache.java
index 909223b..2b7b339 100755
--- a/server-spi/src/main/java/org/keycloak/models/cache/RealmCache.java
+++ b/server-spi/src/main/java/org/keycloak/models/cache/RealmCache.java
@@ -30,35 +30,33 @@ import org.keycloak.models.cache.entities.CachedRole;
 public interface RealmCache {
     void clear();
 
-    CachedRealm getCachedRealm(String id);
+    CachedRealm getRealm(String id);
 
-    void invalidateCachedRealm(CachedRealm realm);
+    void invalidateRealm(CachedRealm realm);
 
-    void addCachedRealm(CachedRealm realm);
+    void addRealm(CachedRealm realm);
 
-    CachedRealm getCachedRealmByName(String name);
+    CachedRealm getRealmByName(String name);
 
-    void invalidateCachedRealmById(String id);
+    void invalidateRealmById(String id);
 
-    CachedClient getApplication(String id);
+    CachedClient getClient(String id);
 
-    void invalidateApplication(CachedClient app);
+    void invalidateClient(CachedClient app);
 
-    void evictCachedApplicationById(String id);
+    void evictClientById(String id);
 
-    void addCachedClient(CachedClient app);
+    void addClient(CachedClient app);
 
-    void invalidateCachedApplicationById(String id);
+    void invalidateClientById(String id);
 
     CachedRole getRole(String id);
 
     void invalidateRole(CachedRole role);
 
-    void evictCachedRoleById(String id);
+    void evictRoleById(String id);
 
-    void addCachedRole(CachedRole role);
-
-    void invalidateCachedRoleById(String id);
+    void addRole(CachedRole role);
 
     void invalidateRoleById(String id);
 
@@ -66,9 +64,7 @@ public interface RealmCache {
 
     void invalidateGroup(CachedGroup role);
 
-    void addCachedGroup(CachedGroup role);
-
-    void invalidateCachedGroupById(String id);
+    void addGroup(CachedGroup role);
 
     void invalidateGroupById(String id);
 
@@ -76,10 +72,10 @@ public interface RealmCache {
 
     void invalidateClientTemplate(CachedClientTemplate app);
 
-    void evictCachedClientTemplateById(String id);
+    void evictClientTemplateById(String id);
 
-    void addCachedClientTemplate(CachedClientTemplate app);
+    void addClientTemplate(CachedClientTemplate app);
 
-    void invalidateCachedClientTemplateById(String id);
+    void invalidateClientTemplateById(String id);
 
 }
diff --git a/server-spi/src/main/java/org/keycloak/models/RealmProvider.java b/server-spi/src/main/java/org/keycloak/models/RealmProvider.java
index ad9e2d1..2b3f5fa 100755
--- a/server-spi/src/main/java/org/keycloak/models/RealmProvider.java
+++ b/server-spi/src/main/java/org/keycloak/models/RealmProvider.java
@@ -35,8 +35,14 @@ public interface RealmProvider extends Provider {
     RealmModel getRealm(String id);
     RealmModel getRealmByName(String name);
 
-    RoleModel getRoleById(String id, RealmModel realm);
     ClientModel getClientById(String id, RealmModel realm);
+    ClientModel getClientByClientId(String clientId, RealmModel realm);
+
+
+    RoleModel getRoleById(String id, RealmModel realm);
+
+    boolean removeClient(String id, RealmModel realm);
+
     ClientTemplateModel getClientTemplateById(String id, RealmModel realm);
     GroupModel getGroupById(String id, RealmModel realm);
 
diff --git a/services/src/main/java/org/keycloak/services/managers/RealmManager.java b/services/src/main/java/org/keycloak/services/managers/RealmManager.java
index dbaaad2..55695a0 100755
--- a/services/src/main/java/org/keycloak/services/managers/RealmManager.java
+++ b/services/src/main/java/org/keycloak/services/managers/RealmManager.java
@@ -211,10 +211,11 @@ public class RealmManager implements RealmImporter {
     public boolean removeRealm(RealmModel realm) {
         List<UserFederationProviderModel> federationProviders = realm.getUserFederationProviders();
 
+        ClientModel masterAdminClient = realm.getMasterAdminClient();
         boolean removed = model.removeRealm(realm.getId());
         if (removed) {
-            if (realm.getMasterAdminClient() != null) {
-                new ClientManager(this).removeClient(getKeycloakAdminstrationRealm(), realm.getMasterAdminClient());
+            if (masterAdminClient != null) {
+                new ClientManager(this).removeClient(getKeycloakAdminstrationRealm(), masterAdminClient);
             }
 
             UserSessionProvider sessions = session.sessions();
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/admin/ConcurrencyTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/admin/ConcurrencyTest.java
index f510f04..7984fae 100755
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/admin/ConcurrencyTest.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/admin/ConcurrencyTest.java
@@ -46,8 +46,8 @@ public class ConcurrencyTest extends AbstractClientTest {
 
     private static final Logger log = Logger.getLogger(ConcurrencyTest.class);
 
-    private static final int DEFAULT_THREADS = 1;
-    private static final int DEFAULT_ITERATIONS = 5;
+    private static final int DEFAULT_THREADS = 5;
+    private static final int DEFAULT_ITERATIONS = 20;
 
     // If enabled only one request is allowed at the time. Useful for checking that test is working.
     private static final boolean SYNCHRONIZED = false;
diff --git a/testsuite/pom.xml b/testsuite/pom.xml
index 6f8079e..bc054ce 100755
--- a/testsuite/pom.xml
+++ b/testsuite/pom.xml
@@ -57,6 +57,7 @@
         <module>tomcat8</module>
         <module>jetty</module>
         <module>performance</module>
+        <module>stress</module>
         <module>integration-arquillian</module>
     </modules>
 
diff --git a/testsuite/stress/pom.xml b/testsuite/stress/pom.xml
new file mode 100755
index 0000000..f7ea824
--- /dev/null
+++ b/testsuite/stress/pom.xml
@@ -0,0 +1,571 @@
+<?xml version="1.0"?>
+<!--
+  ~ 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.
+  -->
+
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+    <parent>
+        <artifactId>keycloak-testsuite-pom</artifactId>
+        <groupId>org.keycloak</groupId>
+        <version>1.9.0.Final-SNAPSHOT</version>
+        <relativePath>../pom.xml</relativePath>
+    </parent>
+    <modelVersion>4.0.0</modelVersion>
+
+    <artifactId>keycloak-stress-tester</artifactId>
+    <name>Keycloak Stress TestSuite</name>
+    <description />
+
+    <dependencies>
+        <dependency>
+            <groupId>org.bouncycastle</groupId>
+            <artifactId>bcprov-jdk15on</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.bouncycastle</groupId>
+            <artifactId>bcpkix-jdk15on</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.keycloak</groupId>
+            <artifactId>keycloak-dependencies-server-all</artifactId>
+            <type>pom</type>
+        </dependency>
+        <dependency>
+            <groupId>org.keycloak</groupId>
+            <artifactId>keycloak-admin-client</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.keycloak</groupId>
+            <artifactId>keycloak-wildfly-adduser</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>log4j</groupId>
+            <artifactId>log4j</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.jboss.spec.javax.servlet</groupId>
+            <artifactId>jboss-servlet-api_3.0_spec</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.jboss.spec.javax.ws.rs</groupId>
+            <artifactId>jboss-jaxrs-api_2.0_spec</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.jboss.resteasy</groupId>
+            <artifactId>async-http-servlet-3.0</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.jboss.resteasy</groupId>
+            <artifactId>resteasy-jaxrs</artifactId>
+            <exclusions>
+                <exclusion>
+                    <groupId>log4j</groupId>
+                    <artifactId>log4j</artifactId>
+                </exclusion>
+                <exclusion>
+                    <groupId>org.slf4j</groupId>
+                    <artifactId>slf4j-api</artifactId>
+                </exclusion>
+                <exclusion>
+                    <groupId>org.slf4j</groupId>
+                    <artifactId>slf4j-simple</artifactId>
+                </exclusion>
+            </exclusions>
+        </dependency>
+        <dependency>
+            <groupId>org.jboss.resteasy</groupId>
+            <artifactId>resteasy-client</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.jboss.resteasy</groupId>
+            <artifactId>resteasy-undertow</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.jboss.resteasy</groupId>
+            <artifactId>resteasy-multipart-provider</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.jboss.resteasy</groupId>
+            <artifactId>resteasy-jackson2-provider</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>com.google.zxing</groupId>
+            <artifactId>javase</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.httpcomponents</groupId>
+            <artifactId>httpclient</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.keycloak</groupId>
+            <artifactId>keycloak-server-spi</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.keycloak</groupId>
+            <artifactId>keycloak-ldap-federation</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.keycloak</groupId>
+            <artifactId>keycloak-kerberos-federation</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.keycloak</groupId>
+            <artifactId>keycloak-undertow-adapter</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.keycloak</groupId>
+            <artifactId>keycloak-saml-adapter-core</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.keycloak</groupId>
+            <artifactId>keycloak-saml-servlet-filter-adapter</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.keycloak</groupId>
+            <artifactId>keycloak-servlet-filter-adapter</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.keycloak</groupId>
+            <artifactId>keycloak-saml-undertow-adapter</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.keycloak</groupId>
+            <artifactId>keycloak-jaxrs-oauth-client</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.keycloak</groupId>
+            <artifactId>federation-properties-example</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.jboss.logging</groupId>
+            <artifactId>jboss-logging</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>io.undertow</groupId>
+            <artifactId>undertow-servlet</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>io.undertow</groupId>
+            <artifactId>undertow-core</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>com.fasterxml.jackson.core</groupId>
+            <artifactId>jackson-core</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>com.fasterxml.jackson.core</groupId>
+            <artifactId>jackson-databind</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>com.fasterxml.jackson.core</groupId>
+            <artifactId>jackson-annotations</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>junit</groupId>
+            <artifactId>junit</artifactId>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.hamcrest</groupId>
+            <artifactId>hamcrest-all</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.hibernate.javax.persistence</groupId>
+            <artifactId>hibernate-jpa-2.1-api</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>com.h2database</groupId>
+            <artifactId>h2</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.hibernate</groupId>
+            <artifactId>hibernate-entitymanager</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>com.icegreen</groupId>
+            <artifactId>greenmail</artifactId>
+            <exclusions>
+                <exclusion>
+                    <groupId>org.slf4j</groupId>
+                    <artifactId>slf4j-api</artifactId>
+                </exclusion>
+            </exclusions>
+        </dependency>
+        <dependency>
+            <groupId>org.infinispan</groupId>
+            <artifactId>infinispan-core</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.seleniumhq.selenium</groupId>
+            <artifactId>selenium-java</artifactId>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
+            <groupId>xml-apis</groupId>
+            <artifactId>xml-apis</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.seleniumhq.selenium</groupId>
+            <artifactId>selenium-chrome-driver</artifactId>
+            <scope>provided</scope>
+        </dependency>
+
+        <!-- Apache DS -->
+        <dependency>
+            <groupId>org.keycloak</groupId>
+            <artifactId>keycloak-util-embedded-ldap</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>org.wildfly</groupId>
+            <artifactId>wildfly-undertow</artifactId>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.keycloak</groupId>
+            <artifactId>keycloak-testsuite-integration</artifactId>
+            <type>test-jar</type>
+        </dependency>
+
+
+    </dependencies>
+    <build>
+        <plugins>
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-jar-plugin</artifactId>
+                <version>2.2</version>
+                <executions>
+                    <execution>
+                        <goals>
+                            <goal>test-jar</goal>
+                        </goals>
+                    </execution>
+                </executions>
+            </plugin>
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-deploy-plugin</artifactId>
+                <configuration>
+                    <skip>true</skip>
+                </configuration>
+            </plugin>
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-compiler-plugin</artifactId>
+                <configuration>
+                    <source>${maven.compiler.source}</source>
+                    <target>${maven.compiler.target}</target>
+                </configuration>
+            </plugin>
+            <plugin>
+                <groupId>org.codehaus.mojo</groupId>
+                <artifactId>exec-maven-plugin</artifactId>
+                <configuration>
+                    <workingDirectory>${project.basedir}</workingDirectory>
+                </configuration>
+            </plugin>
+            <plugin>
+                <groupId>org.apache.felix</groupId>
+                <artifactId>maven-bundle-plugin</artifactId>
+                <inherited>true</inherited>
+                <extensions>true</extensions>
+            </plugin>
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-javadoc-plugin</artifactId>
+                <configuration>
+                    <skip>true</skip>
+                </configuration>
+            </plugin>
+        </plugins>
+    </build>
+
+    <profiles>
+        <profile>
+            <id>keycloak-server</id>
+            <build>
+                <plugins>
+                    <plugin>
+                        <groupId>org.codehaus.mojo</groupId>
+                        <artifactId>exec-maven-plugin</artifactId>
+                        <configuration>
+                            <mainClass>org.keycloak.testsuite.KeycloakServer</mainClass>
+                            <classpathScope>test</classpathScope>
+                        </configuration>
+                    </plugin>
+                </plugins>
+            </build>
+        </profile>
+        <profile>
+            <id>mail-server</id>
+            <build>
+                <plugins>
+                    <plugin>
+                        <groupId>org.codehaus.mojo</groupId>
+                        <artifactId>exec-maven-plugin</artifactId>
+                        <configuration>
+                            <mainClass>org.keycloak.testsuite.MailServer</mainClass>
+                            <classpathScope>test</classpathScope>
+                        </configuration>
+                    </plugin>
+                </plugins>
+            </build>
+        </profile>
+        <profile>
+            <id>totp</id>
+            <build>
+                <plugins>
+                    <plugin>
+                        <groupId>org.codehaus.mojo</groupId>
+                        <artifactId>exec-maven-plugin</artifactId>
+                        <configuration>
+                            <mainClass>org.keycloak.testsuite.TotpGenerator</mainClass>
+                            <classpathScope>test</classpathScope>
+                        </configuration>
+                    </plugin>
+                </plugins>
+            </build>
+        </profile>
+        <profile>
+            <id>ldap</id>
+            <build>
+                <plugins>
+                    <plugin>
+                        <groupId>org.codehaus.mojo</groupId>
+                        <artifactId>exec-maven-plugin</artifactId>
+                        <configuration>
+                            <mainClass>org.keycloak.util.ldap.LDAPEmbeddedServer</mainClass>
+                            <classpathScope>test</classpathScope>
+                        </configuration>
+                    </plugin>
+                </plugins>
+            </build>
+        </profile>
+        <profile>
+            <id>kerberos</id>
+            <build>
+                <plugins>
+                    <plugin>
+                        <groupId>org.codehaus.mojo</groupId>
+                        <artifactId>exec-maven-plugin</artifactId>
+                        <configuration>
+                            <mainClass>org.keycloak.util.ldap.KerberosEmbeddedServer</mainClass>
+                            <classpathScope>test</classpathScope>
+                        </configuration>
+                    </plugin>
+                </plugins>
+            </build>
+        </profile>
+
+        <profile>
+            <id>jpa</id>
+
+            <build>
+                <plugins>
+                    <plugin>
+                        <groupId>org.apache.maven.plugins</groupId>
+                        <artifactId>maven-surefire-plugin</artifactId>
+                        <configuration>
+                            <systemPropertyVariables>
+                                <keycloak.realm.provider>jpa</keycloak.realm.provider>
+                                <keycloak.user.provider>jpa</keycloak.user.provider>
+                                <keycloak.userSessionPersister.provider>jpa</keycloak.userSessionPersister.provider>
+                                <keycloak.eventsStore.provider>jpa</keycloak.eventsStore.provider>
+
+                                <keycloak.liquibase.logging.level>debug</keycloak.liquibase.logging.level>
+                            </systemPropertyVariables>
+                        </configuration>
+                    </plugin>
+                </plugins>
+            </build>
+        </profile>
+
+        <profile>
+            <id>mongo</id>
+
+            <properties>
+                <keycloak.connectionsMongo.host>localhost</keycloak.connectionsMongo.host>
+                <keycloak.connectionsMongo.port>27018</keycloak.connectionsMongo.port>
+                <keycloak.connectionsMongo.db>keycloak</keycloak.connectionsMongo.db>
+                <keycloak.connectionsMongo.bindIp>127.0.0.1</keycloak.connectionsMongo.bindIp>
+            </properties>
+
+            <build>
+                <plugins>
+
+                    <!-- Postpone tests to "integration-test" phase, so that we can bootstrap embedded mongo on 27018 before running tests -->
+                    <plugin>
+                        <groupId>org.apache.maven.plugins</groupId>
+                        <artifactId>maven-surefire-plugin</artifactId>
+                        <executions>
+                            <execution>
+                                <id>test</id>
+                                <phase>integration-test</phase>
+                                <goals>
+                                    <goal>test</goal>
+                                </goals>
+                                <configuration>
+                                    <systemPropertyVariables>
+                                        <keycloak.realm.provider>mongo</keycloak.realm.provider>
+                                        <keycloak.user.provider>mongo</keycloak.user.provider>
+                                        <keycloak.userSessionPersister.provider>mongo</keycloak.userSessionPersister.provider>
+                                        <keycloak.eventsStore.provider>mongo</keycloak.eventsStore.provider>
+                                        <keycloak.connectionsMongo.host>${keycloak.connectionsMongo.host}</keycloak.connectionsMongo.host>
+                                        <keycloak.connectionsMongo.port>${keycloak.connectionsMongo.port}</keycloak.connectionsMongo.port>
+                                        <keycloak.connectionsMongo.db>${keycloak.connectionsMongo.db}</keycloak.connectionsMongo.db>
+                                        <keycloak.connectionsMongo.bindIp>${keycloak.connectionsMongo.bindIp}</keycloak.connectionsMongo.bindIp>
+                                    </systemPropertyVariables>
+                                </configuration>
+                            </execution>
+                            <execution>
+                                <id>default-test</id>
+                                <configuration>
+                                    <skip>true</skip>
+                                </configuration>
+                            </execution>
+                        </executions>
+                    </plugin>
+
+                    <!-- Embedded mongo -->
+                    <plugin>
+                        <groupId>com.github.joelittlejohn.embedmongo</groupId>
+                        <artifactId>embedmongo-maven-plugin</artifactId>
+                        <executions>
+                            <execution>
+                                <id>start-mongodb</id>
+                                <phase>pre-integration-test</phase>
+                                <goals>
+                                    <goal>start</goal>
+                                </goals>
+                                <configuration>
+                                    <port>${keycloak.connectionsMongo.port}</port>
+                                    <logging>file</logging>
+                                    <logFile>${project.build.directory}/mongodb.log</logFile>
+                                    <bindIp>${keycloak.connectionsMongo.bindIp}</bindIp>
+                                </configuration>
+                            </execution>
+                            <execution>
+                                <id>stop-mongodb</id>
+                                <phase>post-integration-test</phase>
+                                <goals>
+                                    <goal>stop</goal>
+                                </goals>
+                            </execution>
+                        </executions>
+                    </plugin>
+                </plugins>
+            </build>
+
+        </profile>
+
+        <!-- MySQL -->
+        <profile>
+            <activation>
+                <property>
+                    <name>keycloak.connectionsJpa.driver</name>
+                    <value>com.mysql.jdbc.Driver</value>
+                </property>
+            </activation>
+            <id>mysql</id>
+            <dependencies>
+                <dependency>
+                    <groupId>mysql</groupId>
+                    <artifactId>mysql-connector-java</artifactId>
+                </dependency>
+            </dependencies>
+        </profile>
+
+        <!-- PostgreSQL -->
+        <profile>
+            <activation>
+                <property>
+                    <name>keycloak.connectionsJpa.driver</name>
+                    <value>org.postgresql.Driver</value>
+                </property>
+            </activation>
+            <id>postgresql</id>
+            <dependencies>
+                <dependency>
+                    <groupId>org.postgresql</groupId>
+                    <artifactId>postgresql</artifactId>
+                    <version>${postgresql.version}</version>
+                </dependency>
+            </dependencies>
+        </profile>
+
+        <profile>
+            <id>clean-jpa</id>
+            <build>
+                <plugins>
+                    <plugin>
+                        <groupId>org.liquibase</groupId>
+                        <artifactId>liquibase-maven-plugin</artifactId>
+                        <configuration>
+                            <changeLogFile>META-INF/jpa-changelog-master.xml</changeLogFile>
+
+                            <url>${keycloak.connectionsJpa.url}</url>
+                            <driver>${keycloak.connectionsJpa.driver}</driver>
+                            <username>${keycloak.connectionsJpa.user}</username>
+                            <password>${keycloak.connectionsJpa.password}</password>
+
+                            <promptOnNonLocalDatabase>false</promptOnNonLocalDatabase>
+                            <databaseClass>${keycloak.connectionsJpa.liquibaseDatabaseClass}</databaseClass>
+                        </configuration>
+                        <executions>
+                            <execution>
+                                <id>clean-jpa</id>
+                                <phase>clean</phase>
+                                <goals>
+                                    <goal>dropAll</goal>
+                                </goals>
+                            </execution>
+                        </executions>
+                    </plugin>
+                </plugins>
+            </build>
+        </profile>
+
+        <!-- Ldap profiles -->
+        <profile>
+            <activation>
+                <property>
+                    <name>ldap.vendor</name>
+                    <value>msad</value>
+                </property>
+            </activation>
+            <id>msad</id>
+            <build>
+                <plugins>
+                    <plugin>
+                        <groupId>org.apache.maven.plugins</groupId>
+                        <artifactId>maven-surefire-plugin</artifactId>
+                        <configuration>
+                            <includes>
+                                <include>org/keycloak/testsuite/federation/ldap/base/**</include>
+                            </includes>
+                            <excludes>
+                                <exclude>**/LDAPMultipleAttributesTest.java</exclude>
+                            </excludes>
+                        </configuration>
+                    </plugin>
+                </plugins>
+            </build>
+        </profile>
+
+    </profiles>
+</project>
diff --git a/testsuite/stress/src/main/java/org/keycloak/test/stress/MaxRateExecutor.java b/testsuite/stress/src/main/java/org/keycloak/test/stress/MaxRateExecutor.java
new file mode 100755
index 0000000..e56aa83
--- /dev/null
+++ b/testsuite/stress/src/main/java/org/keycloak/test/stress/MaxRateExecutor.java
@@ -0,0 +1,138 @@
+package org.keycloak.test.stress;
+
+import java.util.Collections;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.ExecutorCompletionService;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.Future;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Executes a test N number of times.  This is done multiple times over an ever expanding amount of threads to determine
+ * when the computer is saturated and you can't eek out any more concurrent requests.
+ *
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public class MaxRateExecutor {
+
+    public static class RateResult {
+        StressResult result;
+        int threads;
+        long time;
+
+        public RateResult(StressResult result, int threads, long time) {
+            this.result = result;
+            this.threads = threads;
+            this.time = time;
+        }
+
+        public StressResult getResult() {
+            return result;
+        }
+
+        public int getThreads() {
+            return threads;
+        }
+
+        public long getTime() {
+            return time;
+        }
+    }
+
+    List<RateResult> allResults = new LinkedList<>();
+    RateResult fastest = null;
+    RateResult last = null;
+
+
+
+    public void best(TestFactory factory, int jobs) {
+        fastest = last = null;
+        int threads = 2;
+        do {
+            fastest = last;
+            try {
+                last = execute(factory, threads, jobs);
+                allResults.add(last);
+            } catch (InterruptedException e) {
+                throw new RuntimeException(e);
+            } catch (ExecutionException e) {
+                throw new RuntimeException(e);
+            }
+            threads++;
+        } while (fastest == null || fastest.time > last.time);
+    }
+
+    public RateResult getFastest() {
+        return fastest;
+    }
+
+    public RateResult getLast() {
+        return last;
+    }
+
+    public RateResult execute(TestFactory factory, int threads, int jobs) throws InterruptedException, ExecutionException {
+        List<StressTest> tests = new LinkedList<>();
+        ExecutorService executor = Executors.newFixedThreadPool(threads);
+        ExecutorCompletionService<StressTest> completionService = new ExecutorCompletionService<>(executor);
+        StressResult result = new StressResult("num threads:" + threads);
+        addTest(factory, result, tests, threads + 5);
+        long start = System.currentTimeMillis();
+        for (StressTest stressTest : tests) {
+            completionService.submit(stressTest);
+        }
+        for (int i = 0; i < jobs; i++) {
+            Future<StressTest> future = completionService.take();
+            StressTest stressTest = future.get();
+            if (i < jobs - threads - 5) completionService.submit(stressTest);
+        }
+        long end = System.currentTimeMillis() - start;
+        executor.shutdown();
+        executor.awaitTermination(10, TimeUnit.SECONDS);
+        RateResult rate = new RateResult(result, threads, end);
+        return rate;
+    }
+
+    private void addTest(TestFactory factory, StressResult result, List<StressTest> tests, int num) {
+        int add = num - tests.size();
+        for (int i = 0; i < add; i++) {
+            Test test = factory.create();
+            test.init();
+            StressTest stress = new StressTest(result, test, 1);
+            tests.add(stress);
+        }
+    }
+
+    public void printResults() {
+        System.out.println("*******************");
+        System.out.println("*   Best Result   *");
+        System.out.println("*******************");
+        printResult(fastest);
+    }
+
+
+    public void printResult(RateResult result) {
+        System.out.println("Threads: " + result.getThreads());
+        System.out.println("Total Time: " + result.getTime());
+        System.out.println("Rate: " + ((double)result.getResult().getIterations()) / ((double)result.getTime()));
+        System.out.println("Successes: " + result.getResult().getSuccess());
+        System.out.println("Iterations: " + result.getResult().getIterations());
+        System.out.println("Average time per iteration: " + result.getResult().getAverageTime());
+
+    }
+
+    public void printSummary() {
+
+        for (RateResult result : allResults) {
+            System.out.println("*******************");
+            printSummary(result);
+        }
+    }
+    public void printSummary(RateResult result) {
+        System.out.println("Threads: " + result.getThreads());
+        System.out.println("Total Time: " + result.getTime());
+    }
+}
diff --git a/testsuite/stress/src/main/java/org/keycloak/test/stress/StressExecutor.java b/testsuite/stress/src/main/java/org/keycloak/test/stress/StressExecutor.java
new file mode 100755
index 0000000..ebf6ef1
--- /dev/null
+++ b/testsuite/stress/src/main/java/org/keycloak/test/stress/StressExecutor.java
@@ -0,0 +1,65 @@
+package org.keycloak.test.stress;
+
+import java.util.Collections;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Random;
+import java.util.concurrent.Callable;
+import java.util.concurrent.CompletionService;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.ExecutorCompletionService;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.Future;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Executes all test threads until completion.
+ *
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public class StressExecutor {
+    protected List<StressTest> tests = new LinkedList<>();
+    protected List<StressResult> results = new LinkedList<>();
+
+    public void addTest(Class<? extends Test> test, int threads, int iterations) {
+        StressResult result = new StressResult(test.getName());
+        results.add(result);
+        for (int i = 0; i < threads; i++) {
+            try {
+                Test t = test.newInstance();
+                t.init();
+                StressTest stress = new StressTest(result, t, iterations);
+                tests.add(stress);
+            } catch (InstantiationException e) {
+                throw new RuntimeException(e);
+            } catch (IllegalAccessException e) {
+                throw new RuntimeException(e);
+            }
+        }
+    }
+
+    public void addTest(Test test, StressResult result, int iterations) {
+        tests.add(new StressTest(result, test, iterations));
+    }
+
+    public void addTest(Test test, int iterations) {
+        StressResult result = new StressResult(test.getClass().getName());
+        tests.add(new StressTest(result, test, iterations));
+    }
+
+    public long execute() throws InterruptedException, ExecutionException {
+        ExecutorService executor = Executors.newFixedThreadPool(tests.size());
+        Collections.shuffle(tests);
+        long start = System.currentTimeMillis();
+        for (StressTest test : tests) {
+            executor.submit(test);
+        }
+        executor.shutdown();
+        boolean done = executor.awaitTermination(100, TimeUnit.HOURS);
+        long end = System.currentTimeMillis() - start;
+        return end;
+
+    }
+}
diff --git a/testsuite/stress/src/main/java/org/keycloak/test/stress/StressResult.java b/testsuite/stress/src/main/java/org/keycloak/test/stress/StressResult.java
new file mode 100755
index 0000000..080c553
--- /dev/null
+++ b/testsuite/stress/src/main/java/org/keycloak/test/stress/StressResult.java
@@ -0,0 +1,62 @@
+package org.keycloak.test.stress;
+
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.concurrent.atomic.AtomicLong;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public class StressResult {
+    ThreadLocal<Long> start = new ThreadLocal<>();
+    AtomicLong iterations = new AtomicLong();
+    AtomicLong totalTime = new AtomicLong();
+    String name;
+    AtomicInteger success = new AtomicInteger();
+
+    public StressResult(String name) {
+        this.name = name;
+    }
+
+    public void start() {
+        start.set(System.currentTimeMillis());
+    }
+
+    public void success() {
+        success.incrementAndGet();
+    }
+
+    public void end() {
+        long end = System.currentTimeMillis() - start.get();
+        totalTime.addAndGet(end);
+        iterations.incrementAndGet();
+    }
+
+    public int getSuccess() {
+        return success.get();
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    public long getTotalTime() {
+        return totalTime.longValue();
+    }
+    public long getIterations() {
+        return iterations.get();
+    }
+
+    public double getAverageTime() {
+        return (double)(double)totalTime.get() / (double)iterations.get();
+    }
+    public double getRate() {
+        return (double)(double)iterations.get() / (double)totalTime.get();
+    }
+
+    public void clear() {
+        iterations.set(0);
+        totalTime.set(0);
+        success.set(0);
+    }
+}
diff --git a/testsuite/stress/src/main/java/org/keycloak/test/stress/StressTest.java b/testsuite/stress/src/main/java/org/keycloak/test/stress/StressTest.java
new file mode 100755
index 0000000..363c5e2
--- /dev/null
+++ b/testsuite/stress/src/main/java/org/keycloak/test/stress/StressTest.java
@@ -0,0 +1,37 @@
+package org.keycloak.test.stress;
+
+import jdk.nashorn.internal.codegen.CompilerConstants;
+
+import java.util.concurrent.Callable;
+import java.util.concurrent.CountDownLatch;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public class StressTest implements Callable<StressTest> {
+    protected StressResult result;
+    protected Callable<Boolean> test;
+    protected int iterations;
+
+    public StressTest(StressResult result, Callable<Boolean> test, int iterations) {
+        this.result = result;
+        this.test = test;
+        this.iterations = iterations;
+    }
+
+    @Override
+    public StressTest call() throws Exception {
+        for (int i = 0; i < iterations; i++) {
+            result.start();
+            try {
+                if (test.call()) {
+                    result.success();
+                }
+            } catch (Throwable throwable) {
+            }
+            result.end();
+        }
+        return this;
+    }
+}
diff --git a/testsuite/stress/src/main/java/org/keycloak/test/stress/Test.java b/testsuite/stress/src/main/java/org/keycloak/test/stress/Test.java
new file mode 100755
index 0000000..04b7233
--- /dev/null
+++ b/testsuite/stress/src/main/java/org/keycloak/test/stress/Test.java
@@ -0,0 +1,11 @@
+package org.keycloak.test.stress;
+
+import java.util.concurrent.Callable;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public interface Test extends Callable<Boolean> {
+    void init();
+}
diff --git a/testsuite/stress/src/main/java/org/keycloak/test/stress/TestFactory.java b/testsuite/stress/src/main/java/org/keycloak/test/stress/TestFactory.java
new file mode 100755
index 0000000..aee1f7b
--- /dev/null
+++ b/testsuite/stress/src/main/java/org/keycloak/test/stress/TestFactory.java
@@ -0,0 +1,9 @@
+package org.keycloak.test.stress;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public interface TestFactory {
+    Test create();
+}
diff --git a/testsuite/stress/src/main/java/org/keycloak/test/stress/tests/LoginLogout.java b/testsuite/stress/src/main/java/org/keycloak/test/stress/tests/LoginLogout.java
new file mode 100755
index 0000000..859bc45
--- /dev/null
+++ b/testsuite/stress/src/main/java/org/keycloak/test/stress/tests/LoginLogout.java
@@ -0,0 +1,104 @@
+package org.keycloak.test.stress.tests;
+
+import org.junit.Assert;
+import org.keycloak.OAuth2Constants;
+import org.keycloak.admin.client.Keycloak;
+import org.keycloak.models.Constants;
+import org.keycloak.protocol.oidc.OIDCLoginProtocolService;
+import org.keycloak.test.stress.Test;
+import org.keycloak.testsuite.OAuthClient;
+import org.keycloak.testsuite.pages.LoginPage;
+import org.keycloak.testsuite.rule.WebResource;
+import org.keycloak.testsuite.rule.WebRule;
+import org.openqa.selenium.WebDriver;
+
+import javax.ws.rs.core.UriBuilder;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public class LoginLogout implements Test {
+    public WebRule webRule = new WebRule(this);
+
+    protected String securedResourceUrl;
+    protected List<String> containsInPage = new LinkedList<>();
+    protected String realm;
+    protected String authServerUrl;
+    protected String username;
+    protected String password;
+
+    protected String loginUrl;
+    protected String logoutUrl;
+
+    @WebResource
+    protected WebDriver driver;
+
+    @WebResource
+    protected LoginPage loginPage;
+
+    public LoginLogout securedResourceUrl(String securedResourceUrl) {
+        this.securedResourceUrl = securedResourceUrl;
+        return this;
+    }
+
+    public LoginLogout addPageContains(String contains) {
+        containsInPage.add(contains);
+        return this;
+    }
+
+    public LoginLogout realm(String realm) {
+        this.realm = realm;
+        return this;
+    }
+
+    public LoginLogout authServerUrl(String authServerUrl) {
+        this.authServerUrl = authServerUrl;
+        return this;
+    }
+
+    public LoginLogout username(String username) {
+        this.username = username;
+        return this;
+    }
+
+    public LoginLogout password(String password) {
+        this.password = password;
+        return this;
+    }
+
+    @Override
+    public void init() {
+
+        loginUrl = OIDCLoginProtocolService.authUrl(UriBuilder.fromUri(authServerUrl)).build(realm).toString();
+        logoutUrl = OIDCLoginProtocolService.logoutUrl(UriBuilder.fromUri(authServerUrl))
+                .queryParam(OAuth2Constants.REDIRECT_URI, securedResourceUrl).build(realm).toString();
+        try {
+            webRule.before();
+        } catch (Throwable e) {
+            throw new RuntimeException(e);
+        }
+
+    }
+
+    @Override
+    public Boolean call() throws Exception {
+        driver.navigate().to(securedResourceUrl);
+        Assert.assertTrue(driver.getCurrentUrl().startsWith(loginUrl));
+        loginPage.login(username, password);
+        Assert.assertTrue(driver.getCurrentUrl().startsWith(securedResourceUrl));
+        String pageSource = driver.getPageSource();
+        for (String contains : containsInPage) {
+            Assert.assertTrue(pageSource.contains(contains));
+
+        }
+
+        // test logout
+        driver.navigate().to(logoutUrl);
+        Assert.assertTrue(driver.getCurrentUrl().startsWith(loginUrl));
+        return true;
+    }
+}
diff --git a/testsuite/stress/src/test/java/org/keycloak/test/CustomerDatabaseServlet.java b/testsuite/stress/src/test/java/org/keycloak/test/CustomerDatabaseServlet.java
new file mode 100755
index 0000000..0115ef1
--- /dev/null
+++ b/testsuite/stress/src/test/java/org/keycloak/test/CustomerDatabaseServlet.java
@@ -0,0 +1,51 @@
+/*
+ * 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.test;
+
+import org.junit.Assert;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.security.Principal;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public class CustomerDatabaseServlet extends HttpServlet {
+    private static final String LINK = "<a href=\"%s\" id=\"%s\">%s</a>";
+
+    @Override
+    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
+        resp.setContentType("text/html");
+        PrintWriter pw = resp.getWriter();
+        Principal principal = req.getUserPrincipal();
+        Assert.assertNotNull(principal);
+        pw.printf("<html><head><title>%s</title></head><body>", "Customer Portal");
+        pw.println("Stian Thorgersen");
+        pw.println("Bill Burke");
+        pw.print("</body></html>");
+        pw.flush();
+
+
+    }
+}
diff --git a/testsuite/stress/src/test/java/org/keycloak/test/LoginLogoutTest.java b/testsuite/stress/src/test/java/org/keycloak/test/LoginLogoutTest.java
new file mode 100755
index 0000000..351f8f4
--- /dev/null
+++ b/testsuite/stress/src/test/java/org/keycloak/test/LoginLogoutTest.java
@@ -0,0 +1,96 @@
+package org.keycloak.test;
+
+import org.junit.ClassRule;
+import org.junit.Test;
+import org.keycloak.models.KeycloakSession;
+import org.keycloak.models.RealmModel;
+import org.keycloak.services.managers.RealmManager;
+import org.keycloak.test.stress.MaxRateExecutor;
+import org.keycloak.test.stress.StressExecutor;
+import org.keycloak.test.stress.TestFactory;
+import org.keycloak.test.stress.tests.LoginLogout;
+import org.keycloak.testsuite.adapter.AdapterTestStrategy;
+import org.keycloak.testsuite.adapter.CallAuthenticatedServlet;
+import org.keycloak.testsuite.adapter.CustomerDatabaseServlet;
+import org.keycloak.testsuite.adapter.CustomerServlet;
+import org.keycloak.testsuite.adapter.InputServlet;
+import org.keycloak.testsuite.adapter.ProductServlet;
+import org.keycloak.testsuite.adapter.SessionServlet;
+import org.keycloak.testsuite.rule.AbstractKeycloakRule;
+
+import java.net.URL;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public class LoginLogoutTest {
+    @ClassRule
+    public static AbstractKeycloakRule keycloakRule = new AbstractKeycloakRule() {
+        @Override
+        protected void configure(KeycloakSession session, RealmManager manager, RealmModel adminRealm) {
+            RealmModel realm = AdapterTestStrategy.baseAdapterTestInitialization(session, manager, adminRealm, getClass());
+
+            URL url = getClass().getResource("/adapter-test/cust-app-keycloak.json");
+            createApplicationDeployment()
+                    .name("customer-portal").contextPath("/customer-portal")
+                    .servletClass(org.keycloak.test.CustomerDatabaseServlet.class).adapterConfigPath(url.getPath())
+                    .role("user").deployApplication();
+
+        }
+    };
+
+    @Test
+    public void testStressExecutor() throws Exception {
+        System.out.println("*************************");
+        System.out.println();
+        System.out.println();
+        StressExecutor executor = new StressExecutor();
+        LoginLogout test = new LoginLogout();
+        test.authServerUrl("http://localhost:8081/auth")
+                .realm("demo")
+                .username("bburke@redhat.com")
+                .password("password")
+                .securedResourceUrl("http://localhost:8081/customer-portal");
+        test.init();
+        executor.addTest(test, 5);
+        long time = executor.execute();
+        System.out.println("Took: " + time );
+    }
+
+    /*
+**************************
+*   Bill's Best Result   *
+**************************
+Threads: 13
+Total Time: 1018
+Successes: 400
+Iterations: 400
+Average time: 32.8075
+Rate: 0.030480835174883793
+     */
+
+    @Test
+    public void testRate() throws Exception {
+        System.out.println("*************************");
+        System.out.println();
+        System.out.println();
+        TestFactory factory = new TestFactory() {
+            @Override
+            public org.keycloak.test.stress.Test create() {
+                LoginLogout test = new LoginLogout();
+                test.authServerUrl("http://localhost:8081/auth")
+                        .realm("demo")
+                        .username("bburke@redhat.com")
+                        .password("password")
+                        .securedResourceUrl("http://localhost:8081/customer-portal");
+                return test;
+            }
+        };
+        MaxRateExecutor executor = new MaxRateExecutor();
+        executor.best(factory, 10);
+        executor.printResults();
+        executor.printSummary();
+    }
+
+}
diff --git a/testsuite/stress/src/test/resources/adapter-test/cust-app-keycloak.json b/testsuite/stress/src/test/resources/adapter-test/cust-app-keycloak.json
new file mode 100755
index 0000000..e9ad987
--- /dev/null
+++ b/testsuite/stress/src/test/resources/adapter-test/cust-app-keycloak.json
@@ -0,0 +1,11 @@
+{
+    "realm": "demo",
+    "resource": "customer-portal",
+    "realm-public-key": "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCrVrCuTtArbgaZzL1hvh0xtL5mc7o0NqPVnYXkLvgcwiC3BjLGw1tGEGoJaXDuSaRllobm53JBhjx33UNv+5z/UMG4kytBWxheNVKnL6GgqlNabMaFfPLPCF8kAgKnsi79NMo+n6KnSY8YeUmec/p2vjO2NjsSAVcWEQMVhJ31LwIDAQAB",
+    "auth-server-url": "http://localhost:8081/auth",
+    "ssl-required" : "external",
+    "expose-token": true,
+    "credentials": {
+        "secret": "password"
+    }
+}
diff --git a/testsuite/stress/src/test/resources/testrealm.json b/testsuite/stress/src/test/resources/testrealm.json
new file mode 100755
index 0000000..b4718dd
--- /dev/null
+++ b/testsuite/stress/src/test/resources/testrealm.json
@@ -0,0 +1,185 @@
+{
+    "id": "test",
+    "realm": "test",
+    "enabled": true,
+    "sslRequired": "external",
+    "registrationAllowed": true,
+    "resetPasswordAllowed": true,
+    "editUsernameAllowed" : true,
+    "privateKey": "MIICXAIBAAKBgQCrVrCuTtArbgaZzL1hvh0xtL5mc7o0NqPVnYXkLvgcwiC3BjLGw1tGEGoJaXDuSaRllobm53JBhjx33UNv+5z/UMG4kytBWxheNVKnL6GgqlNabMaFfPLPCF8kAgKnsi79NMo+n6KnSY8YeUmec/p2vjO2NjsSAVcWEQMVhJ31LwIDAQABAoGAfmO8gVhyBxdqlxmIuglbz8bcjQbhXJLR2EoS8ngTXmN1bo2L90M0mUKSdc7qF10LgETBzqL8jYlQIbt+e6TH8fcEpKCjUlyq0Mf/vVbfZSNaVycY13nTzo27iPyWQHK5NLuJzn1xvxxrUeXI6A2WFpGEBLbHjwpx5WQG9A+2scECQQDvdn9NE75HPTVPxBqsEd2z10TKkl9CZxu10Qby3iQQmWLEJ9LNmy3acvKrE3gMiYNWb6xHPKiIqOR1as7L24aTAkEAtyvQOlCvr5kAjVqrEKXalj0Tzewjweuxc0pskvArTI2Oo070h65GpoIKLc9jf+UA69cRtquwP93aZKtW06U8dQJAF2Y44ks/mK5+eyDqik3koCI08qaC8HYq2wVl7G2QkJ6sbAaILtcvD92ToOvyGyeE0flvmDZxMYlvaZnaQ0lcSQJBAKZU6umJi3/xeEbkJqMfeLclD27XGEFoPeNrmdx0q10Azp4NfJAY+Z8KRyQCR2BEG+oNitBOZ+YXF9KCpH3cdmECQHEigJhYg+ykOvr1aiZUMFT72HU0jnmQe2FVekuG+LJUt2Tm7GtMjTFoGpf0JwrVuZN39fOYAlo+nTixgeW7X8Y=",
+    "publicKey": "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCrVrCuTtArbgaZzL1hvh0xtL5mc7o0NqPVnYXkLvgcwiC3BjLGw1tGEGoJaXDuSaRllobm53JBhjx33UNv+5z/UMG4kytBWxheNVKnL6GgqlNabMaFfPLPCF8kAgKnsi79NMo+n6KnSY8YeUmec/p2vjO2NjsSAVcWEQMVhJ31LwIDAQAB",
+    "requiredCredentials": [ "password" ],
+    "defaultRoles": [ "user" ],
+    "smtpServer": {
+        "from": "auto@keycloak.org",
+        "host": "localhost",
+        "port":"3025"
+    },
+    "users" : [
+        {
+            "username" : "test-user@localhost",
+            "enabled": true,
+            "email" : "test-user@localhost",
+            "firstName": "Tom",
+            "lastName": "Brady",
+            "credentials" : [
+                { "type" : "password",
+                  "value" : "password" }
+            ],
+            "realmRoles": ["user", "offline_access"],
+            "clientRoles": {
+                "test-app": [ "customer-user" ],
+                "account": [ "view-profile", "manage-account" ]
+            }
+        },
+        {
+            "username" : "john-doh@localhost",
+            "enabled": true,
+            "email" : "john-doh@localhost",
+            "firstName": "John",
+            "lastName": "Doh",
+            "credentials" : [
+                { "type" : "password",
+                    "value" : "password" }
+            ],
+            "realmRoles": ["user"],
+            "clientRoles": {
+                "test-app": [ "customer-user" ],
+                "account": [ "view-profile", "manage-account" ]
+            }
+        },
+        {
+                "username" : "keycloak-user@localhost",
+            "enabled": true,
+            "email" : "keycloak-user@localhost",
+            "credentials" : [
+                { "type" : "password",
+                    "value" : "password" }
+            ],
+            "realmRoles": ["user"],
+            "clientRoles": {
+                "test-app": [ "customer-user" ],
+                "account": [ "view-profile", "manage-account" ]
+            }
+        },
+        {
+            "username" : "topGroupUser",
+            "enabled": true,
+            "email" : "top@redhat.com",
+            "credentials" : [
+                { "type" : "password",
+                    "value" : "password" }
+            ],
+            "groups": [
+                "/topGroup"
+            ]
+        },
+        {
+            "username" : "level2GroupUser",
+            "enabled": true,
+            "email" : "level2@redhat.com",
+            "credentials" : [
+                { "type" : "password",
+                    "value" : "password" }
+            ],
+            "groups": [
+                "/topGroup/level2group"
+            ]
+        }
+    ],
+    "scopeMappings": [
+        {
+            "client": "third-party",
+            "roles": ["user"]
+        },
+        {
+            "client": "test-app",
+            "roles": ["user"]
+        }
+    ],
+    "clients": [
+        {
+            "clientId": "test-app",
+            "enabled": true,
+            "baseUrl": "http://localhost:8081/app",
+            "redirectUris": [
+                "http://localhost:8081/app/*"
+            ],
+            "adminUrl": "http://localhost:8081/app/logout",
+            "secret": "password"
+        },
+        {
+            "clientId" : "third-party",
+            "enabled": true,
+            "consentRequired": true,
+
+            "redirectUris": [
+                "http://localhost:8081/app/*"
+            ],
+            "secret": "password"
+        }
+    ],
+    "roles" : {
+        "realm" : [
+            {
+                "name": "user",
+                "description": "Have User privileges"
+            },
+            {
+                "name": "admin",
+                "description": "Have Administrator privileges"
+            }
+        ],
+        "client" : {
+            "test-app" : [
+                {
+                    "name": "customer-user",
+                    "description": "Have Customer User privileges"
+                },
+                {
+                    "name": "customer-admin",
+                    "description": "Have Customer Admin privileges"
+                }
+            ]
+        }
+
+    },
+    "groups" : [
+        {
+            "name": "topGroup",
+            "attributes": {
+                "topAttribute": ["true"]
+
+            },
+            "realmRoles": ["user"],
+
+            "subGroups": [
+                {
+                    "name": "level2group",
+                    "realmRoles": ["admin"],
+                    "clientRoles": {
+                        "test-app": ["customer-user"]
+                    },
+                    "attributes": {
+                        "level2Attribute": ["true"]
+
+                    }
+                }
+            ]
+        }
+    ],
+
+
+    "clientScopeMappings": {
+        "test-app": [
+            {
+                "client": "third-party",
+                "roles": ["customer-user"]
+            }
+        ]
+    },
+
+    "internationalizationEnabled": true,
+    "supportedLocales": ["en", "de"],
+    "defaultLocale": "en"
+}