keycloak-memoizeit

Changes

model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/DefaultCacheRealmProvider.java 396(+0 -396)

model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/InfinispanRealmCache.java 202(+0 -202)

model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/locking/entities/RevisionedCachedClient.java 40(+0 -40)

model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/locking/entities/RevisionedCachedClientRole.java 32(+0 -32)

model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/locking/entities/RevisionedCachedClientTemplate.java 34(+0 -34)

model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/locking/entities/RevisionedCachedGroup.java 31(+0 -31)

model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/locking/entities/RevisionedCachedRealm.java 55(+0 -55)

model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/locking/entities/RevisionedCachedRealmRole.java 31(+0 -31)

model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/locking/LockingCacheRealmProvider.java 523(+0 -523)

model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/locking/LockingCacheRealmProviderFactory.java 159(+0 -159)

model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/locking/LockingConnectionProviderFactory.java 54(+0 -54)

model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/locking/LockingRealmCache.java 300(+0 -300)

Details

diff --git a/distribution/feature-packs/server-feature-pack/src/main/resources/content/standalone/configuration/keycloak-server.json b/distribution/feature-packs/server-feature-pack/src/main/resources/content/standalone/configuration/keycloak-server.json
index ff5bb85..87a2da8 100755
--- a/distribution/feature-packs/server-feature-pack/src/main/resources/content/standalone/configuration/keycloak-server.json
+++ b/distribution/feature-packs/server-feature-pack/src/main/resources/content/standalone/configuration/keycloak-server.json
@@ -23,7 +23,7 @@
     },
 
     "userCache": {
-        "infinispan" : {
+        "default" : {
             "enabled": true
         }
     },
@@ -61,15 +61,15 @@
     },
 
     "realmCache": {
-        "provider": "infinispan-locking",
-        "infinispan-locking" : {
+        "provider": "default",
+        "default" : {
             "enabled": true
         }
     },
 
     "connectionsInfinispan": {
-        "provider": "locking",
-        "locking": {
+        "provider": "default",
+        "default": {
             "cacheContainer" : "java:comp/env/infinispan/Keycloak"
         }
     }
diff --git a/model/infinispan/src/main/java/org/keycloak/connections/infinispan/DefaultInfinispanConnectionProviderFactory.java b/model/infinispan/src/main/java/org/keycloak/connections/infinispan/DefaultInfinispanConnectionProviderFactory.java
index 01d229f..ded6c3c 100755
--- a/model/infinispan/src/main/java/org/keycloak/connections/infinispan/DefaultInfinispanConnectionProviderFactory.java
+++ b/model/infinispan/src/main/java/org/keycloak/connections/infinispan/DefaultInfinispanConnectionProviderFactory.java
@@ -24,6 +24,7 @@ import org.infinispan.configuration.global.GlobalConfigurationBuilder;
 import org.infinispan.manager.DefaultCacheManager;
 import org.infinispan.manager.EmbeddedCacheManager;
 import org.infinispan.transaction.LockingMode;
+import org.infinispan.transaction.TransactionMode;
 import org.infinispan.transaction.lookup.DummyTransactionManagerLookup;
 import org.jboss.logging.Logger;
 import org.keycloak.Config;
@@ -154,6 +155,15 @@ public class DefaultInfinispanConnectionProviderFactory implements InfinispanCon
         }
         Configuration replicationCacheConfiguration = replicationConfigBuilder.build();
         cacheManager.defineConfiguration(InfinispanConnectionProvider.WORK_CACHE_NAME, replicationCacheConfiguration);
+
+        ConfigurationBuilder counterConfigBuilder = new ConfigurationBuilder();
+        counterConfigBuilder.invocationBatching().enable()
+                .transaction().transactionMode(TransactionMode.TRANSACTIONAL);
+        counterConfigBuilder.transaction().transactionManagerLookup(new DummyTransactionManagerLookup());
+        counterConfigBuilder.transaction().lockingMode(LockingMode.PESSIMISTIC);
+        Configuration counterCacheConfiguration = counterConfigBuilder.build();
+
+        cacheManager.defineConfiguration(InfinispanConnectionProvider.VERSION_CACHE_NAME, counterCacheConfiguration);
     }
 
 }
diff --git a/model/infinispan/src/main/java/org/keycloak/connections/infinispan/InfinispanConnectionProvider.java b/model/infinispan/src/main/java/org/keycloak/connections/infinispan/InfinispanConnectionProvider.java
old mode 100644
new mode 100755
index b37beb9..8a21def
--- a/model/infinispan/src/main/java/org/keycloak/connections/infinispan/InfinispanConnectionProvider.java
+++ b/model/infinispan/src/main/java/org/keycloak/connections/infinispan/InfinispanConnectionProvider.java
@@ -25,6 +25,7 @@ import org.keycloak.provider.Provider;
  */
 public interface InfinispanConnectionProvider extends Provider {
 
+    public static final String VERSION_CACHE_NAME = "realmVersions";
     static final String REALM_CACHE_NAME = "realms";
     static final String USER_CACHE_NAME = "users";
     static final String SESSION_CACHE_NAME = "sessions";
diff --git a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/ClientAdapter.java b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/ClientAdapter.java
index a9145b2..2094fa2 100755
--- a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/ClientAdapter.java
+++ b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/ClientAdapter.java
@@ -19,8 +19,7 @@ package org.keycloak.models.cache.infinispan;
 
 import org.keycloak.models.*;
 import org.keycloak.models.cache.CacheRealmProvider;
-import org.keycloak.models.cache.RealmCache;
-import org.keycloak.models.cache.entities.CachedClient;
+import org.keycloak.models.cache.infinispan.entities.CachedClient;
 
 import java.util.*;
 
@@ -45,7 +44,7 @@ public class ClientAdapter implements ClientModel {
 
     private void getDelegateForUpdate() {
         if (updated == null) {
-            cacheSession.registerApplicationInvalidation(getId());
+            cacheSession.registerClientInvalidation(getId());
             updated = cacheSession.getDelegate().getClientById(getId(), cachedRealm);
             if (updated == null) throw new IllegalStateException("Not found in database");
         }
@@ -377,7 +376,6 @@ public class ClientAdapter implements ClientModel {
     public void setClientId(String clientId) {
         getDelegateForUpdate();
         updated.setClientId(clientId);
-        cacheSession.registerRealmInvalidation(cachedRealm.getId());
     }
 
     @Override
@@ -465,12 +463,19 @@ public class ClientAdapter implements ClientModel {
     }
 
     @Override
-    public void updateDefaultRoles(String[] defaultRoles) {
+    public void updateDefaultRoles(String... defaultRoles) {
         getDelegateForUpdate();
         updated.updateDefaultRoles(defaultRoles);
     }
 
     @Override
+    public void removeDefaultRoles(String... defaultRoles) {
+        getDelegateForUpdate();
+        updated.removeDefaultRoles(defaultRoles);
+
+    }
+
+    @Override
     public boolean isBearerOnly() {
         if (updated != null) return updated.isBearerOnly();
         return cached.isBearerOnly();
@@ -544,12 +549,10 @@ public class ClientAdapter implements ClientModel {
 
     @Override
     public RoleModel getRole(String name) {
-        if (updated != null) return updated.getRole(name);
-        String id = cached.getRoles().get(name);
-        if (id == null) {
-            return null;
+        for (RoleModel role : getRoles()) {
+            if (role.getName().equals(name)) return role;
         }
-        return cacheSession.getRoleById(id, cachedRealm);
+        return null;
     }
 
     @Override
@@ -577,15 +580,7 @@ public class ClientAdapter implements ClientModel {
 
     @Override
     public Set<RoleModel> getRoles() {
-        if (updated != null) return updated.getRoles();
-
-        Set<RoleModel> roles = new HashSet<RoleModel>();
-        for (String id : cached.getRoles().values()) {
-            RoleModel roleById = cacheSession.getRoleById(id, cachedRealm);
-            if (roleById == null) continue;
-            roles.add(roleById);
-        }
-        return roles;
+        return cacheSession.getClientRoles(cachedRealm, this);
     }
 
     @Override
diff --git a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/ClientTemplateAdapter.java b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/ClientTemplateAdapter.java
index ce4095e..b54738a 100755
--- a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/ClientTemplateAdapter.java
+++ b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/ClientTemplateAdapter.java
@@ -24,8 +24,7 @@ import org.keycloak.models.RealmModel;
 import org.keycloak.models.RoleContainerModel;
 import org.keycloak.models.RoleModel;
 import org.keycloak.models.cache.CacheRealmProvider;
-import org.keycloak.models.cache.RealmCache;
-import org.keycloak.models.cache.entities.CachedClientTemplate;
+import org.keycloak.models.cache.infinispan.entities.CachedClientTemplate;
 
 import java.util.HashMap;
 import java.util.HashSet;
@@ -39,14 +38,12 @@ import java.util.Set;
 public class ClientTemplateAdapter implements ClientTemplateModel {
     protected CacheRealmProvider cacheSession;
     protected RealmModel cachedRealm;
-    protected RealmCache cache;
 
     protected ClientTemplateModel updated;
     protected CachedClientTemplate cached;
 
-    public ClientTemplateAdapter(RealmModel cachedRealm, CachedClientTemplate cached, CacheRealmProvider cacheSession, RealmCache cache) {
+    public ClientTemplateAdapter(RealmModel cachedRealm, CachedClientTemplate cached, CacheRealmProvider cacheSession) {
         this.cachedRealm = cachedRealm;
-        this.cache = cache;
         this.cacheSession = cacheSession;
         this.cached = cached;
     }
diff --git a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/DefaultCacheUserProvider.java b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/DefaultCacheUserProvider.java
index 3726edb..e8f74bc 100755
--- a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/DefaultCacheUserProvider.java
+++ b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/DefaultCacheUserProvider.java
@@ -19,8 +19,7 @@ package org.keycloak.models.cache.infinispan;
 
 import org.keycloak.models.*;
 import org.keycloak.models.cache.CacheUserProvider;
-import org.keycloak.models.cache.UserCache;
-import org.keycloak.models.cache.entities.CachedUser;
+import org.keycloak.models.cache.infinispan.entities.CachedUser;
 
 import java.util.*;
 
diff --git a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/entities/ClientListQuery.java b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/entities/ClientListQuery.java
new file mode 100755
index 0000000..4fc650d
--- /dev/null
+++ b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/entities/ClientListQuery.java
@@ -0,0 +1,49 @@
+package org.keycloak.models.cache.infinispan.entities;
+
+import org.keycloak.models.RealmModel;
+
+import java.util.HashSet;
+import java.util.Set;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public class ClientListQuery extends AbstractRevisioned implements ClientQuery {
+    private final Set<String> clients;
+    private final String realm;
+    private final String realmName;
+
+    public ClientListQuery(Long revisioned, String id, RealmModel realm, Set<String> clients) {
+        super(revisioned, id);
+        this.realm = realm.getId();
+        this.realmName = realm.getName();
+        this.clients = clients;
+    }
+
+    public ClientListQuery(Long revisioned, String id, RealmModel realm, String client) {
+        super(revisioned, id);
+        this.realm = realm.getId();
+        this.realmName = realm.getName();
+        this.clients = new HashSet<>();
+        this.clients.add(client);
+    }
+
+    @Override
+    public Set<String> getClients() {
+        return clients;
+    }
+
+    @Override
+    public String getRealm() {
+        return realm;
+    }
+
+    @Override
+    public String toString() {
+        return "ClientListQuery{" +
+                "id='" + getId() + "'" +
+                "realmName='" + realmName + '\'' +
+                '}';
+    }
+}
diff --git a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/entities/ClientQuery.java b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/entities/ClientQuery.java
new file mode 100755
index 0000000..e58b921
--- /dev/null
+++ b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/entities/ClientQuery.java
@@ -0,0 +1,13 @@
+package org.keycloak.models.cache.infinispan.entities;
+
+import org.keycloak.models.cache.infinispan.entities.InRealm;
+
+import java.util.Set;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public interface ClientQuery extends InRealm {
+    Set<String> getClients();
+}
diff --git a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/entities/ClientTemplateQuery.java b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/entities/ClientTemplateQuery.java
new file mode 100755
index 0000000..e07f7ab
--- /dev/null
+++ b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/entities/ClientTemplateQuery.java
@@ -0,0 +1,13 @@
+package org.keycloak.models.cache.infinispan.entities;
+
+import org.keycloak.models.cache.infinispan.entities.InRealm;
+
+import java.util.Set;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public interface ClientTemplateQuery extends InRealm {
+    Set<String> getTemplates();
+}
diff --git a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/entities/GroupQuery.java b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/entities/GroupQuery.java
new file mode 100755
index 0000000..905d271
--- /dev/null
+++ b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/entities/GroupQuery.java
@@ -0,0 +1,13 @@
+package org.keycloak.models.cache.infinispan.entities;
+
+import org.keycloak.models.cache.infinispan.entities.InRealm;
+
+import java.util.Set;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public interface GroupQuery extends InRealm {
+    Set<String> getGroups();
+}
diff --git a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/entities/InClient.java b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/entities/InClient.java
new file mode 100755
index 0000000..464dbaf
--- /dev/null
+++ b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/entities/InClient.java
@@ -0,0 +1,9 @@
+package org.keycloak.models.cache.infinispan.entities;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public interface InClient extends InRealm {
+    String getClientId();
+}
diff --git a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/entities/InRealm.java b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/entities/InRealm.java
new file mode 100755
index 0000000..8a880ec
--- /dev/null
+++ b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/entities/InRealm.java
@@ -0,0 +1,9 @@
+package org.keycloak.models.cache.infinispan.entities;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public interface InRealm extends Revisioned {
+    String getRealm();
+}
diff --git a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/entities/RealmListQuery.java b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/entities/RealmListQuery.java
new file mode 100755
index 0000000..446fe58
--- /dev/null
+++ b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/entities/RealmListQuery.java
@@ -0,0 +1,27 @@
+package org.keycloak.models.cache.infinispan.entities;
+
+import java.util.HashSet;
+import java.util.Set;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public class RealmListQuery extends AbstractRevisioned implements RealmQuery {
+    private final Set<String> realms;
+
+    public RealmListQuery(Long revision, String id, String realm) {
+        super(revision, id);
+        realms = new HashSet<>();
+        realms.add(realm);
+    }
+    public RealmListQuery(Long revision, String id, Set<String> realms) {
+        super(revision, id);
+        this.realms = realms;
+    }
+
+    @Override
+    public Set<String> getRealms() {
+        return realms;
+    }
+}
\ No newline at end of file
diff --git a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/entities/RealmQuery.java b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/entities/RealmQuery.java
new file mode 100755
index 0000000..612e95b
--- /dev/null
+++ b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/entities/RealmQuery.java
@@ -0,0 +1,11 @@
+package org.keycloak.models.cache.infinispan.entities;
+
+import java.util.Set;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public interface RealmQuery {
+    Set<String> getRealms();
+}
diff --git a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/entities/RoleListQuery.java b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/entities/RoleListQuery.java
new file mode 100755
index 0000000..4b0ab08
--- /dev/null
+++ b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/entities/RoleListQuery.java
@@ -0,0 +1,68 @@
+package org.keycloak.models.cache.infinispan.entities;
+
+import org.keycloak.models.RealmModel;
+import org.keycloak.models.cache.infinispan.entities.AbstractRevisioned;
+import org.keycloak.models.cache.infinispan.entities.ClientQuery;
+import org.keycloak.models.cache.infinispan.entities.RoleQuery;
+
+import java.util.HashSet;
+import java.util.Set;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public class RoleListQuery extends AbstractRevisioned implements RoleQuery, InClient {
+    private final Set<String> roles;
+    private final String realm;
+    private final String realmName;
+    private String client;
+
+    public RoleListQuery(Long revisioned, String id, RealmModel realm, Set<String> roles) {
+        super(revisioned, id);
+        this.realm = realm.getId();
+        this.realmName = realm.getName();
+        this.roles = roles;
+    }
+
+    public RoleListQuery(Long revisioned, String id, RealmModel realm, String role) {
+        super(revisioned, id);
+        this.realm = realm.getId();
+        this.realmName = realm.getName();
+        this.roles = new HashSet<>();
+        this.roles.add(role);
+    }
+
+    public RoleListQuery(Long revision, String id, RealmModel realm, Set<String> roles, String client) {
+        this(revision, id, realm, roles);
+        this.client = client;
+    }
+
+    public RoleListQuery(Long revision, String id, RealmModel realm, String role, String client) {
+        this(revision, id, realm, role);
+        this.client = client;
+    }
+
+    @Override
+    public Set<String> getRoles() {
+        return roles;
+    }
+
+    @Override
+    public String getRealm() {
+        return realm;
+    }
+
+    @Override
+    public String getClientId() {
+        return client;
+    }
+
+    @Override
+    public String toString() {
+        return "RoleListQuery{" +
+                "id='" + getId() + "'" +
+                "realmName='" + realmName + '\'' +
+                '}';
+    }
+}
diff --git a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/entities/RoleQuery.java b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/entities/RoleQuery.java
new file mode 100755
index 0000000..6635831
--- /dev/null
+++ b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/entities/RoleQuery.java
@@ -0,0 +1,13 @@
+package org.keycloak.models.cache.infinispan.entities;
+
+import org.keycloak.models.cache.infinispan.entities.InRealm;
+
+import java.util.Set;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public interface RoleQuery extends InRealm {
+    Set<String> getRoles();
+}
diff --git a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/GroupAdapter.java b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/GroupAdapter.java
index af704ec..1810a34 100755
--- a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/GroupAdapter.java
+++ b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/GroupAdapter.java
@@ -24,7 +24,7 @@ import org.keycloak.models.RealmModel;
 import org.keycloak.models.RoleContainerModel;
 import org.keycloak.models.RoleModel;
 import org.keycloak.models.cache.CacheRealmProvider;
-import org.keycloak.models.cache.entities.CachedGroup;
+import org.keycloak.models.cache.infinispan.entities.CachedGroup;
 
 import java.util.HashSet;
 import java.util.List;
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 34aa5ce..fb9946b 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
@@ -18,15 +18,6 @@
 package org.keycloak.models.cache.infinispan;
 
 import org.infinispan.Cache;
-import org.infinispan.notifications.Listener;
-import org.infinispan.notifications.cachelistener.annotation.CacheEntriesEvicted;
-import org.infinispan.notifications.cachelistener.annotation.CacheEntryCreated;
-import org.infinispan.notifications.cachelistener.annotation.CacheEntryInvalidated;
-import org.infinispan.notifications.cachelistener.annotation.CacheEntryRemoved;
-import org.infinispan.notifications.cachelistener.event.CacheEntriesEvictedEvent;
-import org.infinispan.notifications.cachelistener.event.CacheEntryCreatedEvent;
-import org.infinispan.notifications.cachelistener.event.CacheEntryInvalidatedEvent;
-import org.infinispan.notifications.cachelistener.event.CacheEntryRemovedEvent;
 import org.jboss.logging.Logger;
 import org.keycloak.Config;
 import org.keycloak.connections.infinispan.InfinispanConnectionProvider;
@@ -34,10 +25,7 @@ import org.keycloak.models.KeycloakSession;
 import org.keycloak.models.KeycloakSessionFactory;
 import org.keycloak.models.cache.CacheRealmProvider;
 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;
+import org.keycloak.models.cache.infinispan.entities.Revisioned;
 
 /**
  * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
@@ -47,23 +35,21 @@ public class InfinispanCacheRealmProviderFactory implements CacheRealmProviderFa
 
     private static final Logger log = Logger.getLogger(InfinispanCacheRealmProviderFactory.class);
 
-    protected volatile InfinispanRealmCache realmCache;
-
-    protected final ConcurrentHashMap<String, String> realmLookup = new ConcurrentHashMap<>();
+    protected volatile StreamRealmCache realmCache;
 
     @Override
     public CacheRealmProvider create(KeycloakSession session) {
         lazyInit(session);
-        return new DefaultCacheRealmProvider(realmCache, session);
+        return new StreamCacheRealmProvider(realmCache, session);
     }
 
     private void lazyInit(KeycloakSession session) {
         if (realmCache == null) {
             synchronized (this) {
                 if (realmCache == null) {
-                    Cache<String, Object> cache = session.getProvider(InfinispanConnectionProvider.class).getCache(InfinispanConnectionProvider.REALM_CACHE_NAME);
-                    cache.addListener(new CacheListener());
-                    realmCache = new InfinispanRealmCache(cache, realmLookup);
+                    Cache<String, Revisioned> cache = session.getProvider(InfinispanConnectionProvider.class).getCache(InfinispanConnectionProvider.REALM_CACHE_NAME);
+                    Cache<String, Long> revisions = session.getProvider(InfinispanConnectionProvider.class).getCache(InfinispanConnectionProvider.VERSION_CACHE_NAME);
+                    realmCache = new StreamRealmCache(cache, revisions);
                 }
             }
         }
@@ -84,77 +70,7 @@ public class InfinispanCacheRealmProviderFactory implements CacheRealmProviderFa
 
     @Override
     public String getId() {
-        return "infinispan";
+        return "default";
     }
 
-    @Listener
-    public class CacheListener {
-
-        @CacheEntryCreated
-        public void created(CacheEntryCreatedEvent<String, Object> event) {
-            if (!event.isPre()) {
-                Object object = event.getValue();
-                if (object != null) {
-                    if (object instanceof CachedRealm) {
-                        CachedRealm realm = (CachedRealm) object;
-                        realmLookup.put(realm.getName(), realm.getId());
-                        log.tracev("Realm added realm={0}", realm.getName());
-                    }
-                }
-            }
-        }
-
-        @CacheEntryRemoved
-        public void removed(CacheEntryRemovedEvent<String, Object> event) {
-            if (event.isPre()) {
-                Object object = event.getValue();
-                if (object != null) {
-                    remove(object);
-                }
-            }
-        }
-
-        @CacheEntryInvalidated
-        public void removed(CacheEntryInvalidatedEvent<String, Object> event) {
-            if (event.isPre()) {
-                Object object = event.getValue();
-                if (object != null) {
-                    remove(object);
-                }
-            }
-        }
-
-        @CacheEntriesEvicted
-        public void userEvicted(CacheEntriesEvictedEvent<String, Object> event) {
-            for (Object object : event.getEntries().values()) {
-                remove(object);
-            }
-        }
-
-        private void remove(Object object) {
-            if (object instanceof CachedRealm) {
-                CachedRealm realm = (CachedRealm) object;
-
-                realmLookup.remove(realm.getName());
-
-                for (String r : realm.getRealmRoles().values()) {
-                    realmCache.evictRoleById(r);
-                }
-
-                for (String c : realm.getClients()) {
-                    realmCache.evictClientById(c);
-                }
-
-                log.tracev("Realm removed realm={0}", realm.getName());
-            } else if (object instanceof CachedClient) {
-                CachedClient client = (CachedClient) object;
-
-                for (String r : client.getRoles().values()) {
-                    realmCache.evictRoleById(r);
-                }
-
-                log.tracev("Client removed client={0}", client.getId());
-            }
-        }
-    }
 }
diff --git a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/InfinispanCacheUserProviderFactory.java b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/InfinispanCacheUserProviderFactory.java
index b5f62e2..e8657ff 100755
--- a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/InfinispanCacheUserProviderFactory.java
+++ b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/InfinispanCacheUserProviderFactory.java
@@ -28,7 +28,7 @@ import org.keycloak.models.KeycloakSession;
 import org.keycloak.models.KeycloakSessionFactory;
 import org.keycloak.models.cache.CacheUserProvider;
 import org.keycloak.models.cache.CacheUserProviderFactory;
-import org.keycloak.models.cache.entities.CachedUser;
+import org.keycloak.models.cache.infinispan.entities.CachedUser;
 
 import java.util.concurrent.ConcurrentHashMap;
 
@@ -78,7 +78,7 @@ public class InfinispanCacheUserProviderFactory implements CacheUserProviderFact
 
     @Override
     public String getId() {
-        return "infinispan";
+        return "default";
     }
 
     @Listener
diff --git a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/InfinispanUserCache.java b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/InfinispanUserCache.java
index 52edabc..bb7196e 100755
--- a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/InfinispanUserCache.java
+++ b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/InfinispanUserCache.java
@@ -19,15 +19,14 @@ package org.keycloak.models.cache.infinispan;
 
 import org.infinispan.Cache;
 import org.jboss.logging.Logger;
-import org.keycloak.models.cache.UserCache;
-import org.keycloak.models.cache.entities.CachedUser;
+import org.keycloak.models.cache.infinispan.entities.CachedUser;
 
 /**
  * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
  */
 public class InfinispanUserCache implements UserCache {
 
-    protected static final Logger logger = Logger.getLogger(InfinispanRealmCache.class);
+    protected static final Logger logger = Logger.getLogger(InfinispanUserCache.class);
 
     protected volatile boolean enabled = true;
 
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 2f3048a..030286b 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
@@ -21,8 +21,7 @@ import org.keycloak.Config;
 import org.keycloak.common.enums.SslRequired;
 import org.keycloak.models.*;
 import org.keycloak.models.cache.CacheRealmProvider;
-import org.keycloak.models.cache.RealmCache;
-import org.keycloak.models.cache.entities.CachedRealm;
+import org.keycloak.models.cache.infinispan.entities.CachedRealm;
 import org.keycloak.models.utils.KeycloakModelUtils;
 
 import java.security.Key;
@@ -582,47 +581,37 @@ public class RealmAdapter implements RealmModel {
     }
 
     @Override
-    public void updateDefaultRoles(String[] defaultRoles) {
+    public void updateDefaultRoles(String... defaultRoles) {
         getDelegateForUpdate();
         updated.updateDefaultRoles(defaultRoles);
     }
 
     @Override
+    public void removeDefaultRoles(String... defaultRoles) {
+        getDelegateForUpdate();
+        updated.removeDefaultRoles(defaultRoles);
+
+    }
+
+    @Override
     public List<ClientModel> getClients() {
-        if (updated != null) return updated.getClients();
-        List<ClientModel> apps = new LinkedList<>();
-        for (String id : cached.getClients()) {
-            ClientModel model = cacheSession.getClientById(id, this);
-            if (model == null) {
-                throw new IllegalStateException("Cached application not found: " + id);
-            }
-            apps.add(model);
-        }
-        return Collections.unmodifiableList(apps);
+        return cacheSession.getClients(this);
 
     }
 
     @Override
     public ClientModel addClient(String name) {
-        getDelegateForUpdate();
-        ClientModel app = updated.addClient(name);
-        cacheSession.registerApplicationInvalidation(app.getId());
-        return app;
+        return cacheSession.addClient(this, name);
     }
 
     @Override
     public ClientModel addClient(String id, String clientId) {
-        getDelegateForUpdate();
-        ClientModel app =  updated.addClient(id, clientId);
-        cacheSession.registerApplicationInvalidation(app.getId());
-        return app;
+        return cacheSession.addClient(this, id, clientId);
     }
 
     @Override
     public boolean removeClient(String id) {
-        cacheSession.registerApplicationInvalidation(id);
-        getDelegateForUpdate();
-        return updated.removeClient(id);
+        return cacheSession.removeClient(id, this);
     }
 
     @Override
@@ -888,12 +877,18 @@ public class RealmAdapter implements RealmModel {
 
     @Override
     public RoleModel getRole(String name) {
-        if (updated != null) return updated.getRole(name);
-        String id = cached.getRealmRoles().get(name);
-        if (id == null) return null;
-        return cacheSession.getRoleById(id, this);
+        for (RoleModel role : getRoles()) {
+            if (role.getName().equals(name)) return role;
+        }
+        return null;
+    }
+
+    @Override
+    public Set<RoleModel> getRoles() {
+        return cacheSession.getRealmRoles(this);
     }
 
+
     @Override
     public RoleModel addRole(String name) {
         getDelegateForUpdate();
@@ -917,18 +912,6 @@ public class RealmAdapter implements RealmModel {
         return updated.removeRole(role);
     }
 
-    @Override
-    public Set<RoleModel> getRoles() {
-        if (updated != null) return updated.getRoles();
-
-        Set<RoleModel> roles = new HashSet<RoleModel>();
-        for (String id : cached.getRealmRoles().values()) {
-            RoleModel roleById = cacheSession.getRoleById(id, this);
-            if (roleById == null) continue;
-            roles.add(roleById);
-        }
-        return Collections.unmodifiableSet(roles);
-    }
 
     @Override
     public boolean isIdentityFederationEnabled() {
diff --git a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/RoleAdapter.java b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/RoleAdapter.java
index 6458c1a..0b0c3a6 100755
--- a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/RoleAdapter.java
+++ b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/RoleAdapter.java
@@ -21,10 +21,9 @@ import org.keycloak.models.RealmModel;
 import org.keycloak.models.RoleContainerModel;
 import org.keycloak.models.RoleModel;
 import org.keycloak.models.cache.CacheRealmProvider;
-import org.keycloak.models.cache.RealmCache;
-import org.keycloak.models.cache.entities.CachedClientRole;
-import org.keycloak.models.cache.entities.CachedRealmRole;
-import org.keycloak.models.cache.entities.CachedRole;
+import org.keycloak.models.cache.infinispan.entities.CachedClientRole;
+import org.keycloak.models.cache.infinispan.entities.CachedRealmRole;
+import org.keycloak.models.cache.infinispan.entities.CachedRole;
 import org.keycloak.models.utils.KeycloakModelUtils;
 
 import java.util.HashSet;
@@ -38,13 +37,11 @@ public class RoleAdapter implements RoleModel {
 
     protected RoleModel updated;
     protected CachedRole cached;
-    protected RealmCache cache;
     protected CacheRealmProvider cacheSession;
     protected RealmModel realm;
 
-    public RoleAdapter(CachedRole cached, RealmCache cache, CacheRealmProvider session, RealmModel realm) {
+    public RoleAdapter(CachedRole cached, CacheRealmProvider session, RealmModel realm) {
         this.cached = cached;
-        this.cache = cache;
         this.cacheSession = session;
         this.realm = realm;
     }
@@ -125,7 +122,7 @@ public class RoleAdapter implements RoleModel {
         for (String id : cached.getComposites()) {
             RoleModel role = realm.getRoleById(id);
             if (role == null) {
-                throw new IllegalStateException("Could not find composite: " + id);
+                throw new IllegalStateException("Could not find composite in role " + getName() + ": " + id);
             }
             set.add(role);
         }
@@ -138,7 +135,7 @@ public class RoleAdapter implements RoleModel {
             return realm;
         } else {
             CachedClientRole appRole = (CachedClientRole)cached;
-            return realm.getClientById(appRole.getIdClient());
+            return realm.getClientById(appRole.getClientId());
         }
     }
 
diff --git a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/stream/ClientQueryPredicate.java b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/stream/ClientQueryPredicate.java
new file mode 100755
index 0000000..bf4ade8
--- /dev/null
+++ b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/stream/ClientQueryPredicate.java
@@ -0,0 +1,48 @@
+package org.keycloak.models.cache.infinispan.stream;
+
+import org.jboss.logging.Logger;
+import org.keycloak.models.cache.infinispan.entities.ClientQuery;
+import org.keycloak.models.cache.infinispan.entities.Revisioned;
+
+import java.io.Serializable;
+import java.util.Map;
+import java.util.function.Predicate;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public class ClientQueryPredicate implements Predicate<Map.Entry<String, Revisioned>>, Serializable {
+    protected static final Logger logger = Logger.getLogger(ClientQueryPredicate.class);
+    private String client;
+    private String inRealm;
+
+    public static ClientQueryPredicate create() {
+        return new ClientQueryPredicate();
+    }
+
+    public ClientQueryPredicate client(String client) {
+        this.client = client;
+        return this;
+    }
+
+    public ClientQueryPredicate inRealm(String inRealm) {
+        this.inRealm = inRealm;
+        return this;
+    }
+
+
+
+
+
+    @Override
+    public boolean test(Map.Entry<String, Revisioned> entry) {
+        Object value = entry.getValue();
+        if (value == null) return false;
+        if (!(value instanceof ClientQuery)) return false;
+        ClientQuery query = (ClientQuery)value;
+        if (client != null && !query.getClients().contains(client)) return false;
+        if (inRealm != null && !query.getRealm().equals(inRealm)) return false;
+        return true;
+    }
+}
diff --git a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/stream/ClientTemplateQueryPredicate.java b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/stream/ClientTemplateQueryPredicate.java
new file mode 100755
index 0000000..fba0c02
--- /dev/null
+++ b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/stream/ClientTemplateQueryPredicate.java
@@ -0,0 +1,40 @@
+package org.keycloak.models.cache.infinispan.stream;
+
+import org.keycloak.models.cache.infinispan.entities.ClientTemplateQuery;
+import org.keycloak.models.cache.infinispan.entities.Revisioned;
+
+import java.io.Serializable;
+import java.util.Map;
+import java.util.function.Predicate;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public class ClientTemplateQueryPredicate implements Predicate<Map.Entry<String, Revisioned>>, Serializable {
+    private String template;
+
+    public static ClientTemplateQueryPredicate create() {
+        return new ClientTemplateQueryPredicate();
+    }
+
+    public ClientTemplateQueryPredicate template(String template) {
+        this.template = template;
+        return this;
+    }
+
+
+
+
+
+    @Override
+    public boolean test(Map.Entry<String, Revisioned> entry) {
+        Object value = entry.getValue();
+        if (value == null) return false;
+        if (!(value instanceof ClientTemplateQuery)) return false;
+        ClientTemplateQuery query = (ClientTemplateQuery)value;
+
+
+        return query.getTemplates().contains(template);
+    }
+}
diff --git a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/stream/GroupQueryPredicate.java b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/stream/GroupQueryPredicate.java
new file mode 100755
index 0000000..855930e
--- /dev/null
+++ b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/stream/GroupQueryPredicate.java
@@ -0,0 +1,40 @@
+package org.keycloak.models.cache.infinispan.stream;
+
+import org.keycloak.models.cache.infinispan.entities.GroupQuery;
+import org.keycloak.models.cache.infinispan.entities.Revisioned;
+
+import java.io.Serializable;
+import java.util.Map;
+import java.util.function.Predicate;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public class GroupQueryPredicate implements Predicate<Map.Entry<String, Revisioned>>, Serializable {
+    private String group;
+
+    public static GroupQueryPredicate create() {
+        return new GroupQueryPredicate();
+    }
+
+    public GroupQueryPredicate group(String group) {
+        this.group = group;
+        return this;
+    }
+
+
+
+
+
+    @Override
+    public boolean test(Map.Entry<String, Revisioned> entry) {
+        Object value = entry.getValue();
+        if (value == null) return false;
+        if (!(value instanceof GroupQuery)) return false;
+        GroupQuery query = (GroupQuery)value;
+
+
+        return query.getGroups().contains(group);
+    }
+}
diff --git a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/stream/HasRolePredicate.java b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/stream/HasRolePredicate.java
new file mode 100755
index 0000000..7d32462
--- /dev/null
+++ b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/stream/HasRolePredicate.java
@@ -0,0 +1,46 @@
+package org.keycloak.models.cache.infinispan.stream;
+
+import org.keycloak.models.cache.infinispan.entities.CachedGroup;
+import org.keycloak.models.cache.infinispan.entities.CachedRole;
+import org.keycloak.models.cache.infinispan.entities.Revisioned;
+import org.keycloak.models.cache.infinispan.entities.RoleQuery;
+
+import java.io.Serializable;
+import java.util.Map;
+import java.util.function.Predicate;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public class HasRolePredicate implements Predicate<Map.Entry<String, Revisioned>>, Serializable {
+    private String role;
+
+    public static HasRolePredicate create() {
+        return new HasRolePredicate();
+    }
+
+    public HasRolePredicate role(String role) {
+        this.role = role;
+        return this;
+    }
+
+    @Override
+    public boolean test(Map.Entry<String, Revisioned> entry) {
+        Object value = entry.getValue();
+        if (value == null) return false;
+        if (value instanceof CachedRole) {
+            CachedRole cachedRole = (CachedRole)value;
+            if (cachedRole.getComposites().contains(role)) return true;
+        }
+        if (value instanceof CachedGroup) {
+            CachedGroup cachedRole = (CachedGroup)value;
+            if (cachedRole.getRoleMappings().contains(role)) return true;
+        }
+        if (value instanceof RoleQuery) {
+            RoleQuery roleQuery = (RoleQuery)value;
+            if (roleQuery.getRoles().contains(role)) return true;
+        }
+        return false;
+    }
+}
diff --git a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/stream/InClientPredicate.java b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/stream/InClientPredicate.java
new file mode 100755
index 0000000..a582f34
--- /dev/null
+++ b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/stream/InClientPredicate.java
@@ -0,0 +1,34 @@
+package org.keycloak.models.cache.infinispan.stream;
+
+import org.keycloak.models.cache.infinispan.entities.InClient;
+import org.keycloak.models.cache.infinispan.entities.Revisioned;
+
+import java.io.Serializable;
+import java.util.Map;
+import java.util.function.Predicate;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public class InClientPredicate implements Predicate<Map.Entry<String, Revisioned>>, Serializable {
+    private String clientId;
+
+    public static InClientPredicate create() {
+        return new InClientPredicate();
+    }
+
+    public InClientPredicate client(String id) {
+        clientId = id;
+        return this;
+    }
+
+    @Override
+    public boolean test(Map.Entry<String, Revisioned> entry) {
+        Object value = entry.getValue();
+        if (value == null) return false;
+        if (!(value instanceof InClient)) return false;
+
+        return clientId.equals(((InClient)value).getClientId());
+    }
+}
diff --git a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/stream/InRealmPredicate.java b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/stream/InRealmPredicate.java
new file mode 100755
index 0000000..d24a9bd
--- /dev/null
+++ b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/stream/InRealmPredicate.java
@@ -0,0 +1,34 @@
+package org.keycloak.models.cache.infinispan.stream;
+
+import org.keycloak.models.cache.infinispan.entities.InRealm;
+import org.keycloak.models.cache.infinispan.entities.Revisioned;
+
+import java.io.Serializable;
+import java.util.Map;
+import java.util.function.Predicate;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public class InRealmPredicate implements Predicate<Map.Entry<String, Revisioned>>, Serializable {
+    private String realm;
+
+    public static InRealmPredicate create() {
+        return new InRealmPredicate();
+    }
+
+    public InRealmPredicate realm(String id) {
+        realm = id;
+        return this;
+    }
+
+    @Override
+    public boolean test(Map.Entry<String, Revisioned> entry) {
+        Object value = entry.getValue();
+        if (value == null) return false;
+        if (!(value instanceof InRealm)) return false;
+
+        return realm.equals(((InRealm)value).getRealm());
+    }
+}
diff --git a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/stream/RealmQueryPredicate.java b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/stream/RealmQueryPredicate.java
new file mode 100755
index 0000000..dbb64f5
--- /dev/null
+++ b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/stream/RealmQueryPredicate.java
@@ -0,0 +1,40 @@
+package org.keycloak.models.cache.infinispan.stream;
+
+import org.keycloak.models.cache.infinispan.entities.RealmQuery;
+import org.keycloak.models.cache.infinispan.entities.Revisioned;
+
+import java.io.Serializable;
+import java.util.Map;
+import java.util.function.Predicate;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public class RealmQueryPredicate implements Predicate<Map.Entry<String, Revisioned>>, Serializable {
+    private String realm;
+
+    public static RealmQueryPredicate create() {
+        return new RealmQueryPredicate();
+    }
+
+    public RealmQueryPredicate realm(String realm) {
+        this.realm = realm;
+        return this;
+    }
+
+
+
+
+
+    @Override
+    public boolean test(Map.Entry<String, Revisioned> entry) {
+        Object value = entry.getValue();
+        if (value == null) return false;
+        if (!(value instanceof RealmQuery)) return false;
+        RealmQuery query = (RealmQuery)value;
+
+
+        return query.getRealms().contains(realm);
+    }
+}
diff --git a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/stream/RoleQueryPredicate.java b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/stream/RoleQueryPredicate.java
new file mode 100755
index 0000000..5e37d59
--- /dev/null
+++ b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/stream/RoleQueryPredicate.java
@@ -0,0 +1,40 @@
+package org.keycloak.models.cache.infinispan.stream;
+
+import org.keycloak.models.cache.infinispan.entities.Revisioned;
+import org.keycloak.models.cache.infinispan.entities.RoleQuery;
+
+import java.io.Serializable;
+import java.util.Map;
+import java.util.function.Predicate;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public class RoleQueryPredicate implements Predicate<Map.Entry<String, Revisioned>>, Serializable {
+    private String role;
+
+    public static RoleQueryPredicate create() {
+        return new RoleQueryPredicate();
+    }
+
+    public RoleQueryPredicate role(String role) {
+        this.role = role;
+        return this;
+    }
+
+
+
+
+
+    @Override
+    public boolean test(Map.Entry<String, Revisioned> entry) {
+        Object value = entry.getValue();
+        if (value == null) return false;
+        if (!(value instanceof RoleQuery)) return false;
+        RoleQuery query = (RoleQuery)value;
+
+
+        return query.getRoles().contains(role);
+    }
+}
diff --git a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/StreamCacheRealmProvider.java b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/StreamCacheRealmProvider.java
new file mode 100755
index 0000000..21c0c7d
--- /dev/null
+++ b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/StreamCacheRealmProvider.java
@@ -0,0 +1,776 @@
+/*
+ * 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.models.cache.infinispan;
+
+import org.jboss.logging.Logger;
+import org.keycloak.migration.MigrationModel;
+import org.keycloak.models.ClientModel;
+import org.keycloak.models.ClientTemplateModel;
+import org.keycloak.models.GroupModel;
+import org.keycloak.models.KeycloakSession;
+import org.keycloak.models.KeycloakTransaction;
+import org.keycloak.models.RealmModel;
+import org.keycloak.models.RealmProvider;
+import org.keycloak.models.RoleModel;
+import org.keycloak.models.cache.CacheRealmProvider;
+import org.keycloak.models.cache.infinispan.entities.CachedClient;
+import org.keycloak.models.cache.infinispan.entities.CachedClientRole;
+import org.keycloak.models.cache.infinispan.entities.CachedClientTemplate;
+import org.keycloak.models.cache.infinispan.entities.CachedGroup;
+import org.keycloak.models.cache.infinispan.entities.CachedRealm;
+import org.keycloak.models.cache.infinispan.entities.CachedRealmRole;
+import org.keycloak.models.cache.infinispan.entities.CachedRole;
+import org.keycloak.models.cache.infinispan.entities.ClientListQuery;
+import org.keycloak.models.cache.infinispan.entities.RealmListQuery;
+import org.keycloak.models.cache.infinispan.entities.RoleListQuery;
+import org.keycloak.models.utils.KeycloakModelUtils;
+
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+
+/**
+ * - the high level architecture of this cache is an invalidation cache.
+ * - the cache is manual/custom versioned.  When a model is updated, we remove it from the cache
+ * which causes an invalidation message to be sent across the cluster.
+ * - We had to do it this way because Infinispan REPEATABLE_READ
+ * wouldn't cut it in invalidation mode.  Also, REPEATABLE_READ doesn't work very well on relationships and items that are
+ * not in the cache.
+ * - There are two Infinispan caches.  One clustered that holds actual objects and a another local one that holds revision
+ * numbers of cached objects.  Whenever a cached object is removed (invalidated), the local revision
+ * cache number or that key is bumped higher based on a local version counter.  Whenever a cache entry is fetched, this
+ * revision number is also fetched and compared against the revision number in the cache entry to see if the cache entry
+ * is stale.  Whenever a cache entry is added, this revision number is also checked against the revision cache.
+ * - Revision entries are actually never removed (although they could be evicted by cache eviction policies).  The reason for this
+ * is that it is possible for a stale object to be inserted if one thread loads and the data is updated in the database before
+ * it is added to the cache.  So, we keep the version number around for this.
+ * - In a transaction, objects are registered to be invalidated.  If an object is marked for invalidation within a transaction
+ * a cached object should never be returned.  An DB adapter should always be returned.
+ * - At prepare phase of the transaction, a local lock on the revision cache will be obtained for each object marked for invalidation
+ * we sort the list of these keys to order local acquisition and avoid deadlocks.
+ * - After DB commits, the objects marked for invalidation are invalidated, or rather removed from the cache.  At this time
+ * the revision cache entry for this object has its version number bumped.
+ * - Whenever an object is marked for invalidation, the cache is also searched for any objects that are related to this object
+ * and need to also be evicted/removed.  We use the Infinispan Stream SPI for this.
+ *
+ * ClientList caches:
+ * - lists of clients are cached in a specific cache entry i.e. realm clients, find client by clientId
+ * - realm client lists need to be invalidated and evited whenever a client is added or removed from a realm.  RealmProvider
+ * now has addClient/removeClient at its top level.  All adapaters should use these methods so that the appropriate invalidations
+ * can be registered.
+ * - whenever a client is added/removed the realm of the client is added to a listInvalidations set
+ * this set must be checked before sending back or caching a cached query.  This check is required to
+ * avoid caching an uncommitted removal/add in a query cache.
+ * - when a client is removed, any queries that contain that client must also be removed.
+ * - a client removal will also cause anything that is contained and cached within that client to be removed
+ *
+ * Clustered caches:
+ * - There is a Infinispan @Listener registered.  If an invalidation event happens, this is treated like
+ * the object was removed from the database and will perform evictions based on that assumption.
+ * - Eviction events will also cascade other evictions, but not assume this is a db removal.
+ *
+ * Groups and Roles:
+ * - roles are tricky because of composites.  Composite lists are cached too.  So, when a role is removed
+ * we also iterate and invalidate any role or group that contains that role being removed.
+ *
+ *
+ *
+ * - any relationship should be resolved from session.realms().  For example if JPA.getClientByClientId() is invoked,
+ *  JPA should find the id of the client and then call session.realms().getClientById().  THis is to ensure that the cached
+ *  object is invoked and all proper invalidation are being invoked.
+ *
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public class StreamCacheRealmProvider implements CacheRealmProvider {
+    protected static final Logger logger = Logger.getLogger(StreamCacheRealmProvider.class);
+    public static final String REALM_CLIENTS_QUERY_SUFFIX = ".realm.clients";
+    public static final String ROLES_QUERY_SUFFIX = ".roles";
+    public static final String ROLE_BY_NAME_QUERY_SUFFIX = ".role.by-name";
+    protected StreamRealmCache cache;
+    protected KeycloakSession session;
+    protected RealmProvider delegate;
+    protected boolean transactionActive;
+    protected boolean setRollbackOnly;
+
+    protected Map<String, RealmModel> managedRealms = new HashMap<>();
+    protected Map<String, ClientModel> managedApplications = new HashMap<>();
+    protected Map<String, ClientTemplateModel> managedClientTemplates = new HashMap<>();
+    protected Map<String, RoleModel> managedRoles = new HashMap<>();
+    protected Map<String, GroupModel> managedGroups = new HashMap<>();
+    protected Set<String> listInvalidations = new HashSet<>();
+    protected Set<String> invalidations = new HashSet<>();
+
+    protected boolean clearAll;
+
+    public StreamCacheRealmProvider(StreamRealmCache cache, KeycloakSession session) {
+        this.cache = cache;
+        this.session = session;
+        session.getTransaction().enlistPrepare(getPrepareTransaction());
+        session.getTransaction().enlistAfterCompletion(getAfterTransaction());
+    }
+
+    @Override
+    public void clear() {
+        cache.clear();
+    }
+
+    @Override
+    public MigrationModel getMigrationModel() {
+        return getDelegate().getMigrationModel();
+    }
+
+    @Override
+    public RealmProvider getDelegate() {
+        if (!transactionActive) throw new IllegalStateException("Cannot access delegate without a transaction");
+        if (delegate != null) return delegate;
+        delegate = session.getProvider(RealmProvider.class);
+        return delegate;
+    }
+
+    @Override
+    public void registerRealmInvalidation(String id) {
+        invalidations.add(id);
+        cache.realmInvalidation(id, invalidations);
+    }
+
+    @Override
+    public void registerClientInvalidation(String id) {
+        invalidations.add(id);
+        cache.clientInvalidation(id, invalidations);
+    }
+    @Override
+    public void registerClientTemplateInvalidation(String id) {
+        invalidations.add(id);
+        cache.clientTemplateInvalidation(id, invalidations);
+    }
+
+    @Override
+    public void registerRoleInvalidation(String id) {
+        invalidations.add(id);
+        cache.roleInvalidation(id, invalidations);
+    }
+
+    @Override
+    public void registerGroupInvalidation(String id) {
+        invalidations.add(id);
+        cache.groupInvalidation(id, invalidations);
+    }
+
+    protected void runInvalidations() {
+        for (String id : invalidations) {
+            cache.invalidateObject(id);
+        }
+    }
+
+    private KeycloakTransaction getPrepareTransaction() {
+        return new KeycloakTransaction() {
+            @Override
+            public void begin() {
+                transactionActive = true;
+            }
+
+            @Override
+            public void commit() {
+                if (delegate == null) return;
+                List<String> locks = new LinkedList<>();
+                locks.addAll(invalidations);
+
+                Collections.sort(locks); // lock ordering
+                cache.getRevisions().startBatch();
+                //if (!invalidates.isEmpty()) cache.getRevisions().getAdvancedCache().lock(invalidates);
+                for (String lock : locks) {
+                    boolean success = cache.getRevisions().getAdvancedCache().lock(lock);
+                }
+
+            }
+
+            @Override
+            public void rollback() {
+                setRollbackOnly = true;
+                transactionActive = false;
+            }
+
+            @Override
+            public void setRollbackOnly() {
+                setRollbackOnly = true;
+            }
+
+            @Override
+            public boolean getRollbackOnly() {
+                return setRollbackOnly;
+            }
+
+            @Override
+            public boolean isActive() {
+                return transactionActive;
+            }
+        };
+    }
+
+    private KeycloakTransaction getAfterTransaction() {
+        return new KeycloakTransaction() {
+            @Override
+            public void begin() {
+                transactionActive = true;
+            }
+
+            @Override
+            public void commit() {
+                try {
+                    if (delegate == null) return;
+                    if (clearAll) {
+                        cache.clear();
+                    }
+                    runInvalidations();
+                    transactionActive = false;
+                } finally {
+                    cache.endRevisionBatch();
+                }
+            }
+
+            @Override
+            public void rollback() {
+                try {
+                    setRollbackOnly = true;
+                    runInvalidations();
+                    transactionActive = false;
+                } finally {
+                    cache.endRevisionBatch();
+                }
+            }
+
+            @Override
+            public void setRollbackOnly() {
+                setRollbackOnly = true;
+            }
+
+            @Override
+            public boolean getRollbackOnly() {
+                return setRollbackOnly;
+            }
+
+            @Override
+            public boolean isActive() {
+                return transactionActive;
+            }
+        };
+    }
+
+    @Override
+    public RealmModel createRealm(String name) {
+        RealmModel realm = getDelegate().createRealm(name);
+        registerRealmInvalidation(realm.getId());
+        return realm;
+    }
+
+    @Override
+    public RealmModel createRealm(String id, String name) {
+        RealmModel realm =  getDelegate().createRealm(id, name);
+        registerRealmInvalidation(realm.getId());
+        return realm;
+    }
+
+    @Override
+    public RealmModel getRealm(String id) {
+        CachedRealm cached = cache.get(id, CachedRealm.class);
+        if (cached != null) {
+            logger.tracev("by id cache hit: {0}", cached.getName());
+        }
+        if (cached == null) {
+            Long loaded = cache.getCurrentRevision(id);
+            RealmModel model = getDelegate().getRealm(id);
+            if (model == null) return null;
+            if (invalidations.contains(id)) return model;
+            cached = new CachedRealm(loaded, model);
+            cache.addRevisioned(cached);
+        } else if (invalidations.contains(id)) {
+            return getDelegate().getRealm(id);
+        } else if (managedRealms.containsKey(id)) {
+            return managedRealms.get(id);
+        }
+        RealmAdapter adapter = new RealmAdapter(cached, this);
+        managedRealms.put(id, adapter);
+        return adapter;
+    }
+
+    @Override
+    public RealmModel getRealmByName(String name) {
+        String cacheKey = getRealmByNameCacheKey(name);
+        RealmListQuery query = cache.get(cacheKey, RealmListQuery.class);
+        if (query != null) {
+            logger.tracev("realm by name cache hit: {0}", name);
+        }
+        if (query == null) {
+            Long loaded = cache.getCurrentRevision(cacheKey);
+            RealmModel model = getDelegate().getRealmByName(name);
+            if (model == null) return null;
+            if (invalidations.contains(model.getId())) return model;
+            query = new RealmListQuery(loaded, cacheKey, model.getId());
+            cache.addRevisioned(query);
+            return model;
+        } else if (invalidations.contains(cacheKey)) {
+            return getDelegate().getRealmByName(name);
+        } else {
+            String realmId = query.getRealms().iterator().next();
+            if (invalidations.contains(realmId)) {
+                return getDelegate().getRealmByName(name);
+            }
+            return getRealm(realmId);
+        }
+    }
+
+    public String getRealmByNameCacheKey(String name) {
+        return "realm.query.by.name." + name;
+    }
+
+    @Override
+    public List<RealmModel> getRealms() {
+        // Retrieve realms from backend
+        List<RealmModel> backendRealms = getDelegate().getRealms();
+
+        // Return cache delegates to ensure cache invalidated during write operations
+        List<RealmModel> cachedRealms = new LinkedList<RealmModel>();
+        for (RealmModel realm : backendRealms) {
+            RealmModel cached = getRealm(realm.getId());
+            cachedRealms.add(cached);
+        }
+        return cachedRealms;
+    }
+
+    @Override
+    public boolean removeRealm(String id) {
+        RealmModel realm = getRealm(id);
+        if (realm == null) return false;
+
+        invalidations.add(getRealmClientsQueryCacheKey(id));
+        invalidations.add(getRealmByNameCacheKey(realm.getName()));
+        cache.invalidateObject(id);
+        cache.realmRemoval(id, invalidations);
+        return getDelegate().removeRealm(id);
+    }
+
+    @Override
+    public ClientModel addClient(RealmModel realm, String clientId) {
+        ClientModel client = getDelegate().addClient(realm, clientId);
+        return addedClient(realm, client);
+    }
+
+    @Override
+    public ClientModel addClient(RealmModel realm, String id, String clientId) {
+        ClientModel client = getDelegate().addClient(realm, id, clientId);
+        return addedClient(realm, client);
+    }
+
+    private ClientModel addedClient(RealmModel realm, ClientModel client) {
+        logger.trace("added Client.....");
+        // need to invalidate realm client query cache every time as it may not be loaded on this node, but loaded on another
+        invalidations.add(getRealmClientsQueryCacheKey(realm.getId()));
+        invalidations.add(client.getId());
+        cache.clientAdded(realm.getId(), client.getId(), invalidations);
+        // this is needed so that a new client that hasn't been committed isn't cached in a query
+        listInvalidations.add(realm.getId());
+        return client;
+    }
+
+    private String getRealmClientsQueryCacheKey(String realm) {
+        return realm + REALM_CLIENTS_QUERY_SUFFIX;
+    }
+
+    private String getRolesCacheKey(String container) {
+        return container + ROLES_QUERY_SUFFIX;
+    }
+    private String getRoleByNameCacheKey(String container, String name) {
+        return container + "." + name + ROLES_QUERY_SUFFIX;
+    }
+
+    @Override
+    public List<ClientModel> getClients(RealmModel realm) {
+        String cacheKey = getRealmClientsQueryCacheKey(realm.getId());
+        boolean queryDB = invalidations.contains(cacheKey) || listInvalidations.contains(realm.getId());
+        if (queryDB) {
+            return getDelegate().getClients(realm);
+        }
+
+        ClientListQuery query = cache.get(cacheKey, ClientListQuery.class);
+        if (query != null) {
+            logger.tracev("getClients cache hit: {0}", realm.getName());
+        }
+
+        if (query == null) {
+            Long loaded = cache.getCurrentRevision(cacheKey);
+            List<ClientModel> model = getDelegate().getClients(realm);
+            if (model == null) return null;
+            Set<String> ids = new HashSet<>();
+            for (ClientModel client : model) ids.add(client.getId());
+            query = new ClientListQuery(loaded, cacheKey, realm, ids);
+            logger.tracev("adding realm clients cache miss: realm {0} key {1}", realm.getName(), cacheKey);
+            cache.addRevisioned(query);
+            return model;
+        }
+        List<ClientModel> list = new LinkedList<>();
+        for (String id : query.getClients()) {
+            ClientModel client = session.realms().getClientById(id, realm);
+            if (client == null) {
+                invalidations.add(cacheKey);
+                return getDelegate().getClients(realm);
+            }
+            list.add(client);
+        }
+        return list;
+    }
+
+
+    @Override
+    public boolean removeClient(String id, RealmModel realm) {
+        ClientModel client = getClientById(id, realm);
+        if (client == null) return false;
+        // need to invalidate realm client query cache every time client list is changed
+        invalidations.add(getRealmClientsQueryCacheKey(realm.getId()));
+        invalidations.add(getClientByClientIdCacheKey(client.getClientId(), realm));
+        listInvalidations.add(realm.getId());
+        registerClientInvalidation(id);
+        cache.clientRemoval(realm.getId(), id, invalidations);
+        for (RoleModel role : client.getRoles()) {
+            cache.roleInvalidation(role.getId(), invalidations);
+        }
+        return getDelegate().removeClient(id, realm);
+    }
+
+    @Override
+    public void close() {
+        if (delegate != null) delegate.close();
+    }
+
+    @Override
+    public RoleModel addRealmRole(RealmModel realm, String name) {
+        return addRealmRole(realm, KeycloakModelUtils.generateId(), name);
+    }
+
+    @Override
+    public RoleModel addRealmRole(RealmModel realm, String id, String name) {
+        invalidations.add(getRolesCacheKey(realm.getId()));
+        // this is needed so that a new role that hasn't been committed isn't cached in a query
+        listInvalidations.add(realm.getId());
+        RoleModel role = getDelegate().addRealmRole(realm, name);
+        invalidations.add(role.getId());
+        return role;
+    }
+
+    @Override
+    public Set<RoleModel> getRealmRoles(RealmModel realm) {
+        String cacheKey = getRolesCacheKey(realm.getId());
+        boolean queryDB = invalidations.contains(cacheKey) || listInvalidations.contains(realm.getId());
+        if (queryDB) {
+            return getDelegate().getRealmRoles(realm);
+        }
+
+        RoleListQuery query = cache.get(cacheKey, RoleListQuery.class);
+        if (query != null) {
+            logger.tracev("getRealmRoles cache hit: {0}", realm.getName());
+        }
+
+        if (query == null) {
+            Long loaded = cache.getCurrentRevision(cacheKey);
+            Set<RoleModel> model = getDelegate().getRealmRoles(realm);
+            if (model == null) return null;
+            Set<String> ids = new HashSet<>();
+            for (RoleModel role : model) ids.add(role.getId());
+            query = new RoleListQuery(loaded, cacheKey, realm, ids);
+            logger.tracev("adding realm roles cache miss: realm {0} key {1}", realm.getName(), cacheKey);
+            cache.addRevisioned(query);
+            return model;
+        }
+        Set<RoleModel> list = new HashSet<>();
+        for (String id : query.getRoles()) {
+            RoleModel role = session.realms().getRoleById(id, realm);
+            if (role == null) {
+                invalidations.add(cacheKey);
+                return getDelegate().getRealmRoles(realm);
+            }
+            list.add(role);
+        }
+        return list;
+    }
+
+    @Override
+    public Set<RoleModel> getClientRoles(RealmModel realm, ClientModel client) {
+        String cacheKey = getRolesCacheKey(client.getId());
+        boolean queryDB = invalidations.contains(cacheKey) || listInvalidations.contains(client.getId());
+        if (queryDB) {
+            return getDelegate().getClientRoles(realm, client);
+        }
+
+        RoleListQuery query = cache.get(cacheKey, RoleListQuery.class);
+        if (query != null) {
+            logger.tracev("getClientRoles cache hit: {0}", client.getClientId());
+        }
+
+        if (query == null) {
+            Long loaded = cache.getCurrentRevision(cacheKey);
+            Set<RoleModel> model = getDelegate().getClientRoles(realm, client);
+            if (model == null) return null;
+            Set<String> ids = new HashSet<>();
+            for (RoleModel role : model) ids.add(role.getId());
+            query = new RoleListQuery(loaded, cacheKey, realm, ids, client.getClientId());
+            logger.tracev("adding client roles cache miss: client {0} key {1}", client.getClientId(), cacheKey);
+            cache.addRevisioned(query);
+            return model;
+        }
+        Set<RoleModel> list = new HashSet<>();
+        for (String id : query.getRoles()) {
+            RoleModel role = session.realms().getRoleById(id, realm);
+            if (role == null) {
+                invalidations.add(cacheKey);
+                return getDelegate().getClientRoles(realm, client);
+            }
+            list.add(role);
+        }
+        return list;
+    }
+
+    @Override
+    public RoleModel addClientRole(RealmModel realm, ClientModel client, String name) {
+        return addClientRole(realm, client, KeycloakModelUtils.generateId(), name);
+    }
+
+    @Override
+    public RoleModel addClientRole(RealmModel realm, ClientModel client, String id, String name) {
+        invalidations.add(getRolesCacheKey(client.getId()));
+        // this is needed so that a new role that hasn't been committed isn't cached in a query
+        listInvalidations.add(client.getId());
+        RoleModel role = getDelegate().addClientRole(realm, client, id, name);
+        invalidations.add(role.getId());
+        return role;
+    }
+
+    @Override
+    public RoleModel getRealmRole(RealmModel realm, String name) {
+        String cacheKey = getRoleByNameCacheKey(realm.getId(), name);
+        boolean queryDB = invalidations.contains(cacheKey) || listInvalidations.contains(realm.getId());
+        if (queryDB) {
+            return getDelegate().getRealmRole(realm, name);
+        }
+
+        RoleListQuery query = cache.get(cacheKey, RoleListQuery.class);
+        if (query != null) {
+            logger.tracev("getRealmRole cache hit: {0}.{1}", realm.getName(), name);
+        }
+
+        if (query == null) {
+            Long loaded = cache.getCurrentRevision(cacheKey);
+            RoleModel model = getDelegate().getRealmRole(realm, name);
+            if (model == null) return null;
+            query = new RoleListQuery(loaded, cacheKey, realm, model.getId());
+            logger.tracev("adding realm role cache miss: client {0} key {1}", realm.getName(), cacheKey);
+            cache.addRevisioned(query);
+            return model;
+        }
+        RoleModel role = getRoleById(query.getRoles().iterator().next(), realm);
+        if (role == null) {
+            invalidations.add(cacheKey);
+            return getDelegate().getRealmRole(realm, name);
+        }
+        return role;
+    }
+
+    @Override
+    public RoleModel getClientRole(RealmModel realm, ClientModel client, String name) {
+        String cacheKey = getRoleByNameCacheKey(client.getId(), name);
+        boolean queryDB = invalidations.contains(cacheKey) || listInvalidations.contains(client.getId());
+        if (queryDB) {
+            return getDelegate().getClientRole(realm, client, name);
+        }
+
+        RoleListQuery query = cache.get(cacheKey, RoleListQuery.class);
+        if (query != null) {
+            logger.tracev("getClientRole cache hit: {0}.{1}", client.getClientId(), name);
+        }
+
+        if (query == null) {
+            Long loaded = cache.getCurrentRevision(cacheKey);
+            RoleModel model = getDelegate().getClientRole(realm, client, name);
+            if (model == null) return null;
+            query = new RoleListQuery(loaded, cacheKey, realm, model.getId(), client.getClientId());
+            logger.tracev("adding client role cache miss: client {0} key {1}", client.getClientId(), cacheKey);
+            cache.addRevisioned(query);
+            return model;
+        }
+        RoleModel role = getRoleById(query.getRoles().iterator().next(), realm);
+        if (role == null) {
+            invalidations.add(cacheKey);
+            return getDelegate().getClientRole(realm, client, name);
+        }
+        return role;
+    }
+
+    @Override
+    public boolean removeRole(RealmModel realm, RoleModel role) {
+        invalidations.add(getRolesCacheKey(role.getContainer().getId()));
+        invalidations.add(getRoleByNameCacheKey(role.getContainer().getId(), role.getName()));
+        listInvalidations.add(role.getContainer().getId());
+        invalidations.add(role.getId());
+        return getDelegate().removeRole(realm, role);
+    }
+
+    @Override
+    public RoleModel getRoleById(String id, RealmModel realm) {
+        CachedRole cached = cache.get(id, CachedRole.class);
+        if (cached != null && !cached.getRealm().equals(realm.getId())) {
+            cached = null;
+        }
+
+        if (cached == null) {
+            Long loaded = cache.getCurrentRevision(id);
+            RoleModel model = getDelegate().getRoleById(id, realm);
+            if (model == null) return null;
+            if (invalidations.contains(id)) return model;
+            if (model.getContainer() instanceof ClientModel) {
+                cached = new CachedClientRole(loaded, ((ClientModel) model.getContainer()).getId(), model, realm);
+            } else {
+                cached = new CachedRealmRole(loaded, model, realm);
+            }
+            cache.addRevisioned(cached);
+
+        } else if (invalidations.contains(id)) {
+            return getDelegate().getRoleById(id, realm);
+        } else if (managedRoles.containsKey(id)) {
+            return managedRoles.get(id);
+        }
+        RoleAdapter adapter = new RoleAdapter(cached,this, realm);
+        managedRoles.put(id, adapter);
+        return adapter;
+    }
+
+    @Override
+    public GroupModel getGroupById(String id, RealmModel realm) {
+        CachedGroup cached = cache.get(id, CachedGroup.class);
+        if (cached != null && !cached.getRealm().equals(realm.getId())) {
+            cached = null;
+        }
+
+        if (cached == null) {
+            Long loaded = cache.getCurrentRevision(id);
+            GroupModel model = getDelegate().getGroupById(id, realm);
+            if (model == null) return null;
+            if (invalidations.contains(id)) return model;
+            cached = new CachedGroup(loaded, realm, model);
+            cache.addRevisioned(cached);
+
+        } else if (invalidations.contains(id)) {
+            return getDelegate().getGroupById(id, realm);
+        } else if (managedGroups.containsKey(id)) {
+            return managedGroups.get(id);
+        }
+        GroupAdapter adapter = new GroupAdapter(cached, this, session, realm);
+        managedGroups.put(id, adapter);
+        return adapter;
+    }
+
+    @Override
+    public ClientModel getClientById(String id, RealmModel realm) {
+        CachedClient cached = cache.get(id, CachedClient.class);
+        if (cached != null && !cached.getRealm().equals(realm.getId())) {
+            cached = null;
+        }
+        if (cached != null) {
+            logger.tracev("client by id cache hit: {0}", cached.getClientId());
+        }
+
+        if (cached == null) {
+            Long loaded = cache.getCurrentRevision(id);
+            ClientModel model = getDelegate().getClientById(id, realm);
+            if (model == null) return null;
+            if (invalidations.contains(id)) return model;
+            cached = new CachedClient(loaded, realm, model);
+            logger.tracev("adding client by id cache miss: {0}", cached.getClientId());
+            cache.addRevisioned(cached);
+        } else if (invalidations.contains(id)) {
+            return getDelegate().getClientById(id, realm);
+        } else if (managedApplications.containsKey(id)) {
+            return managedApplications.get(id);
+        }
+        ClientAdapter adapter = new ClientAdapter(realm, cached, this, null);
+        managedApplications.put(id, adapter);
+        return adapter;
+    }
+
+    @Override
+    public ClientModel getClientByClientId(String clientId, RealmModel realm) {
+        String cacheKey = getClientByClientIdCacheKey(clientId, realm);
+        ClientListQuery query = cache.get(cacheKey, ClientListQuery.class);
+        String id = null;
+
+        if (query != null) {
+            logger.tracev("client by name cache hit: {0}", clientId);
+        }
+
+        if (query == null) {
+            Long loaded = cache.getCurrentRevision(cacheKey);
+            ClientModel model = getDelegate().getClientByClientId(clientId, realm);
+            if (model == null) return null;
+            if (invalidations.contains(model.getId())) return model;
+            id = model.getId();
+            query = new ClientListQuery(loaded, cacheKey, realm, id);
+            logger.tracev("adding client by name cache miss: {0}", clientId);
+            cache.addRevisioned(query);
+        } else if (invalidations.contains(cacheKey)) {
+            return getDelegate().getClientByClientId(clientId, realm);
+        } else {
+            id = query.getClients().iterator().next();
+            if (invalidations.contains(id)) {
+                return getDelegate().getClientByClientId(clientId, realm);
+            }
+        }
+        return getClientById(id, realm);
+    }
+
+    public String getClientByClientIdCacheKey(String clientId, RealmModel realm) {
+        return realm.getId() + ".client.query.by.clientId." + clientId;
+    }
+
+    @Override
+    public ClientTemplateModel getClientTemplateById(String id, RealmModel realm) {
+        CachedClientTemplate cached = cache.get(id, CachedClientTemplate.class);
+        if (cached != null && !cached.getRealm().equals(realm.getId())) {
+            cached = null;
+        }
+
+        if (cached == null) {
+            Long loaded = cache.getCurrentRevision(id);
+            ClientTemplateModel model = getDelegate().getClientTemplateById(id, realm);
+            if (model == null) return null;
+            if (invalidations.contains(id)) return model;
+            cached = new CachedClientTemplate(loaded, realm, model);
+            cache.addRevisioned(cached);
+        } else if (invalidations.contains(id)) {
+            return getDelegate().getClientTemplateById(id, realm);
+        } else if (managedClientTemplates.containsKey(id)) {
+            return managedClientTemplates.get(id);
+        }
+        ClientTemplateModel adapter = new ClientTemplateAdapter(realm, cached, this);
+        managedClientTemplates.put(id, adapter);
+        return adapter;
+    }
+
+}
diff --git a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/StreamRealmCache.java b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/StreamRealmCache.java
new file mode 100755
index 0000000..452eee4
--- /dev/null
+++ b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/StreamRealmCache.java
@@ -0,0 +1,304 @@
+/*
+ * 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.models.cache.infinispan;
+
+import org.infinispan.Cache;
+import org.infinispan.notifications.Listener;
+import org.infinispan.notifications.cachelistener.annotation.CacheEntriesEvicted;
+import org.infinispan.notifications.cachelistener.annotation.CacheEntryInvalidated;
+import org.infinispan.notifications.cachelistener.event.CacheEntriesEvictedEvent;
+import org.infinispan.notifications.cachelistener.event.CacheEntryInvalidatedEvent;
+import org.jboss.logging.Logger;
+import org.keycloak.models.cache.infinispan.entities.CachedClient;
+import org.keycloak.models.cache.infinispan.entities.CachedClientTemplate;
+import org.keycloak.models.cache.infinispan.entities.CachedGroup;
+import org.keycloak.models.cache.infinispan.entities.CachedRealm;
+import org.keycloak.models.cache.infinispan.entities.CachedRole;
+import org.keycloak.models.cache.infinispan.entities.Revisioned;
+import org.keycloak.models.cache.infinispan.stream.ClientQueryPredicate;
+import org.keycloak.models.cache.infinispan.stream.ClientTemplateQueryPredicate;
+import org.keycloak.models.cache.infinispan.stream.GroupQueryPredicate;
+import org.keycloak.models.cache.infinispan.stream.HasRolePredicate;
+import org.keycloak.models.cache.infinispan.stream.InClientPredicate;
+import org.keycloak.models.cache.infinispan.stream.InRealmPredicate;
+import org.keycloak.models.cache.infinispan.stream.RealmQueryPredicate;
+import org.keycloak.models.cache.infinispan.stream.RoleQueryPredicate;
+
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.Set;
+import java.util.function.Predicate;
+
+/**
+ * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
+ */
+@Listener
+public class StreamRealmCache {
+
+    protected static final Logger logger = Logger.getLogger(StreamRealmCache.class);
+
+    protected final Cache<String, Long> revisions;
+    protected final Cache<String, Revisioned> cache;
+
+    public StreamRealmCache(Cache<String, Revisioned> cache, Cache<String, Long> revisions) {
+        this.cache = cache;
+        this.cache.addListener(this);
+        this.revisions = revisions;
+    }
+
+    public Cache<String, Revisioned> getCache() {
+        return cache;
+    }
+
+    public Cache<String, Long> getRevisions() {
+        return revisions;
+    }
+
+    public Long getCurrentRevision(String id) {
+        Long revision = revisions.get(id);
+        if (revision == null) return UpdateCounter.current();
+        return revision;
+    }
+
+    public void endRevisionBatch() {
+        try {
+            revisions.endBatch(true);
+        } catch (Exception e) {
+        }
+
+    }
+
+    public <T> T get(String id, Class<T> type) {
+        Revisioned o = (Revisioned)cache.get(id);
+        if (o == null) {
+            return null;
+        }
+        Long rev = revisions.get(id);
+        if (rev == null) {
+            logger.tracev("get() missing rev");
+            return null;
+        }
+        long oRev = o.getRevision() == null ? -1L : o.getRevision().longValue();
+        if (rev > oRev) {
+            logger.tracev("get() rev: {0} o.rev: {1}", rev.longValue(), oRev);
+            return null;
+        }
+        return o != null && type.isInstance(o) ? type.cast(o) : null;
+    }
+
+    public Object invalidateObject(String id) {
+        Revisioned removed = (Revisioned)cache.remove(id);
+        long next = UpdateCounter.next();
+        Object rev = revisions.put(id, next);
+        return removed;
+    }
+
+    public void addRevisioned(Revisioned object) {
+        //startRevisionBatch();
+        String id = object.getId();
+        try {
+            //revisions.getAdvancedCache().lock(id);
+            Long rev = revisions.get(id);
+            if (rev == null) {
+                if (id.endsWith("realm.clients")) logger.trace("addRevisioned rev == null realm.clients");
+                rev = UpdateCounter.current();
+                revisions.put(id, rev);
+            }
+            revisions.startBatch();
+            if (!revisions.getAdvancedCache().lock(id)) {
+                logger.trace("Could not obtain version lock");
+            }
+            rev = revisions.get(id);
+            if (rev == null) {
+                if (id.endsWith("realm.clients")) logger.trace("addRevisioned rev2 == null realm.clients");
+                return;
+            }
+            if (rev.equals(object.getRevision())) {
+                if (id.endsWith("realm.clients")) logger.tracev("adding Object.revision {0} rev {1}", object.getRevision(), rev);
+                cache.putForExternalRead(id, object);
+                return;
+            }
+            if (rev > object.getRevision()) { // revision is ahead, don't cache
+                if (id.endsWith("realm.clients")) logger.trace("addRevisioned revision is ahead realm.clients");
+                return;
+            }
+            // revisions cache has a lower value than the object.revision, so update revision and add it to cache
+            if (id.endsWith("realm.clients")) logger.tracev("adding Object.revision {0} rev {1}", object.getRevision(), rev);
+            revisions.put(id, object.getRevision());
+            cache.putForExternalRead(id, object);
+        } finally {
+            endRevisionBatch();
+        }
+
+    }
+
+
+
+    public void clear() {
+        cache.clear();
+    }
+
+    public void realmInvalidation(String id, Set<String> invalidations) {
+        Predicate<Map.Entry<String, Revisioned>> predicate = getRealmInvalidationPredicate(id);
+        addInvalidations(predicate, invalidations);
+    }
+
+    public Predicate<Map.Entry<String, Revisioned>> getRealmInvalidationPredicate(String id) {
+        return RealmQueryPredicate.create().realm(id);
+    }
+
+    public void clientInvalidation(String id, Set<String> invalidations) {
+        addInvalidations(getClientInvalidationPredicate(id), invalidations);
+    }
+
+    public Predicate<Map.Entry<String, Revisioned>> getClientInvalidationPredicate(String id) {
+        return ClientQueryPredicate.create().client(id);
+    }
+
+    public void roleInvalidation(String id, Set<String> invalidations) {
+        addInvalidations(getRoleInvalidationPredicate(id), invalidations);
+
+    }
+
+    public Predicate<Map.Entry<String, Revisioned>> getRoleInvalidationPredicate(String id) {
+        return HasRolePredicate.create().role(id);
+    }
+
+    public void groupInvalidation(String id, Set<String> invalidations) {
+        addInvalidations(getGroupInvalidationPredicate(id), invalidations);
+
+    }
+
+    public Predicate<Map.Entry<String, Revisioned>> getGroupInvalidationPredicate(String id) {
+        return GroupQueryPredicate.create().group(id);
+    }
+
+    public void clientTemplateInvalidation(String id, Set<String> invalidations) {
+        addInvalidations(getClientTemplateInvalidationPredicate(id), invalidations);
+
+    }
+
+    public Predicate<Map.Entry<String, Revisioned>> getClientTemplateInvalidationPredicate(String id) {
+        return ClientTemplateQueryPredicate.create().template(id);
+    }
+
+    public void realmRemoval(String id, Set<String> invalidations) {
+        Predicate<Map.Entry<String, Revisioned>> predicate = getRealmRemovalPredicate(id);
+        addInvalidations(predicate, invalidations);
+    }
+
+    public Predicate<Map.Entry<String, Revisioned>> getRealmRemovalPredicate(String id) {
+        Predicate<Map.Entry<String, Revisioned>> predicate = null;
+        predicate = RealmQueryPredicate.create().realm(id)
+                .or(InRealmPredicate.create().realm(id));
+        return predicate;
+    }
+
+    public void clientAdded(String realmId, String id, Set<String> invalidations) {
+        addInvalidations(getClientAddedPredicate(realmId), invalidations);
+    }
+
+    public Predicate<Map.Entry<String, Revisioned>> getClientAddedPredicate(String realmId) {
+        return ClientQueryPredicate.create().inRealm(realmId);
+    }
+
+    public void clientRemoval(String realmId, String id, Set<String> invalidations) {
+        Predicate<Map.Entry<String, Revisioned>> predicate = null;
+        predicate = getClientRemovalPredicate(realmId, id);
+        addInvalidations(predicate, invalidations);
+    }
+
+    public Predicate<Map.Entry<String, Revisioned>> getClientRemovalPredicate(String realmId, String id) {
+        Predicate<Map.Entry<String, Revisioned>> predicate;
+        predicate = ClientQueryPredicate.create().inRealm(realmId)
+                .or(ClientQueryPredicate.create().client(id))
+                .or(InClientPredicate.create().client(id));
+        return predicate;
+    }
+
+    public void roleRemoval(String id, Set<String> invalidations) {
+        addInvalidations(getRoleRemovalPredicate(id), invalidations);
+
+    }
+
+    public Predicate<Map.Entry<String, Revisioned>> getRoleRemovalPredicate(String id) {
+        return getRoleInvalidationPredicate(id);
+    }
+
+    public void addInvalidations(Predicate<Map.Entry<String, Revisioned>> predicate, Set<String> invalidations) {
+        Iterator<Map.Entry<String, Revisioned>> it = getEntryIterator(predicate);
+        while (it.hasNext()) {
+            invalidations.add(it.next().getKey());
+        }
+    }
+
+    private Iterator<Map.Entry<String, Revisioned>> getEntryIterator(Predicate<Map.Entry<String, Revisioned>> predicate) {
+        return cache
+                .entrySet()
+                .stream()
+                .filter(predicate).iterator();
+    }
+
+    @CacheEntryInvalidated
+    public void cacheInvalidated(CacheEntryInvalidatedEvent<String, Object> event) {
+        if (!event.isPre()) {
+            Object object = event.getValue();
+            if (object != null) {
+                Predicate<Map.Entry<String, Revisioned>> predicate = getInvalidationPredicate(object);
+                if (predicate != null) runEvictions(predicate);
+            }
+        }
+    }
+
+    @CacheEntriesEvicted
+    public void cacheEvicted(CacheEntriesEvictedEvent<String, Object> event) {
+        if (!event.isPre())
+        for (Object object : event.getEntries().values()) {
+            Predicate<Map.Entry<String, Revisioned>> predicate = getInvalidationPredicate(object);
+            if (predicate != null) runEvictions(predicate);
+        }
+    }
+
+    public void runEvictions(Predicate<Map.Entry<String, Revisioned>> current) {
+        Set<String> evictions = new HashSet<>();
+        addInvalidations(current, evictions);
+        for (String key : evictions) cache.evict(key);
+    }
+
+    protected Predicate<Map.Entry<String, Revisioned>> getInvalidationPredicate(Object object) {
+        if (object instanceof CachedRealm) {
+            CachedRealm cached = (CachedRealm)object;
+            return getRealmRemovalPredicate(cached.getId());
+        } else if (object instanceof CachedClient) {
+            CachedClient cached = (CachedClient)object;
+            Predicate<Map.Entry<String, Revisioned>> predicate = getClientRemovalPredicate(cached.getRealm(), cached.getId());
+            return predicate;
+        } else if (object instanceof CachedRole) {
+            CachedRole cached = (CachedRole)object;
+            return getRoleRemovalPredicate(cached.getId());
+        } else if (object instanceof CachedGroup) {
+            CachedGroup cached = (CachedGroup)object;
+            return getGroupInvalidationPredicate(cached.getId());
+        } else if (object instanceof CachedClientTemplate) {
+            CachedClientTemplate cached = (CachedClientTemplate)object;
+            return getClientTemplateInvalidationPredicate(cached.getId());
+        }
+        return null;
+    }
+}
diff --git a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/UserAdapter.java b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/UserAdapter.java
index b8945bd..6421ca2 100755
--- a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/UserAdapter.java
+++ b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/UserAdapter.java
@@ -19,7 +19,7 @@ package org.keycloak.models.cache.infinispan;
 
 import org.keycloak.models.*;
 import org.keycloak.models.cache.CacheUserProvider;
-import org.keycloak.models.cache.entities.CachedUser;
+import org.keycloak.models.cache.infinispan.entities.CachedUser;
 import org.keycloak.models.utils.KeycloakModelUtils;
 
 import java.util.*;
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 299da00..1fe4945 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,4 +16,3 @@
 #
 
 org.keycloak.connections.infinispan.DefaultInfinispanConnectionProviderFactory
-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 40ced18..a2b04e7 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,4 +16,3 @@
 #
 
 org.keycloak.models.cache.infinispan.InfinispanCacheRealmProviderFactory
-org.keycloak.models.cache.infinispan.locking.LockingCacheRealmProviderFactory
\ No newline at end of file
diff --git a/model/infinispan/src/test/java/org/keycloak/models/sessions/infinispan/initializer/ClusteredCacheBehaviorTest.java b/model/infinispan/src/test/java/org/keycloak/models/sessions/infinispan/initializer/ClusteredCacheBehaviorTest.java
new file mode 100755
index 0000000..bc8b4a3
--- /dev/null
+++ b/model/infinispan/src/test/java/org/keycloak/models/sessions/infinispan/initializer/ClusteredCacheBehaviorTest.java
@@ -0,0 +1,112 @@
+package org.keycloak.models.sessions.infinispan.initializer;
+
+import org.infinispan.Cache;
+import org.infinispan.configuration.cache.CacheMode;
+import org.infinispan.configuration.cache.Configuration;
+import org.infinispan.configuration.cache.ConfigurationBuilder;
+import org.infinispan.configuration.global.GlobalConfigurationBuilder;
+import org.infinispan.manager.DefaultCacheManager;
+import org.infinispan.manager.EmbeddedCacheManager;
+import org.infinispan.notifications.Listener;
+import org.infinispan.notifications.cachelistener.annotation.CacheEntriesEvicted;
+import org.infinispan.notifications.cachelistener.annotation.CacheEntryInvalidated;
+import org.infinispan.notifications.cachelistener.annotation.CacheEntryRemoved;
+import org.infinispan.notifications.cachelistener.event.CacheEntriesEvictedEvent;
+import org.infinispan.notifications.cachelistener.event.CacheEntryInvalidatedEvent;
+import org.infinispan.notifications.cachelistener.event.CacheEntryRemovedEvent;
+import org.junit.Ignore;
+import org.junit.Test;
+import org.keycloak.connections.infinispan.InfinispanConnectionProvider;
+
+/**
+ * Just a simple test to see how distributed caches work
+ *
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+@Ignore
+public class ClusteredCacheBehaviorTest {
+    public EmbeddedCacheManager createManager() {
+        GlobalConfigurationBuilder gcb = new GlobalConfigurationBuilder();
+
+        boolean clustered = true;
+        boolean async = false;
+        boolean allowDuplicateJMXDomains = true;
+
+        if (clustered) {
+            gcb.transport().defaultTransport();
+        }
+        gcb.globalJmxStatistics().allowDuplicateDomains(allowDuplicateJMXDomains);
+
+        EmbeddedCacheManager cacheManager = new DefaultCacheManager(gcb.build());
+
+
+        ConfigurationBuilder invalidationConfigBuilder = new ConfigurationBuilder();
+        if (clustered) {
+            invalidationConfigBuilder.clustering().cacheMode(async ? CacheMode.INVALIDATION_ASYNC : CacheMode.INVALIDATION_SYNC);
+        }
+        Configuration invalidationCacheConfiguration = invalidationConfigBuilder.build();
+
+        cacheManager.defineConfiguration(InfinispanConnectionProvider.REALM_CACHE_NAME, invalidationCacheConfiguration);
+        return cacheManager;
+
+    }
+
+    @Listener
+    public static class CacheListener {
+        String name;
+
+        public CacheListener(String name) {
+            this.name = name;
+        }
+
+
+
+        @CacheEntryRemoved
+        public void removed(CacheEntryRemovedEvent<String, Object> event) {
+            System.out.println("Listener '" + name + "' entry removed  isPre: " + event.isPre());
+        }
+
+        @CacheEntryInvalidated
+        public void removed(CacheEntryInvalidatedEvent<String, Object> event) {
+            System.out.println("Listener '" + name + "' entry invalidated: isPre: " + event.isPre());
+        }
+
+        @CacheEntriesEvicted
+        public void evicted(CacheEntriesEvictedEvent<String, Object> event) {
+            System.out.println("Listener '" + name + "' entry evicted isPre: " + event.isPre());
+
+        }
+
+    }
+
+    @Test
+    public void testListener() throws Exception {
+        EmbeddedCacheManager node1 = createManager();
+        EmbeddedCacheManager node2 = createManager();
+        Cache<String, Object> node1Cache = node1.getCache(InfinispanConnectionProvider.REALM_CACHE_NAME);
+        node1Cache.addListener(new CacheListener("node1"));
+        Cache<String, Object> node2Cache = node2.getCache(InfinispanConnectionProvider.REALM_CACHE_NAME);
+        node2Cache.addListener(new CacheListener("node2"));
+
+        System.out.println("node1 create entry");
+        node1Cache.put("key", "node1");
+        System.out.println("node2 create entry");
+        node2Cache.put("key", "node2");
+        System.out.println("node1 remove entry");
+        node1Cache.remove("key");
+        System.out.println("node2 remove entry");
+        node2Cache.remove("key");
+        System.out.println("node2 put entry");
+        node2Cache.put("key", "node2");
+        System.out.println("node2 evict entry");
+        node2Cache.evict("key");
+        System.out.println("node1/node2 putExternal entry");
+        node1Cache.putForExternalRead("key", "common");
+        node2Cache.putForExternalRead("key", "common");
+        System.out.println("node2 remove entry");
+        node2Cache.remove("key");
+
+
+    }
+}
diff --git a/model/jpa/src/main/java/org/keycloak/models/jpa/ClientAdapter.java b/model/jpa/src/main/java/org/keycloak/models/jpa/ClientAdapter.java
index 311540b..115d4dd 100755
--- a/model/jpa/src/main/java/org/keycloak/models/jpa/ClientAdapter.java
+++ b/model/jpa/src/main/java/org/keycloak/models/jpa/ClientAdapter.java
@@ -612,31 +612,17 @@ public class ClientAdapter implements ClientModel {
 
     @Override
     public RoleModel getRole(String name) {
-        TypedQuery<RoleEntity> query = em.createNamedQuery("getClientRoleByName", RoleEntity.class);
-        query.setParameter("name", name);
-        query.setParameter("client", entity);
-        List<RoleEntity> roles = query.getResultList();
-        if (roles.size() == 0) return null;
-        return new RoleAdapter(realm, em, roles.get(0));
+        return session.realms().getClientRole(realm, this, name);
     }
 
     @Override
     public RoleModel addRole(String name) {
-        return this.addRole(KeycloakModelUtils.generateId(), name);
+        return session.realms().addClientRole(realm, this, name);
     }
 
     @Override
     public RoleModel addRole(String id, String name) {
-        RoleEntity roleEntity = new RoleEntity();
-        roleEntity.setId(id);
-        roleEntity.setName(name);
-        roleEntity.setClient(entity);
-        roleEntity.setClientRole(true);
-        roleEntity.setRealmId(realm.getId());
-        entity.getRoles().add(roleEntity);
-        em.persist(roleEntity);
-        em.flush();
-        return new RoleAdapter(realm, em, roleEntity);
+        return session.realms().addClientRole(realm, this, id, name);
     }
 
     @Override
@@ -650,7 +636,6 @@ public class ClientAdapter implements ClientModel {
         RoleEntity role = RoleAdapter.toRoleEntity(roleModel, em);
         if (!role.isClientRole()) return false;
 
-        entity.getRoles().remove(role);
         entity.getDefaultRoles().remove(role);
         String compositeRoleTable = JpaUtils.getTableNameForNativeQuery("COMPOSITE_ROLE", em);
         em.createNativeQuery("delete from " + compositeRoleTable + " where CHILD_ROLE = :role").setParameter("role", role).executeUpdate();
@@ -666,22 +651,7 @@ public class ClientAdapter implements ClientModel {
 
     @Override
     public Set<RoleModel> getRoles() {
-        Set<RoleModel> list = new HashSet<RoleModel>();
-        /*
-        Collection<RoleEntity> roles = entity.getRoles();
-        if (roles == null) return list;
-        for (RoleEntity entity : roles) {
-            list.add(new RoleAdapter(realm, em, entity));
-        }
-        */
-        TypedQuery<RoleEntity> query = em.createNamedQuery("getClientRoles", RoleEntity.class);
-        query.setParameter("client", entity);
-        List<RoleEntity> roles = query.getResultList();
-        for (RoleEntity roleEntity : roles) {
-             list.add(new RoleAdapter(realm, em, roleEntity));
-        }
-        return list;
-
+        return session.realms().getClientRoles(realm, this);
     }
 
     @Override
@@ -731,10 +701,10 @@ public class ClientAdapter implements ClientModel {
     }
 
     @Override
-    public void updateDefaultRoles(String[] defaultRoles) {
+    public void updateDefaultRoles(String... defaultRoles) {
         Collection<RoleEntity> entities = entity.getDefaultRoles();
         Set<String> already = new HashSet<String>();
-        List<RoleEntity> remove = new ArrayList<RoleEntity>();
+        List<RoleEntity> remove = new ArrayList<>();
         for (RoleEntity rel : entities) {
             if (!contains(rel.getName(), defaultRoles)) {
                 remove.add(rel);
@@ -755,6 +725,24 @@ public class ClientAdapter implements ClientModel {
     }
 
     @Override
+    public void removeDefaultRoles(String... defaultRoles) {
+        Collection<RoleEntity> entities = entity.getDefaultRoles();
+        List<RoleEntity> remove = new ArrayList<RoleEntity>();
+        for (RoleEntity rel : entities) {
+            if (contains(rel.getName(), defaultRoles)) {
+                remove.add(rel);
+            }
+        }
+        for (RoleEntity entity : remove) {
+            entities.remove(entity);
+        }
+        em.flush();
+    }
+
+
+
+
+    @Override
     public int getNodeReRegistrationTimeout() {
         return entity.getNodeReRegistrationTimeout();
     }
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 5466a77..fc10217 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,7 @@ 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="getClientIdsByRealm", query="select client.id from ClientEntity client where client.realm.id = :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"),
 })
@@ -156,9 +157,6 @@ public class ClientEntity {
     @Column(name="NODE_REREG_TIMEOUT")
     private int nodeReRegistrationTimeout;
 
-    @OneToMany(fetch = FetchType.LAZY, cascade ={CascadeType.REMOVE}, mappedBy = "client")
-    Collection<RoleEntity> roles = new ArrayList<RoleEntity>();
-
     @OneToMany(fetch = FetchType.LAZY, cascade ={CascadeType.REMOVE}, orphanRemoval = true)
     @JoinTable(name="CLIENT_DEFAULT_ROLES", joinColumns = { @JoinColumn(name="CLIENT_ID")}, inverseJoinColumns = { @JoinColumn(name="ROLE_ID")})
     Collection<RoleEntity> defaultRoles = new ArrayList<RoleEntity>();
@@ -353,14 +351,6 @@ public class ClientEntity {
         this.managementUrl = managementUrl;
     }
 
-    public Collection<RoleEntity> getRoles() {
-        return roles;
-    }
-
-    public void setRoles(Collection<RoleEntity> roles) {
-        this.roles = roles;
-    }
-
     public Collection<RoleEntity> getDefaultRoles() {
         return defaultRoles;
     }
diff --git a/model/jpa/src/main/java/org/keycloak/models/jpa/entities/GroupEntity.java b/model/jpa/src/main/java/org/keycloak/models/jpa/entities/GroupEntity.java
index 9eadbc8..0b89c87 100755
--- a/model/jpa/src/main/java/org/keycloak/models/jpa/entities/GroupEntity.java
+++ b/model/jpa/src/main/java/org/keycloak/models/jpa/entities/GroupEntity.java
@@ -39,6 +39,7 @@ import java.util.Collection;
  */
 @NamedQueries({
         @NamedQuery(name="getAllGroupsByRealm", query="select u from GroupEntity u where u.realm = :realm order by u.name"),
+        @NamedQuery(name="getAllGroupIdsByRealm", query="select u.id from GroupEntity u where u.realm.id = :realm order by u.name"),
         @NamedQuery(name="getGroupById", query="select u from GroupEntity u where u.id = :id and u.realm = :realm"),
         @NamedQuery(name="getGroupIdsByParent", query="select u.id from GroupEntity u where u.parent = :parent"),
         @NamedQuery(name="getGroupCount", query="select count(u) from GroupEntity u where u.realm = :realm"),
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 684d102..052ce6e 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
@@ -49,8 +49,8 @@ import java.util.Set;
 @Table(name="REALM")
 @Entity
 @NamedQueries({
-        @NamedQuery(name="getAllRealms", query="select realm from RealmEntity realm"),
-        @NamedQuery(name="getRealmByName", query="select realm from RealmEntity realm where realm.name = :name"),
+        @NamedQuery(name="getAllRealmIds", query="select realm.id from RealmEntity realm"),
+        @NamedQuery(name="getRealmIdByName", query="select realm.id from RealmEntity realm where realm.name = :name"),
 })
 public class RealmEntity {
     @Id
@@ -147,15 +147,9 @@ public class RealmEntity {
     @OneToMany(cascade ={CascadeType.REMOVE}, orphanRemoval = true, mappedBy = "realm")
     Collection<UserFederationMapperEntity> userFederationMappers = new ArrayList<UserFederationMapperEntity>();
 
-    @OneToMany(fetch = FetchType.LAZY, cascade ={CascadeType.REMOVE}, orphanRemoval = true, mappedBy="realm")
-    Collection<ClientEntity> clients = new ArrayList<>();
-
     @OneToMany(fetch = FetchType.LAZY, cascade ={CascadeType.REMOVE}, orphanRemoval = true, mappedBy = "realm")
     Collection<ClientTemplateEntity> clientTemplates = new ArrayList<>();
 
-    @OneToMany(fetch = FetchType.LAZY, cascade ={CascadeType.REMOVE}, mappedBy = "realm")
-    Collection<RoleEntity> roles = new ArrayList<RoleEntity>();
-
     @ElementCollection
     @MapKeyColumn(name="NAME")
     @Column(name="VALUE")
@@ -421,29 +415,6 @@ public class RealmEntity {
     public void setRequiredCredentials(Collection<RequiredCredentialEntity> requiredCredentials) {
         this.requiredCredentials = requiredCredentials;
     }
-    public Collection<ClientEntity> getClients() {
-        return clients;
-    }
-
-    public void setClients(Collection<ClientEntity> clients) {
-        this.clients = clients;
-    }
-
-    public Collection<RoleEntity> getRoles() {
-        return roles;
-    }
-
-    public void setRoles(Collection<RoleEntity> roles) {
-        this.roles = roles;
-    }
-
-    public void addRole(RoleEntity role) {
-        if (roles == null) {
-            roles = new ArrayList<RoleEntity>();
-        }
-        roles.add(role);
-    }
-
     public Map<String, String> getSmtpConfig() {
         return smtpConfig;
     }
diff --git a/model/jpa/src/main/java/org/keycloak/models/jpa/entities/RoleEntity.java b/model/jpa/src/main/java/org/keycloak/models/jpa/entities/RoleEntity.java
index b94a743..3d1cae4 100755
--- a/model/jpa/src/main/java/org/keycloak/models/jpa/entities/RoleEntity.java
+++ b/model/jpa/src/main/java/org/keycloak/models/jpa/entities/RoleEntity.java
@@ -49,8 +49,13 @@ import java.util.Collection;
 })
 @NamedQueries({
         @NamedQuery(name="getClientRoles", query="select role from RoleEntity role where role.client = :client"),
+        @NamedQuery(name="getClientRoleIds", query="select role.id from RoleEntity role where role.client.id = :client"),
         @NamedQuery(name="getClientRoleByName", query="select role from RoleEntity role where role.name = :name and role.client = :client"),
-        @NamedQuery(name="getRealmRoleByName", query="select role from RoleEntity role where role.clientRole = false and role.name = :name and role.realm = :realm")
+        @NamedQuery(name="getClientRoleIdByName", query="select role.id from RoleEntity role where role.name = :name and role.client.id = :client"),
+        @NamedQuery(name="getRealmRoles", query="select role from RoleEntity role where role.clientRole = false and role.realm = :realm"),
+        @NamedQuery(name="getRealmRoleIds", query="select role.id from RoleEntity role where role.clientRole = false and role.realm.id = :realm"),
+        @NamedQuery(name="getRealmRoleByName", query="select role from RoleEntity role where role.clientRole = false and role.name = :name and role.realm = :realm"),
+        @NamedQuery(name="getRealmRoleIdByName", query="select role.id from RoleEntity role where role.clientRole = false and role.name = :name and role.realm.id = :realm")
 })
 
 public class RoleEntity {
@@ -105,6 +110,8 @@ public class RoleEntity {
         this.realmId = realmId;
     }
 
+
+
     public String getName() {
         return name;
     }
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 ce245de..6080d72 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
@@ -18,6 +18,7 @@
 package org.keycloak.models.jpa;
 
 import org.jboss.logging.Logger;
+import org.keycloak.connections.jpa.util.JpaUtils;
 import org.keycloak.migration.MigrationModel;
 import org.keycloak.models.ClientModel;
 import org.keycloak.models.ClientTemplateModel;
@@ -25,6 +26,7 @@ import org.keycloak.models.GroupModel;
 import org.keycloak.models.KeycloakSession;
 import org.keycloak.models.RealmModel;
 import org.keycloak.models.RealmProvider;
+import org.keycloak.models.RoleContainerModel;
 import org.keycloak.models.RoleModel;
 import org.keycloak.models.jpa.entities.ClientEntity;
 import org.keycloak.models.jpa.entities.ClientTemplateEntity;
@@ -36,8 +38,11 @@ import org.keycloak.models.utils.KeycloakModelUtils;
 import javax.persistence.EntityManager;
 import javax.persistence.TypedQuery;
 import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashSet;
 import java.util.LinkedList;
 import java.util.List;
+import java.util.Set;
 
 /**
  * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
@@ -90,25 +95,27 @@ public class JpaRealmProvider implements RealmProvider {
 
     @Override
     public List<RealmModel> getRealms() {
-        TypedQuery<RealmEntity> query = em.createNamedQuery("getAllRealms", RealmEntity.class);
-        List<RealmEntity> entities = query.getResultList();
+        TypedQuery<String> query = em.createNamedQuery("getAllRealmIds", String.class);
+        List<String> entities = query.getResultList();
         List<RealmModel> realms = new ArrayList<RealmModel>();
-        for (RealmEntity entity : entities) {
-            realms.add(new RealmAdapter(session, em, entity));
+        for (String id : entities) {
+            RealmModel realm = session.realms().getRealm(id);
+            if (realm != null) realms.add(realm);
+
         }
         return realms;
     }
 
     @Override
     public RealmModel getRealmByName(String name) {
-        TypedQuery<RealmEntity> query = em.createNamedQuery("getRealmByName", RealmEntity.class);
+        TypedQuery<String> query = em.createNamedQuery("getRealmIdByName", String.class);
         query.setParameter("name", name);
-        List<RealmEntity> entities = query.getResultList();
+        List<String> entities = query.getResultList();
         if (entities.size() == 0) return null;
         if (entities.size() > 1) throw new IllegalStateException("Should not be more than one realm with same name");
-        RealmEntity realm = query.getResultList().get(0);
-        if (realm == null) return null;
-        return new RealmAdapter(session, em, realm);
+        String id = query.getResultList().get(0);
+
+        return session.realms().getRealm(id);
     }
 
     @Override
@@ -126,30 +133,25 @@ public class JpaRealmProvider implements RealmProvider {
         num = em.createNamedQuery("deleteGroupsByRealm")
                 .setParameter("realm", realm).executeUpdate();
 
-        TypedQuery<ClientEntity> query = em.createNamedQuery("getClientsByRealm", ClientEntity.class);
-        query.setParameter("realm", realm);
-        List<ClientEntity> clients = query.getResultList();
-        for (ClientEntity a : clients) {
-            adapter.removeClient(a.getId());
-        }
-        /*
-        for (ClientEntity a : new LinkedList<>(realm.getClients())) {
-            adapter.removeClient(a.getId());
+        TypedQuery<String> query = em.createNamedQuery("getClientIdsByRealm", String.class);
+        query.setParameter("realm", realm.getId());
+        List<String> clients = query.getResultList();
+        for (String client : clients) {
+            session.realms().removeClient(client, adapter);
         }
-        */
+
         for (ClientTemplateEntity a : new LinkedList<>(realm.getClientTemplates())) {
             adapter.removeClientTemplate(a.getId());
         }
 
+        for (RoleModel role : adapter.getRoles()) {
+            session.realms().removeRole(adapter, role);
+        }
+
         em.remove(realm);
 
         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;
     }
 
@@ -158,11 +160,117 @@ public class JpaRealmProvider implements RealmProvider {
     }
 
     @Override
+    public RoleModel addRealmRole(RealmModel realm, String name) {
+        return addRealmRole(realm, KeycloakModelUtils.generateId(), name);
+
+    }
+    @Override
+    public RoleModel addRealmRole(RealmModel realm, String id, String name) {
+        RoleEntity entity = new RoleEntity();
+        entity.setId(id);
+        entity.setName(name);
+        RealmEntity ref = em.getReference(RealmEntity.class, realm.getId());
+        entity.setRealm(ref);
+        entity.setRealmId(realm.getId());
+        em.persist(entity);
+        em.flush();
+        return new RoleAdapter(session, realm, em, entity);
+
+    }
+
+    @Override
+    public RoleModel getRealmRole(RealmModel realm, String name) {
+        TypedQuery<String> query = em.createNamedQuery("getRealmRoleIdByName", String.class);
+        query.setParameter("name", name);
+        query.setParameter("realm", realm.getId());
+        List<String> roles = query.getResultList();
+        if (roles.size() == 0) return null;
+        return session.realms().getRoleById(roles.get(0), realm);
+    }
+
+    @Override
+    public RoleModel addClientRole(RealmModel realm, ClientModel client, String name) {
+        return addClientRole(realm, client, KeycloakModelUtils.generateId(), name);
+    }
+    @Override
+    public RoleModel addClientRole(RealmModel realm, ClientModel client, String id, String name) {
+        ClientEntity clientEntity = em.getReference(ClientEntity.class, client.getId());
+        RoleEntity roleEntity = new RoleEntity();
+        roleEntity.setId(id);
+        roleEntity.setName(name);
+        roleEntity.setClient(clientEntity);
+        roleEntity.setClientRole(true);
+        roleEntity.setRealmId(realm.getId());
+        em.persist(roleEntity);
+        em.flush();
+        return new RoleAdapter(session, realm, em, roleEntity);
+    }
+
+    @Override
+    public Set<RoleModel> getRealmRoles(RealmModel realm) {
+        TypedQuery<String> query = em.createNamedQuery("getRealmRoleIds", String.class);
+        query.setParameter("realm", realm.getId());
+        List<String> roles = query.getResultList();
+
+        if (roles.isEmpty()) return Collections.EMPTY_SET;
+        Set<RoleModel> list = new HashSet<RoleModel>();
+        for (String id : roles) {
+            list.add(session.realms().getRoleById(id, realm));
+        }
+        return Collections.unmodifiableSet(list);
+    }
+
+    @Override
+    public RoleModel getClientRole(RealmModel realm, ClientModel client, String name) {
+        TypedQuery<String> query = em.createNamedQuery("getClientRoleIdByName", String.class);
+        query.setParameter("name", name);
+        query.setParameter("client", client.getId());
+        List<String> roles = query.getResultList();
+        if (roles.size() == 0) return null;
+        return session.realms().getRoleById(roles.get(0), realm);
+    }
+
+
+    @Override
+    public Set<RoleModel> getClientRoles(RealmModel realm, ClientModel client) {
+        Set<RoleModel> list = new HashSet<RoleModel>();
+        TypedQuery<String> query = em.createNamedQuery("getClientRoleIds", String.class);
+        query.setParameter("client", client.getId());
+        List<String> roles = query.getResultList();
+        for (String id : roles) {
+            list.add(session.realms().getRoleById(id, realm));
+        }
+        return list;
+
+    }
+
+    @Override
+    public boolean removeRole(RealmModel realm, RoleModel role) {
+        session.users().preRemove(realm, role);
+        RoleEntity roleEntity = em.getReference(RoleEntity.class, role.getId());
+        RoleContainerModel container = role.getContainer();
+        if (container.getDefaultRoles().contains(role.getName())) {
+            container.removeDefaultRoles(role.getName());
+        }
+        String compositeRoleTable = JpaUtils.getTableNameForNativeQuery("COMPOSITE_ROLE", em);
+        em.createNativeQuery("delete from " + compositeRoleTable + " where CHILD_ROLE = :role").setParameter("role", roleEntity).executeUpdate();
+        em.createNamedQuery("deleteScopeMappingByRole").setParameter("role", roleEntity).executeUpdate();
+        em.createNamedQuery("deleteTemplateScopeMappingByRole").setParameter("role", roleEntity).executeUpdate();
+        em.createNamedQuery("deleteGroupRoleMappingsByRole").setParameter("roleId", roleEntity.getId()).executeUpdate();
+
+        em.remove(roleEntity);
+        em.flush();
+
+        return true;
+
+    }
+
+    @Override
     public RoleModel getRoleById(String id, RealmModel realm) {
         RoleEntity entity = em.find(RoleEntity.class, id);
         if (entity == null) return null;
         if (!realm.getId().equals(entity.getRealmId())) return null;
-        return new RoleAdapter(realm, em, entity);
+        return new RoleAdapter(session, realm, em, entity);
     }
 
     @Override
@@ -174,6 +282,52 @@ public class JpaRealmProvider implements RealmProvider {
     }
 
     @Override
+    public ClientModel addClient(RealmModel realm, String clientId) {
+        return addClient(realm, KeycloakModelUtils.generateId(), clientId);
+    }
+
+    @Override
+    public ClientModel addClient(RealmModel realm, String id, String clientId) {
+        if (clientId == null) {
+            clientId = id;
+        }
+        ClientEntity entity = new ClientEntity();
+        entity.setId(id);
+        entity.setClientId(clientId);
+        entity.setEnabled(true);
+        entity.setStandardFlowEnabled(true);
+        RealmEntity realmRef = em.getReference(RealmEntity.class, realm.getId());
+        entity.setRealm(realmRef);
+        em.persist(entity);
+        em.flush();
+        final ClientModel resource = new ClientAdapter(realm, em, session, entity);
+        em.flush();
+        session.getKeycloakSessionFactory().publish(new RealmModel.ClientCreationEvent() {
+            @Override
+            public ClientModel getCreatedClient() {
+                return resource;
+            }
+        });
+        return resource;
+    }
+
+    @Override
+    public List<ClientModel> getClients(RealmModel realm) {
+        TypedQuery<String> query = em.createNamedQuery("getClientIdsByRealm", String.class);
+        query.setParameter("realm", realm.getId());
+        List<String> clients = query.getResultList();
+        if (clients.isEmpty()) return Collections.EMPTY_LIST;
+        List<ClientModel> list = new LinkedList<>();
+        for (String id : clients) {
+            ClientModel client = session.realms().getClientById(id, realm);
+            if (client != null) list.add(client);
+        }
+        return Collections.unmodifiableList(list);
+
+    }
+
+
+    @Override
     public ClientModel getClientById(String id, RealmModel realm) {
         ClientEntity app = em.find(ClientEntity.class, id);
 
@@ -184,13 +338,13 @@ public class JpaRealmProvider implements RealmProvider {
 
     @Override
     public ClientModel getClientByClientId(String clientId, RealmModel realm) {
-        TypedQuery<ClientEntity> query = em.createNamedQuery("findClientByClientId", ClientEntity.class);
+        TypedQuery<String> query = em.createNamedQuery("findClientIdByClientId", String.class);
         query.setParameter("clientId", clientId);
         query.setParameter("realm", realm.getId());
-        List<ClientEntity> results = query.getResultList();
+        List<String> results = query.getResultList();
         if (results.isEmpty()) return null;
-        ClientEntity entity = results.get(0);
-        return new ClientAdapter(realm, em, session, entity);
+        String id = results.get(0);
+        return session.realms().getClientById(id, realm);
     }
 
     @Override
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 65d3b1b..16eb142 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
@@ -683,6 +683,21 @@ public class RealmAdapter implements RealmModel {
     }
 
     @Override
+    public void removeDefaultRoles(String... defaultRoles) {
+        Collection<RoleEntity> entities = realm.getDefaultRoles();
+        List<RoleEntity> remove = new ArrayList<RoleEntity>();
+        for (RoleEntity rel : entities) {
+            if (contains(rel.getName(), defaultRoles)) {
+                remove.add(rel);
+            }
+        }
+        for (RoleEntity entity : remove) {
+            entities.remove(entity);
+        }
+        em.flush();
+     }
+
+    @Override
     public List<GroupModel> getDefaultGroups() {
         Collection<GroupEntity> entities = realm.getDefaultGroups();
         if (entities == null || entities.isEmpty()) return Collections.EMPTY_LIST;
@@ -723,45 +738,17 @@ public class RealmAdapter implements RealmModel {
 
     @Override
     public List<ClientModel> getClients() {
-        TypedQuery<ClientEntity> query = em.createNamedQuery("getClientsByRealm", ClientEntity.class);
-        query.setParameter("realm", realm);
-        List<ClientEntity> clients = query.getResultList();
-        if (clients.isEmpty()) return Collections.EMPTY_LIST;
-        List<ClientModel> list = new LinkedList<>();
-        for (ClientEntity entity : clients) {
-            list.add(new ClientAdapter(this, em, session, entity));
-        }
-      return Collections.unmodifiableList(list);
+        return session.realms().getClients(this);
     }
 
     @Override
     public ClientModel addClient(String name) {
-        return this.addClient(KeycloakModelUtils.generateId(), name);
+        return session.realms().addClient(this, name);
     }
 
     @Override
     public ClientModel addClient(String id, String clientId) {
-        if (clientId == null) {
-            clientId = id;
-        }
-        ClientEntity entity = new ClientEntity();
-        entity.setId(id);
-        entity.setClientId(clientId);
-        entity.setEnabled(true);
-        entity.setStandardFlowEnabled(true);
-        entity.setRealm(realm);
-        realm.getClients().add(entity);
-        em.persist(entity);
-        em.flush();
-        final ClientModel resource = new ClientAdapter(this, em, session, entity);
-        em.flush();
-        session.getKeycloakSessionFactory().publish(new ClientCreationEvent() {
-            @Override
-            public ClientModel getCreatedClient() {
-                return resource;
-            }
-        });
-        return resource;
+        return session.realms().addClient(this, id, clientId);
     }
 
     @Override
@@ -956,7 +943,7 @@ public class RealmAdapter implements RealmModel {
             em.remove(entity);
         }
 
-        List<UserFederationProviderModel> add = new LinkedList<UserFederationProviderModel>();
+        List<UserFederationProviderModel> add = new LinkedList<>();
         for (UserFederationProviderModel model : providers) {
             boolean found = false;
             for (UserFederationProviderEntity entity : realm.getUserFederationProviders()) {
@@ -1008,64 +995,27 @@ public class RealmAdapter implements RealmModel {
 
     @Override
     public RoleModel getRole(String name) {
-        TypedQuery<RoleEntity> query = em.createNamedQuery("getRealmRoleByName", RoleEntity.class);
-        query.setParameter("name", name);
-        query.setParameter("realm", realm);
-        List<RoleEntity> roles = query.getResultList();
-        if (roles.size() == 0) return null;
-        return new RoleAdapter(this, em, roles.get(0));
+        return session.realms().getRealmRole(this, name);
     }
 
     @Override
     public RoleModel addRole(String name) {
-        return this.addRole(KeycloakModelUtils.generateId(), name);
+        return session.realms().addRealmRole(this, name);
     }
 
     @Override
     public RoleModel addRole(String id, String name) {
-        RoleEntity entity = new RoleEntity();
-        entity.setId(id);
-        entity.setName(name);
-        entity.setRealm(realm);
-        entity.setRealmId(realm.getId());
-        realm.getRoles().add(entity);
-        em.persist(entity);
-        em.flush();
-        return new RoleAdapter(this, em, entity);
+        return session.realms().addRealmRole(this, id, name);
     }
 
     @Override
     public boolean removeRole(RoleModel role) {
-        if (role == null) {
-            return false;
-        }
-        if (!role.getContainer().equals(this)) return false;
-        session.users().preRemove(this, role);
-        RoleEntity roleEntity = RoleAdapter.toRoleEntity(role, em);
-        realm.getRoles().remove(roleEntity);
-        realm.getDefaultRoles().remove(roleEntity);
-
-        String compositeRoleTable = JpaUtils.getTableNameForNativeQuery("COMPOSITE_ROLE", em);
-        em.createNativeQuery("delete from " + compositeRoleTable + " where CHILD_ROLE = :role").setParameter("role", roleEntity).executeUpdate();
-        em.createNamedQuery("deleteScopeMappingByRole").setParameter("role", roleEntity).executeUpdate();
-        em.createNamedQuery("deleteTemplateScopeMappingByRole").setParameter("role", roleEntity).executeUpdate();
-        em.createNamedQuery("deleteGroupRoleMappingsByRole").setParameter("roleId", roleEntity.getId()).executeUpdate();
-
-        em.remove(roleEntity);
-        em.flush();
-
-        return true;
+        return session.realms().removeRole(this, role);
     }
 
     @Override
     public Set<RoleModel> getRoles() {
-        Collection<RoleEntity> roles = realm.getRoles();
-        if (roles == null) return Collections.EMPTY_SET;
-        Set<RoleModel> list = new HashSet<RoleModel>();
-        for (RoleEntity entity : roles) {
-            list.add(new RoleAdapter(this, em, entity));
-        }
-        return Collections.unmodifiableSet(list);
+        return session.realms().getRealmRoles(this);
     }
 
     @Override
@@ -1259,9 +1209,14 @@ public class RealmAdapter implements RealmModel {
         if (masterAdminClient == null) {
             return null;
         }
-
-        RealmAdapter masterRealm = new RealmAdapter(session, em, masterAdminClient.getRealm());
-        return new ClientAdapter(masterRealm, em, session, masterAdminClient);
+        RealmModel masterRealm = null;
+        String masterAdminClientRealmId = masterAdminClient.getRealm().getId();
+        if (masterAdminClientRealmId.equals(getId())) {
+            masterRealm = this;
+        } else {
+            masterRealm = session.realms().getRealm(masterAdminClientRealmId);
+        }
+        return session.realms().getClientById(masterAdminClient.getId(), masterRealm);
     }
 
     @Override
@@ -2052,11 +2007,12 @@ public class RealmAdapter implements RealmModel {
 
     @Override
     public List<GroupModel> getGroups() {
-        List<GroupEntity> groups =  em.createNamedQuery("getAllGroupsByRealm").setParameter("realm", realm).getResultList();
+        List<String> groups =  em.createNamedQuery("getAllGroupIdsByRealm", String.class)
+                .setParameter("realm", realm.getId()).getResultList();
         if (groups == null) return Collections.EMPTY_LIST;
         List<GroupModel> list = new LinkedList<>();
-        for (GroupEntity entity : groups) {
-            list.add(new GroupAdapter(this, em, entity));
+        for (String id : groups) {
+            list.add(session.realms().getGroupById(id, this));
         }
         return Collections.unmodifiableList(list);
     }
@@ -2128,7 +2084,7 @@ public class RealmAdapter implements RealmModel {
         if (entities == null || entities.isEmpty()) return Collections.EMPTY_LIST;
         List<ClientTemplateModel> list = new LinkedList<>();
         for (ClientTemplateEntity entity : entities) {
-            list.add(new ClientTemplateAdapter(this, em, session, entity));
+            list.add(session.realms().getClientTemplateById(entity.getId(), this));
         }
         return Collections.unmodifiableList(list);
     }
diff --git a/model/jpa/src/main/java/org/keycloak/models/jpa/RoleAdapter.java b/model/jpa/src/main/java/org/keycloak/models/jpa/RoleAdapter.java
index 21103d7..0ac1be9 100755
--- a/model/jpa/src/main/java/org/keycloak/models/jpa/RoleAdapter.java
+++ b/model/jpa/src/main/java/org/keycloak/models/jpa/RoleAdapter.java
@@ -17,6 +17,7 @@
 
 package org.keycloak.models.jpa;
 
+import org.keycloak.models.KeycloakSession;
 import org.keycloak.models.RealmModel;
 import org.keycloak.models.RoleContainerModel;
 import org.keycloak.models.RoleModel;
@@ -36,11 +37,13 @@ public class RoleAdapter implements RoleModel {
     protected RoleEntity role;
     protected EntityManager em;
     protected RealmModel realm;
+    protected KeycloakSession session;
 
-    public RoleAdapter(RealmModel realm, EntityManager em, RoleEntity role) {
+    public RoleAdapter(KeycloakSession session, RealmModel realm, EntityManager em, RoleEntity role) {
         this.em = em;
         this.realm = realm;
         this.role = role;
+        this.session = session;
     }
 
     public RoleEntity getRole() {
@@ -115,7 +118,10 @@ public class RoleAdapter implements RoleModel {
         Set<RoleModel> set = new HashSet<RoleModel>();
 
         for (RoleEntity composite : getRole().getCompositeRoles()) {
-           set.add(new RoleAdapter(realm, em, composite));
+            set.add(new RoleAdapter(session, realm, em, composite));
+
+            // todo I want to do this, but can't as you get stack overflow
+            // set.add(session.realms().getRoleById(composite.getId(), realm));
         }
         return set;
     }
diff --git a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/ClientAdapter.java b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/ClientAdapter.java
index 91c9308..ff18fdf 100755
--- a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/ClientAdapter.java
+++ b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/ClientAdapter.java
@@ -567,34 +567,18 @@ public class ClientAdapter extends AbstractMongoAdapter<MongoClientEntity> imple
     }
 
     @Override
-    public RoleAdapter getRole(String name) {
-        DBObject query = new QueryBuilder()
-                .and("name").is(name)
-                .and("clientId").is(getId())
-                .get();
-        MongoRoleEntity role = getMongoStore().loadSingleEntity(MongoRoleEntity.class, query, invocationContext);
-        if (role == null) {
-            return null;
-        } else {
-            return new RoleAdapter(session, getRealm(), role, invocationContext);
-        }
+    public RoleModel getRole(String name) {
+        return session.realms().getClientRole(realm, this, name);
     }
 
     @Override
-    public RoleAdapter addRole(String name) {
-        return this.addRole(null, name);
+    public RoleModel addRole(String name) {
+        return session.realms().addClientRole(realm, this, name);
     }
 
     @Override
-    public RoleAdapter addRole(String id, String name) {
-        MongoRoleEntity roleEntity = new MongoRoleEntity();
-        roleEntity.setId(id);
-        roleEntity.setName(name);
-        roleEntity.setClientId(getId());
-
-        getMongoStore().insertEntity(roleEntity, invocationContext);
-
-        return new RoleAdapter(session, getRealm(), roleEntity, this, invocationContext);
+    public RoleModel addRole(String id, String name) {
+        return session.realms().addClientRole(realm, this, id, name);
     }
 
     @Override
@@ -605,17 +589,7 @@ public class ClientAdapter extends AbstractMongoAdapter<MongoClientEntity> imple
 
     @Override
     public Set<RoleModel> getRoles() {
-        DBObject query = new QueryBuilder()
-                .and("clientId").is(getId())
-                .get();
-        List<MongoRoleEntity> roles = getMongoStore().loadEntities(MongoRoleEntity.class, query, invocationContext);
-
-        Set<RoleModel> result = new HashSet<RoleModel>();
-        for (MongoRoleEntity role : roles) {
-            result.add(new RoleAdapter(session, getRealm(), role, this, invocationContext));
-        }
-
-        return result;
+        return session.realms().getClientRoles(realm, this);
     }
 
     @Override
@@ -653,7 +627,7 @@ public class ClientAdapter extends AbstractMongoAdapter<MongoClientEntity> imple
     }
 
     @Override
-    public void updateDefaultRoles(String[] defaultRoles) {
+    public void updateDefaultRoles(String... defaultRoles) {
         List<String> roleNames = new ArrayList<String>();
         for (String roleName : defaultRoles) {
             RoleModel role = getRole(roleName);
@@ -669,6 +643,17 @@ public class ClientAdapter extends AbstractMongoAdapter<MongoClientEntity> imple
     }
 
     @Override
+    public void removeDefaultRoles(String... defaultRoles) {
+        List<String> roleNames = new ArrayList<String>();
+        for (String role : getMongoEntity().getDefaultRoles()) {
+            if (!RealmAdapter.contains(role, defaultRoles)) roleNames.add(role);
+        }
+        getMongoEntity().setDefaultRoles(roleNames);
+        updateMongoEntity();
+    }
+
+
+    @Override
     public int getNodeReRegistrationTimeout() {
         return getMongoEntity().getNodeReRegistrationTimeout();
     }
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 c5d70fb..f8c9b79 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
@@ -39,7 +39,10 @@ import org.keycloak.models.mongo.keycloak.entities.MongoRoleEntity;
 import org.keycloak.models.utils.KeycloakModelUtils;
 
 import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashSet;
 import java.util.List;
+import java.util.Set;
 
 /**
  * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
@@ -105,7 +108,8 @@ public class MongoRealmProvider implements RealmProvider {
 
         List<RealmModel> results = new ArrayList<RealmModel>();
         for (MongoRealmEntity realmEntity : realms) {
-            results.add(new RealmAdapter(session, realmEntity, invocationContext));
+            RealmModel realm = session.realms().getRealm(realmEntity.getId());
+            if (realm != null) results.add(realm);
         }
         return results;
     }
@@ -118,7 +122,7 @@ public class MongoRealmProvider implements RealmProvider {
         MongoRealmEntity realm = getMongoStore().loadSingleEntity(MongoRealmEntity.class, query, invocationContext);
 
         if (realm == null) return null;
-        return new RealmAdapter(session, realm, invocationContext);
+        return session.realms().getRealm(realm.getId());
     }
 
     @Override
@@ -163,6 +167,152 @@ public class MongoRealmProvider implements RealmProvider {
     }
 
     @Override
+    public ClientModel addClient(RealmModel realm, String clientId) {
+        return addClient(realm, KeycloakModelUtils.generateId(), clientId);
+    }
+
+    @Override
+    public ClientModel addClient(RealmModel realm, String id, String clientId) {
+        MongoClientEntity clientEntity = new MongoClientEntity();
+        clientEntity.setId(id);
+        clientEntity.setClientId(clientId);
+        clientEntity.setRealmId(realm.getId());
+        clientEntity.setEnabled(true);
+        clientEntity.setStandardFlowEnabled(true);
+        getMongoStore().insertEntity(clientEntity, invocationContext);
+
+        if (clientId == null) {
+            clientEntity.setClientId(clientEntity.getId());
+            getMongoStore().updateEntity(clientEntity, invocationContext);
+        }
+
+        final ClientModel model = new ClientAdapter(session, realm, clientEntity, invocationContext);
+        session.getKeycloakSessionFactory().publish(new RealmModel.ClientCreationEvent() {
+            @Override
+            public ClientModel getCreatedClient() {
+                return model;
+            }
+        });
+        return model;
+    }
+
+    @Override
+    public List<ClientModel> getClients(RealmModel realm) {
+        DBObject query = new QueryBuilder()
+                .and("realmId").is(realm.getId())
+                .get();
+        List<MongoClientEntity> clientEntities = getMongoStore().loadEntities(MongoClientEntity.class, query, invocationContext);
+
+        if (clientEntities.isEmpty()) return Collections.EMPTY_LIST;
+        List<ClientModel> result = new ArrayList<ClientModel>();
+        for (MongoClientEntity clientEntity : clientEntities) {
+            result.add(session.realms().getClientById(clientEntity.getId(), realm));
+        }
+        return Collections.unmodifiableList(result);
+    }
+
+    @Override
+    public RoleModel addRealmRole(RealmModel realm, String name) {
+        return addRealmRole(realm, KeycloakModelUtils.generateId(), name);
+    }
+
+    @Override
+    public RoleModel addRealmRole(RealmModel realm, String id, String name) {
+        MongoRoleEntity roleEntity = new MongoRoleEntity();
+        roleEntity.setId(id);
+        roleEntity.setName(name);
+        roleEntity.setRealmId(realm.getId());
+
+        getMongoStore().insertEntity(roleEntity, invocationContext);
+
+        return new RoleAdapter(session, realm, roleEntity, realm, invocationContext);
+    }
+
+    @Override
+    public Set<RoleModel> getRealmRoles(RealmModel realm) {
+        DBObject query = new QueryBuilder()
+                .and("realmId").is(realm.getId())
+                .get();
+        List<MongoRoleEntity> roles = getMongoStore().loadEntities(MongoRoleEntity.class, query, invocationContext);
+
+
+        if (roles == null) return Collections.EMPTY_SET;
+        Set<RoleModel> result = new HashSet<RoleModel>();
+        for (MongoRoleEntity role : roles) {
+            result.add(session.realms().getRoleById(role.getId(), realm));
+        }
+
+        return Collections.unmodifiableSet(result);
+
+    }
+
+    @Override
+    public Set<RoleModel> getClientRoles(RealmModel realm, ClientModel client) {
+        DBObject query = new QueryBuilder()
+                .and("clientId").is(client.getId())
+                .get();
+        List<MongoRoleEntity> roles = getMongoStore().loadEntities(MongoRoleEntity.class, query, invocationContext);
+
+        Set<RoleModel> result = new HashSet<RoleModel>();
+        for (MongoRoleEntity role : roles) {
+            result.add(session.realms().getRoleById(role.getId(), realm));
+        }
+
+        return result;
+    }
+
+    @Override
+    public RoleModel getRealmRole(RealmModel realm, String name) {
+        DBObject query = new QueryBuilder()
+                .and("name").is(name)
+                .and("realmId").is(realm.getId())
+                .get();
+        MongoRoleEntity role = getMongoStore().loadSingleEntity(MongoRoleEntity.class, query, invocationContext);
+        if (role == null) {
+            return null;
+        } else {
+            return session.realms().getRoleById(role.getId(), realm);
+        }
+    }
+
+    @Override
+    public RoleModel getClientRole(RealmModel realm, ClientModel client, String name) {
+        DBObject query = new QueryBuilder()
+                .and("name").is(name)
+                .and("clientId").is(client.getId())
+                .get();
+        MongoRoleEntity role = getMongoStore().loadSingleEntity(MongoRoleEntity.class, query, invocationContext);
+        if (role == null) {
+            return null;
+        } else {
+            return session.realms().getRoleById(role.getId(), realm);
+        }
+    }
+
+    @Override
+    public RoleModel addClientRole(RealmModel realm, ClientModel client, String name) {
+        return addClientRole(realm, client, KeycloakModelUtils.generateId(), name);
+    }
+
+    @Override
+    public RoleModel addClientRole(RealmModel realm, ClientModel client, String id, String name) {
+        MongoRoleEntity roleEntity = new MongoRoleEntity();
+        roleEntity.setId(id);
+        roleEntity.setName(name);
+        roleEntity.setClientId(client.getId());
+
+        getMongoStore().insertEntity(roleEntity, invocationContext);
+
+        return new RoleAdapter(session, realm, roleEntity, client, invocationContext);
+    }
+
+    @Override
+    public boolean removeRole(RealmModel realm, RoleModel role) {
+        session.users().preRemove(realm, role);
+        return getMongoStore().removeEntity(MongoRoleEntity.class, role.getId(), invocationContext);
+    }
+
+    @Override
     public boolean removeClient(String id, RealmModel realm) {
         if (id == null) return false;
         ClientModel client = getClientById(id, realm);
@@ -180,7 +330,8 @@ public class MongoRealmProvider implements RealmProvider {
                 .and("clientId").is(clientId)
                 .get();
         MongoClientEntity appEntity = getMongoStore().loadSingleEntity(MongoClientEntity.class, query, invocationContext);
-        return appEntity == null ? null : new ClientAdapter(session, realm, appEntity, invocationContext);
+        if (appEntity == null) return null;
+        return session.realms().getClientById(appEntity.getId(), realm);
 
     }
 
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 48978ff..6c4b6f9 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
@@ -595,47 +595,30 @@ public class RealmAdapter extends AbstractMongoAdapter<MongoRealmEntity> impleme
     }
 
     @Override
-    public RoleAdapter getRole(String name) {
-        DBObject query = new QueryBuilder()
-                .and("name").is(name)
-                .and("realmId").is(getId())
-                .get();
-        MongoRoleEntity role = getMongoStore().loadSingleEntity(MongoRoleEntity.class, query, invocationContext);
-        if (role == null) {
-            return null;
-        } else {
-            return new RoleAdapter(session, this, role, this, invocationContext);
-        }
+    public RoleModel getRole(String name) {
+        return session.realms().getRealmRole(this, name);
     }
 
     @Override
     public RoleModel addRole(String name) {
-        return this.addRole(null, name);
+        return session.realms().addRealmRole(this, name);
     }
 
     @Override
     public RoleModel addRole(String id, String name) {
-        MongoRoleEntity roleEntity = new MongoRoleEntity();
-        roleEntity.setId(id);
-        roleEntity.setName(name);
-        roleEntity.setRealmId(getId());
-
-        getMongoStore().insertEntity(roleEntity, invocationContext);
-
-        return new RoleAdapter(session, this, roleEntity, this, invocationContext);
+        return session.realms().addRealmRole(this, id, name);
     }
 
     @Override
     public boolean removeRole(RoleModel role) {
-        return removeRoleById(role.getId());
+        return session.realms().removeRole(this, role);
     }
 
     @Override
     public boolean removeRoleById(String id) {
         RoleModel role = getRoleById(id);
         if (role == null) return false;
-        session.users().preRemove(this, role);
-        return getMongoStore().removeEntity(MongoRoleEntity.class, id, invocationContext);
+        return removeRole(role);
     }
 
     @Override
@@ -657,7 +640,7 @@ public class RealmAdapter extends AbstractMongoAdapter<MongoRealmEntity> impleme
 
     @Override
     public RoleModel getRoleById(String id) {
-        return model.getRoleById(id, this);
+        return session.realms().getRoleById(id, this);
     }
 
     @Override
@@ -780,6 +763,24 @@ public class RealmAdapter extends AbstractMongoAdapter<MongoRealmEntity> impleme
         updateRealm();
     }
 
+    public static boolean contains(String str, String[] array) {
+        for (String s : array) {
+            if (str.equals(s)) return true;
+        }
+        return false;
+    }
+
+
+    @Override
+    public void removeDefaultRoles(String... defaultRoles) {
+        List<String> roleNames = new ArrayList<String>();
+        for (String role : realm.getDefaultRoles()) {
+            if (!contains(role, defaultRoles)) roleNames.add(role);
+        }
+        realm.setDefaultRoles(roleNames);
+        updateRealm();
+    }
+
     @Override
     public List<GroupModel> getDefaultGroups() {
         List<String> entities = realm.getDefaultGroups();
@@ -815,47 +816,18 @@ public class RealmAdapter extends AbstractMongoAdapter<MongoRealmEntity> impleme
 
     @Override
     public List<ClientModel> getClients() {
-        DBObject query = new QueryBuilder()
-                .and("realmId").is(getId())
-                .get();
-        List<MongoClientEntity> clientEntities = getMongoStore().loadEntities(MongoClientEntity.class, query, invocationContext);
-
-        if (clientEntities.isEmpty()) return Collections.EMPTY_LIST;
-        List<ClientModel> result = new ArrayList<ClientModel>();
-        for (MongoClientEntity clientEntity : clientEntities) {
-            result.add(new ClientAdapter(session, this, clientEntity, invocationContext));
-        }
-        return Collections.unmodifiableList(result);
+        return session.realms().getClients(this);
     }
 
     @Override
     public ClientModel addClient(String name) {
-        return this.addClient(null, name);
+        return session.realms().addClient(this, name);
     }
 
     @Override
     public ClientModel addClient(String id, String clientId) {
-        MongoClientEntity clientEntity = new MongoClientEntity();
-        clientEntity.setId(id);
-        clientEntity.setClientId(clientId);
-        clientEntity.setRealmId(getId());
-        clientEntity.setEnabled(true);
-        clientEntity.setStandardFlowEnabled(true);
-        getMongoStore().insertEntity(clientEntity, invocationContext);
-
-        if (clientId == null) {
-            clientEntity.setClientId(clientEntity.getId());
-            getMongoStore().updateEntity(clientEntity, invocationContext);
-        }
+        return session.realms().addClient(this, id, clientId);
 
-        final ClientModel model = new ClientAdapter(session, this, clientEntity, invocationContext);
-        session.getKeycloakSessionFactory().publish(new ClientCreationEvent() {
-            @Override
-            public ClientModel getCreatedClient() {
-                return model;
-            }
-        });
-        return model;
     }
 
     @Override
diff --git a/server-spi/src/main/java/org/keycloak/models/cache/CacheRealmProvider.java b/server-spi/src/main/java/org/keycloak/models/cache/CacheRealmProvider.java
index 0f90b1d..7bc1299 100755
--- a/server-spi/src/main/java/org/keycloak/models/cache/CacheRealmProvider.java
+++ b/server-spi/src/main/java/org/keycloak/models/cache/CacheRealmProvider.java
@@ -29,7 +29,7 @@ public interface CacheRealmProvider extends RealmProvider {
 
     void registerRealmInvalidation(String id);
 
-    void registerApplicationInvalidation(String id);
+    void registerClientInvalidation(String id);
     void registerClientTemplateInvalidation(String id);
 
     void registerRoleInvalidation(String id);
diff --git a/server-spi/src/main/java/org/keycloak/models/ClientModel.java b/server-spi/src/main/java/org/keycloak/models/ClientModel.java
index 8399969..6d96eb0 100755
--- a/server-spi/src/main/java/org/keycloak/models/ClientModel.java
+++ b/server-spi/src/main/java/org/keycloak/models/ClientModel.java
@@ -85,12 +85,6 @@ public interface ClientModel extends RoleContainerModel,  ProtocolMapperContaine
 
     void setBaseUrl(String url);
 
-    List<String> getDefaultRoles();
-
-    void addDefaultRole(String name);
-
-    void updateDefaultRoles(String[] defaultRoles);
-
 
     boolean isBearerOnly();
     void setBearerOnly(boolean only);
diff --git a/server-spi/src/main/java/org/keycloak/models/RealmModel.java b/server-spi/src/main/java/org/keycloak/models/RealmModel.java
index bc7dec5..1c42e45 100755
--- a/server-spi/src/main/java/org/keycloak/models/RealmModel.java
+++ b/server-spi/src/main/java/org/keycloak/models/RealmModel.java
@@ -181,12 +181,6 @@ public interface RealmModel extends RoleContainerModel {
 
     RoleModel getRoleById(String id);
 
-    List<String> getDefaultRoles();
-
-    void addDefaultRole(String name);
-
-    void updateDefaultRoles(String[] defaultRoles);
-
     List<GroupModel> getDefaultGroups();
 
     void addDefaultGroup(GroupModel group);
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 2b3f5fa..e7584e7 100755
--- a/server-spi/src/main/java/org/keycloak/models/RealmProvider.java
+++ b/server-spi/src/main/java/org/keycloak/models/RealmProvider.java
@@ -21,6 +21,7 @@ import org.keycloak.migration.MigrationModel;
 import org.keycloak.provider.Provider;
 
 import java.util.List;
+import java.util.Set;
 
 /**
  * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
@@ -35,10 +36,34 @@ public interface RealmProvider extends Provider {
     RealmModel getRealm(String id);
     RealmModel getRealmByName(String name);
 
+    ClientModel addClient(RealmModel realm, String clientId);
+
+    ClientModel addClient(RealmModel realm, String id, String clientId);
+
+    List<ClientModel> getClients(RealmModel realm);
+
     ClientModel getClientById(String id, RealmModel realm);
     ClientModel getClientByClientId(String clientId, RealmModel realm);
 
 
+    RoleModel addRealmRole(RealmModel realm, String name);
+
+    RoleModel addRealmRole(RealmModel realm, String id, String name);
+
+    RoleModel getRealmRole(RealmModel realm, String name);
+
+    RoleModel addClientRole(RealmModel realm, ClientModel client, String name);
+
+    RoleModel addClientRole(RealmModel realm, ClientModel client, String id, String name);
+
+    Set<RoleModel> getRealmRoles(RealmModel realm);
+
+    RoleModel getClientRole(RealmModel realm, ClientModel client, String name);
+
+    Set<RoleModel> getClientRoles(RealmModel realm, ClientModel client);
+
+    boolean removeRole(RealmModel realm, RoleModel role);
+
     RoleModel getRoleById(String id, RealmModel realm);
 
     boolean removeClient(String id, RealmModel realm);
diff --git a/server-spi/src/main/java/org/keycloak/models/RoleContainerModel.java b/server-spi/src/main/java/org/keycloak/models/RoleContainerModel.java
index f405821..24c60b3 100755
--- a/server-spi/src/main/java/org/keycloak/models/RoleContainerModel.java
+++ b/server-spi/src/main/java/org/keycloak/models/RoleContainerModel.java
@@ -17,6 +17,7 @@
 
 package org.keycloak.models;
 
+import java.util.List;
 import java.util.Set;
 
 /**
@@ -24,6 +25,8 @@ import java.util.Set;
  * @version $Revision: 1 $
  */
 public interface RoleContainerModel {
+    String getId();
+
     RoleModel getRole(String name);
 
     RoleModel addRole(String name);
@@ -34,4 +37,11 @@ public interface RoleContainerModel {
 
     Set<RoleModel> getRoles();
 
+    List<String> getDefaultRoles();
+
+    void addDefaultRole(String name);
+
+    void updateDefaultRoles(String... defaultRoles);
+
+    void removeDefaultRoles(String... defaultRoles);
 }
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 7984fae..9adebd3 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
@@ -27,6 +27,7 @@ import org.keycloak.admin.client.resource.RealmResource;
 import org.keycloak.representations.idm.ClientRepresentation;
 import org.keycloak.representations.idm.RoleRepresentation;
 
+import javax.ws.rs.NotFoundException;
 import javax.ws.rs.core.Response;
 import java.util.LinkedList;
 import java.util.List;
@@ -90,6 +91,7 @@ public class ConcurrencyTest extends AbstractClientTest {
 
     @Test
     public void createClient() throws Throwable {
+        System.out.println("***************************");
         long start = System.currentTimeMillis();
         run(new KeycloakRunnable() {
             @Override
@@ -101,7 +103,8 @@ public class ConcurrencyTest extends AbstractClientTest {
                 String id = ApiUtil.getCreatedId(response);
                 response.close();
 
-                assertNotNull(realm.clients().get(id));
+                c = realm.clients().get(id).toRepresentation();
+                assertNotNull(c);
                 boolean found = false;
                 for (ClientRepresentation r : realm.clients().findAll()) {
                     if (r.getClientId().equals(name)) {
@@ -120,6 +123,58 @@ public class ConcurrencyTest extends AbstractClientTest {
     }
 
     @Test
+    @Ignore
+    public void createRemoveClient() throws Throwable {
+        // FYI< this will fail as HSQL seems to be trying to perform table locks.
+        System.out.println("***************************");
+        long start = System.currentTimeMillis();
+        run(new KeycloakRunnable() {
+            @Override
+            public void run(Keycloak keycloak, RealmResource realm, int threadNum, int iterationNum) {
+                String name = "c-" + threadNum + "-" + iterationNum;
+                ClientRepresentation c = new ClientRepresentation();
+                c.setClientId(name);
+                Response response = realm.clients().create(c);
+                String id = ApiUtil.getCreatedId(response);
+                response.close();
+
+                c = realm.clients().get(id).toRepresentation();
+                assertNotNull(c);
+                boolean found = false;
+                for (ClientRepresentation r : realm.clients().findAll()) {
+                    if (r.getClientId().equals(name)) {
+                        found = true;
+                        break;
+                    }
+                }
+                if (!found) {
+                    fail("Client " + name + " not found in client list");
+                }
+                realm.clients().get(id).remove();
+                try {
+                    c = realm.clients().get(id).toRepresentation();
+                    fail("Client " + name + " should not be found.  Should throw a 404");
+                } catch (NotFoundException e) {
+
+                }
+                found = false;
+                for (ClientRepresentation r : realm.clients().findAll()) {
+                    if (r.getClientId().equals(name)) {
+                        found = true;
+                        break;
+                    }
+                }
+                Assert.assertFalse("Client " + name + " should not be in client list", found);
+
+            }
+        });
+        long end = System.currentTimeMillis() - start;
+        System.out.println("createClient took " + end);
+
+    }
+
+
+    @Test
     public void createRole() throws Throwable {
         long start = System.currentTimeMillis();
         run(new KeycloakRunnable() {
diff --git a/testsuite/integration/src/test/resources/log4j.properties b/testsuite/integration/src/test/resources/log4j.properties
index 91c9646..a199a41 100755
--- a/testsuite/integration/src/test/resources/log4j.properties
+++ b/testsuite/integration/src/test/resources/log4j.properties
@@ -19,7 +19,7 @@ log4j.rootLogger=info, stdout
 
 log4j.appender.stdout=org.apache.log4j.ConsoleAppender
 log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
-log4j.appender.stdout.layout.ConversionPattern=%d{HH:mm:ss,SSS} %-5p [%c] %m%n
+log4j.appender.stdout.layout.ConversionPattern=%d{HH:mm:ss,SSS} %-5p %t [%c] %m%n
 
 log4j.logger.org.keycloak=info
 
diff --git a/testsuite/integration/src/test/resources/META-INF/keycloak-server.json b/testsuite/integration/src/test/resources/META-INF/keycloak-server.json
index 9329cde..950fd22 100755
--- a/testsuite/integration/src/test/resources/META-INF/keycloak-server.json
+++ b/testsuite/integration/src/test/resources/META-INF/keycloak-server.json
@@ -27,7 +27,7 @@
     },
 
     "userCache": {
-        "infinispan" : {
+        "default" : {
             "enabled": true
         }
     },
@@ -78,15 +78,13 @@
     },
 
     "realmCache": {
-        "provider": "infinispan-locking",
-        "infinispan-locking" : {
+        "default" : {
             "enabled": true
         }
     },
 
     "connectionsInfinispan": {
-        "provider": "locking",
-        "locking": {
+        "default": {
             "clustered": "${keycloak.connectionsInfinispan.clustered:false}",
             "async": "${keycloak.connectionsInfinispan.async:true}",
             "sessionsOwners": "${keycloak.connectionsInfinispan.sessionsOwners:2}"
diff --git a/testsuite/integration-arquillian/tests/base/src/test/resources/META-INF/keycloak-server.json b/testsuite/integration-arquillian/tests/base/src/test/resources/META-INF/keycloak-server.json
index e26f610..ae0f5f9 100755
--- a/testsuite/integration-arquillian/tests/base/src/test/resources/META-INF/keycloak-server.json
+++ b/testsuite/integration-arquillian/tests/base/src/test/resources/META-INF/keycloak-server.json
@@ -27,7 +27,10 @@
     },
 
     "userCache": {
-        "provider": "${keycloak.user.cache.provider:infinispan}",
+        "provider": "${keycloak.user.cache.provider:default}",
+        "default" : {
+            "enabled": true
+        },
         "mem": {
             "maxSize": 20000
         }
@@ -95,15 +98,14 @@
     },
 
     "realmCache": {
-        "provider": "infinispan-locking",
-        "infinispan-locking" : {
+        "provider": "${keycloak.realm.cache.provider:default}",
+        "default" : {
             "enabled": true
         }
     },
 
     "connectionsInfinispan": {
-        "provider": "locking",
-        "locking": {
+        "default": {
             "clustered": "${keycloak.connectionsInfinispan.clustered:false}",
             "async": "${keycloak.connectionsInfinispan.async:true}",
             "sessionsOwners": "${keycloak.connectionsInfinispan.sessionsOwners:2}"