keycloak-aplcache

Merge pull request #2215 from patriot1burke/master concurrency,

2/11/2016 12:41:37 PM

Changes

pom.xml 0(+0 -0)

Details

diff --git a/distribution/demo-dist/src/main/xslt/standalone.xsl b/distribution/demo-dist/src/main/xslt/standalone.xsl
index c8a02b0..2b67a54 100755
--- a/distribution/demo-dist/src/main/xslt/standalone.xsl
+++ b/distribution/demo-dist/src/main/xslt/standalone.xsl
@@ -89,6 +89,9 @@
                 <local-cache name="sessions"/>
                 <local-cache name="offlineSessions"/>
                 <local-cache name="loginFailures"/>
+                <local-cache name="realmVersions">
+                    <transaction mode="BATCH" locking="PESSIMISTIC"/>
+                </local-cache>
             </cache-container>
             <xsl:apply-templates select="node()|@*"/>
         </xsl:copy>
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
old mode 100644
new mode 100755
index b96d61b..41dab85
--- 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
@@ -28,12 +28,6 @@
         }
     },
 
-    "realmCache": {
-        "infinispan" : {
-            "enabled": true
-        }
-    },
-
     "userSessionPersister": {
         "provider": "jpa"
     },
@@ -67,8 +61,16 @@
         }
     },
 
+    "realmCache": {
+        "provider": "infinispan-locking",
+        "infinispan-locking" : {
+            "enabled": true
+        }
+    },
+
     "connectionsInfinispan": {
-        "default" : {
+        "provider": "locking",
+        "locking": {
             "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 45ce0d2..b90ef33 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
@@ -37,11 +37,11 @@ public class DefaultInfinispanConnectionProviderFactory implements InfinispanCon
 
     protected static final Logger logger = Logger.getLogger(DefaultInfinispanConnectionProviderFactory.class);
 
-    private Config.Scope config;
+    protected Config.Scope config;
 
-    private EmbeddedCacheManager cacheManager;
+    protected EmbeddedCacheManager cacheManager;
 
-    private boolean containerManaged;
+    protected boolean containerManaged;
 
     @Override
     public InfinispanConnectionProvider create(KeycloakSession session) {
@@ -73,7 +73,7 @@ public class DefaultInfinispanConnectionProviderFactory implements InfinispanCon
 
     }
 
-    private void lazyInit() {
+    protected void lazyInit() {
         if (cacheManager == null) {
             synchronized (this) {
                 if (cacheManager == null) {
@@ -88,7 +88,7 @@ public class DefaultInfinispanConnectionProviderFactory implements InfinispanCon
         }
     }
 
-    private void initContainerManaged(String cacheContainerLookup) {
+    protected void initContainerManaged(String cacheContainerLookup) {
         try {
             cacheManager = (EmbeddedCacheManager) new InitialContext().lookup(cacheContainerLookup);
             containerManaged = true;
@@ -99,7 +99,7 @@ public class DefaultInfinispanConnectionProviderFactory implements InfinispanCon
         }
     }
 
-    private void initEmbedded() {
+    protected void initEmbedded() {
         GlobalConfigurationBuilder gcb = new GlobalConfigurationBuilder();
 
         boolean clustered = config.getBoolean("clustered", false);
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 e5a3472..a9145b2 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
@@ -546,7 +546,9 @@ public class ClientAdapter implements ClientModel {
     public RoleModel getRole(String name) {
         if (updated != null) return updated.getRole(name);
         String id = cached.getRoles().get(name);
-        if (id == null) return null;
+        if (id == null) {
+            return null;
+        }
         return cacheSession.getRoleById(id, cachedRealm);
     }
 
diff --git a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/counter/entities/RevisionedCachedClient.java b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/counter/entities/RevisionedCachedClient.java
new file mode 100755
index 0000000..c07725e
--- /dev/null
+++ b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/counter/entities/RevisionedCachedClient.java
@@ -0,0 +1,41 @@
+package org.keycloak.models.cache.infinispan.counter.entities;
+
+import org.keycloak.models.ClientModel;
+import org.keycloak.models.RealmModel;
+import org.keycloak.models.RealmProvider;
+import org.keycloak.models.RoleModel;
+import org.keycloak.models.cache.RealmCache;
+import org.keycloak.models.cache.entities.CachedClient;
+import org.keycloak.models.cache.entities.CachedClientRole;
+import org.keycloak.models.cache.infinispan.counter.Revisioned;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public class RevisionedCachedClient extends CachedClient implements Revisioned {
+
+    public RevisionedCachedClient(Long revision, RealmCache cache, RealmProvider delegate, RealmModel realm, ClientModel model) {
+        super(cache, delegate, realm, model);
+        this.revision = revision;
+    }
+
+    private Long revision;
+
+    @Override
+    public Long getRevision() {
+        return revision;
+    }
+
+    @Override
+    public void setRevision(Long revision) {
+        this.revision = revision;
+    }
+
+    @Override
+    protected void cacheRoles(RealmCache cache, RealmModel realm, ClientModel model) {
+        for (RoleModel role : model.getRoles()) {
+            roles.put(role.getName(), role.getId());
+        }
+    }
+}
diff --git a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/counter/entities/RevisionedCachedClientRole.java b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/counter/entities/RevisionedCachedClientRole.java
new file mode 100755
index 0000000..2bdb176
--- /dev/null
+++ b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/counter/entities/RevisionedCachedClientRole.java
@@ -0,0 +1,32 @@
+package org.keycloak.models.cache.infinispan.counter.entities;
+
+import org.keycloak.models.RealmModel;
+import org.keycloak.models.RoleModel;
+import org.keycloak.models.cache.entities.CachedClientRole;
+import org.keycloak.models.cache.infinispan.counter.Revisioned;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public class RevisionedCachedClientRole extends CachedClientRole implements Revisioned {
+
+    public RevisionedCachedClientRole(Long revision, String idClient, RoleModel model, RealmModel realm) {
+        super(idClient, model, realm);
+        this.revision = revision;
+    }
+
+    private Long revision;
+
+    @Override
+    public Long getRevision() {
+        return revision;
+    }
+
+    @Override
+    public void setRevision(Long revision) {
+        this.revision = revision;
+    }
+
+
+}
diff --git a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/counter/entities/RevisionedCachedClientTemplate.java b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/counter/entities/RevisionedCachedClientTemplate.java
new file mode 100755
index 0000000..eeca1f6
--- /dev/null
+++ b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/counter/entities/RevisionedCachedClientTemplate.java
@@ -0,0 +1,34 @@
+package org.keycloak.models.cache.infinispan.counter.entities;
+
+import org.keycloak.models.ClientTemplateModel;
+import org.keycloak.models.RealmModel;
+import org.keycloak.models.RealmProvider;
+import org.keycloak.models.cache.RealmCache;
+import org.keycloak.models.cache.entities.CachedClientTemplate;
+import org.keycloak.models.cache.infinispan.counter.Revisioned;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public class RevisionedCachedClientTemplate extends CachedClientTemplate implements Revisioned {
+
+    public RevisionedCachedClientTemplate(Long revision, RealmCache cache, RealmProvider delegate, RealmModel realm, ClientTemplateModel model) {
+        super(cache, delegate, realm, model);
+        this.revision = revision;
+    }
+
+    private Long revision;
+
+    @Override
+    public Long getRevision() {
+        return revision;
+    }
+
+    @Override
+    public void setRevision(Long revision) {
+        this.revision = revision;
+    }
+
+
+}
diff --git a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/counter/entities/RevisionedCachedGroup.java b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/counter/entities/RevisionedCachedGroup.java
new file mode 100755
index 0000000..32529ea
--- /dev/null
+++ b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/counter/entities/RevisionedCachedGroup.java
@@ -0,0 +1,31 @@
+package org.keycloak.models.cache.infinispan.counter.entities;
+
+import org.keycloak.models.GroupModel;
+import org.keycloak.models.RealmModel;
+import org.keycloak.models.cache.entities.CachedGroup;
+import org.keycloak.models.cache.infinispan.counter.Revisioned;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public class RevisionedCachedGroup extends CachedGroup implements Revisioned {
+    public RevisionedCachedGroup(Long revision, RealmModel realm, GroupModel group) {
+        super(realm, group);
+        this.revision = revision;
+    }
+
+    private Long revision;
+
+    @Override
+    public Long getRevision() {
+        return revision;
+    }
+
+    @Override
+    public void setRevision(Long revision) {
+        this.revision = revision;
+    }
+
+
+}
diff --git a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/counter/entities/RevisionedCachedRealm.java b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/counter/entities/RevisionedCachedRealm.java
new file mode 100755
index 0000000..c527ea5
--- /dev/null
+++ b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/counter/entities/RevisionedCachedRealm.java
@@ -0,0 +1,59 @@
+package org.keycloak.models.cache.infinispan.counter.entities;
+
+import org.keycloak.models.ClientModel;
+import org.keycloak.models.ClientTemplateModel;
+import org.keycloak.models.RealmModel;
+import org.keycloak.models.RealmProvider;
+import org.keycloak.models.RoleModel;
+import org.keycloak.models.cache.RealmCache;
+import org.keycloak.models.cache.entities.CachedClient;
+import org.keycloak.models.cache.entities.CachedClientTemplate;
+import org.keycloak.models.cache.entities.CachedRealm;
+import org.keycloak.models.cache.entities.CachedRealmRole;
+import org.keycloak.models.cache.entities.CachedRole;
+import org.keycloak.models.cache.infinispan.counter.Revisioned;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public class RevisionedCachedRealm extends CachedRealm implements Revisioned {
+
+    public RevisionedCachedRealm(Long revision, RealmCache cache, RealmProvider delegate, RealmModel model) {
+        super(cache, delegate, model);
+        this.revision = revision;
+    }
+
+    private Long revision;
+
+    @Override
+    public Long getRevision() {
+        return revision;
+    }
+
+    @Override
+    public void setRevision(Long revision) {
+        this.revision = revision;
+    }
+
+    @Override
+    protected void cacheClientTemplates(RealmCache cache, RealmProvider delegate, RealmModel model) {
+        for (ClientTemplateModel template : model.getClientTemplates()) {
+            clientTemplates.add(template.getId());
+        }
+    }
+
+    @Override
+    protected void cacheClients(RealmCache cache, RealmProvider delegate, RealmModel model) {
+        for (ClientModel client : model.getClients()) {
+            clients.put(client.getClientId(), client.getId());
+        }
+    }
+
+    @Override
+    protected void cacheRealmRoles(RealmCache cache, RealmModel model) {
+        for (RoleModel role : model.getRoles()) {
+            realmRoles.put(role.getName(), role.getId());
+        }
+    }
+}
diff --git a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/counter/entities/RevisionedCachedRealmRole.java b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/counter/entities/RevisionedCachedRealmRole.java
new file mode 100755
index 0000000..776b885
--- /dev/null
+++ b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/counter/entities/RevisionedCachedRealmRole.java
@@ -0,0 +1,31 @@
+package org.keycloak.models.cache.infinispan.counter.entities;
+
+import org.keycloak.models.RealmModel;
+import org.keycloak.models.RoleModel;
+import org.keycloak.models.cache.entities.CachedRealmRole;
+import org.keycloak.models.cache.infinispan.counter.Revisioned;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public class RevisionedCachedRealmRole extends CachedRealmRole implements Revisioned {
+
+    public RevisionedCachedRealmRole(Long revision, RoleModel model, RealmModel realm) {
+        super(model, realm);
+        this.revision = revision;
+    }
+
+    private Long revision;
+
+    @Override
+    public Long getRevision() {
+        return revision;
+    }
+
+    @Override
+    public void setRevision(Long revision) {
+        this.revision = revision;
+    }
+
+}
diff --git a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/counter/entities/RevisionedCachedUser.java b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/counter/entities/RevisionedCachedUser.java
new file mode 100755
index 0000000..f281bef
--- /dev/null
+++ b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/counter/entities/RevisionedCachedUser.java
@@ -0,0 +1,30 @@
+package org.keycloak.models.cache.infinispan.counter.entities;
+
+import org.keycloak.models.RealmModel;
+import org.keycloak.models.UserModel;
+import org.keycloak.models.cache.entities.CachedUser;
+import org.keycloak.models.cache.infinispan.counter.Revisioned;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public class RevisionedCachedUser extends CachedUser implements Revisioned {
+    public RevisionedCachedUser(Long revision, RealmModel realm, UserModel user) {
+        super(realm, user);
+        this.revision = revision;
+    }
+
+    private Long revision;
+
+    @Override
+    public Long getRevision() {
+        return revision;
+    }
+
+    @Override
+    public void setRevision(Long revision) {
+        this.revision = revision;
+    }
+
+}
diff --git a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/counter/Revisioned.java b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/counter/Revisioned.java
new file mode 100755
index 0000000..f4d4c7a
--- /dev/null
+++ b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/counter/Revisioned.java
@@ -0,0 +1,10 @@
+package org.keycloak.models.cache.infinispan.counter;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public interface Revisioned {
+    Long getRevision();
+    void setRevision(Long revision);
+}
diff --git a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/counter/RevisionedCacheRealmProvider.java b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/counter/RevisionedCacheRealmProvider.java
new file mode 100755
index 0000000..d241519
--- /dev/null
+++ b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/counter/RevisionedCacheRealmProvider.java
@@ -0,0 +1,401 @@
+/*
+ * 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.counter;
+
+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.RealmCache;
+import org.keycloak.models.cache.entities.CachedClient;
+import org.keycloak.models.cache.entities.CachedClientRole;
+import org.keycloak.models.cache.entities.CachedClientTemplate;
+import org.keycloak.models.cache.entities.CachedGroup;
+import org.keycloak.models.cache.entities.CachedRealm;
+import org.keycloak.models.cache.entities.CachedRealmRole;
+import org.keycloak.models.cache.entities.CachedRole;
+import org.keycloak.models.cache.infinispan.ClientAdapter;
+import org.keycloak.models.cache.infinispan.ClientTemplateAdapter;
+import org.keycloak.models.cache.infinispan.GroupAdapter;
+import org.keycloak.models.cache.infinispan.RealmAdapter;
+import org.keycloak.models.cache.infinispan.RoleAdapter;
+import org.keycloak.models.cache.infinispan.counter.entities.RevisionedCachedClient;
+import org.keycloak.models.cache.infinispan.counter.entities.RevisionedCachedClientRole;
+import org.keycloak.models.cache.infinispan.counter.entities.RevisionedCachedClientTemplate;
+import org.keycloak.models.cache.infinispan.counter.entities.RevisionedCachedGroup;
+import org.keycloak.models.cache.infinispan.counter.entities.RevisionedCachedRealm;
+import org.keycloak.models.cache.infinispan.counter.entities.RevisionedCachedRealmRole;
+
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public class RevisionedCacheRealmProvider implements CacheRealmProvider {
+    protected static final Logger logger = Logger.getLogger(RevisionedCacheRealmProvider.class);
+    protected RevisionedRealmCache cache;
+    protected KeycloakSession session;
+    protected RealmProvider delegate;
+    protected boolean transactionActive;
+    protected boolean setRollbackOnly;
+
+    protected Set<String> realmInvalidations = new HashSet<>();
+    protected Set<String> appInvalidations = new HashSet<>();
+    protected Set<String> clientTemplateInvalidations = new HashSet<>();
+    protected Set<String> roleInvalidations = new HashSet<>();
+    protected Set<String> groupInvalidations = new HashSet<>();
+    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 boolean clearAll;
+
+    public RevisionedCacheRealmProvider(RevisionedRealmCache cache, KeycloakSession session) {
+        this.cache = cache;
+        this.session = session;
+
+        session.getTransaction().enlistAfterCompletion(getTransaction());
+    }
+
+    @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) {
+        realmInvalidations.add(id);
+    }
+
+    @Override
+    public void registerApplicationInvalidation(String id) {
+        appInvalidations.add(id);
+    }
+    @Override
+    public void registerClientTemplateInvalidation(String id) {
+        clientTemplateInvalidations.add(id);
+    }
+
+    @Override
+    public void registerRoleInvalidation(String id) {
+        roleInvalidations.add(id);
+    }
+
+    @Override
+    public void registerGroupInvalidation(String id) {
+        groupInvalidations.add(id);
+
+    }
+
+    protected void runInvalidations() {
+        for (String id : realmInvalidations) {
+            cache.invalidateCachedRealmById(id);
+        }
+        for (String id : roleInvalidations) {
+            cache.invalidateRoleById(id);
+        }
+        for (String id : groupInvalidations) {
+            cache.invalidateGroupById(id);
+        }
+        for (String id : appInvalidations) {
+            cache.invalidateCachedApplicationById(id);
+        }
+        for (String id : clientTemplateInvalidations) {
+            cache.invalidateCachedClientTemplateById(id);
+        }
+    }
+
+    private KeycloakTransaction getTransaction() {
+        return new KeycloakTransaction() {
+            @Override
+            public void begin() {
+                transactionActive = true;
+            }
+
+            @Override
+            public void commit() {
+                if (delegate == null) return;
+                if (clearAll) {
+                    cache.clear();
+                }
+                runInvalidations();
+                transactionActive = false;
+            }
+
+            @Override
+            public void rollback() {
+                setRollbackOnly = true;
+                runInvalidations();
+                transactionActive = false;
+            }
+
+            @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.getCachedRealm(id);
+        if (cached != null) {
+            logger.tracev("by id cache hit: {0}", cached.getName());
+        }
+        if (cached == null) {
+            Long loaded = UpdateCounter.current();
+            RealmModel model = getDelegate().getRealm(id);
+            if (model == null) return null;
+            if (realmInvalidations.contains(id)) return model;
+            cached = new RevisionedCachedRealm(loaded, cache, this, model);
+            logger.tracev("try caching realm: {0} {1}", cached.getName(), loaded);
+            cache.addCachedRealm(cached);
+        } else if (realmInvalidations.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) {
+        CachedRealm cached = cache.getCachedRealmByName(name);
+        if (cached != null) {
+            logger.tracev("by name cache hit: {0}", cached.getName());
+        }
+        if (cached == null) {
+            Long loaded = UpdateCounter.current();
+            RealmModel model = getDelegate().getRealmByName(name);
+            if (model == null) return null;
+            if (realmInvalidations.contains(model.getId())) return model;
+            cached = new RevisionedCachedRealm(loaded, cache, this, model);
+            logger.tracev("try caching realm: {0}", cached.getName());
+            cache.addCachedRealm(cached);
+        } else if (realmInvalidations.contains(cached.getId())) {
+            return getDelegate().getRealmByName(name);
+        } else if (managedRealms.containsKey(cached.getId())) {
+            return managedRealms.get(cached.getId());
+        }
+        RealmAdapter adapter = new RealmAdapter(cached, this);
+        managedRealms.put(cached.getId(), adapter);
+        return adapter;
+    }
+
+    @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) {
+        cache.invalidateCachedRealmById(id);
+
+        RealmModel realm = getDelegate().getRealm(id);
+        Set<RoleModel> realmRoles = null;
+        if (realm != null) {
+            realmRoles = realm.getRoles();
+        }
+
+        boolean didIt = getDelegate().removeRealm(id);
+        realmInvalidations.add(id);
+
+        // TODO: Temporary workaround to invalidate cached realm roles
+        if (didIt && realmRoles != null) {
+            for (RoleModel role : realmRoles) {
+                roleInvalidations.add(role.getId());
+            }
+        }
+
+        return didIt;
+    }
+
+    @Override
+    public void close() {
+        if (delegate != null) delegate.close();
+    }
+
+    @Override
+    public RoleModel getRoleById(String id, RealmModel realm) {
+        CachedRole cached = cache.getRole(id);
+        if (cached != null && !cached.getRealm().equals(realm.getId())) {
+            cached = null;
+        }
+
+        if (cached == null) {
+            Long loaded = UpdateCounter.current();
+            RoleModel model = getDelegate().getRoleById(id, realm);
+            if (model == null) return null;
+            if (roleInvalidations.contains(id)) return model;
+            if (model.getContainer() instanceof ClientModel) {
+                cached = new RevisionedCachedClientRole(loaded, ((ClientModel) model.getContainer()).getId(), model, realm);
+            } else {
+                cached = new RevisionedCachedRealmRole(loaded, model, realm);
+            }
+            cache.addCachedRole(cached);
+
+        } else if (roleInvalidations.contains(id)) {
+            return getDelegate().getRoleById(id, realm);
+        } else if (managedRoles.containsKey(id)) {
+            return managedRoles.get(id);
+        }
+        RoleAdapter adapter = new RoleAdapter(cached, cache, this, realm);
+        managedRoles.put(id, adapter);
+        return adapter;
+    }
+
+    @Override
+    public GroupModel getGroupById(String id, RealmModel realm) {
+        CachedGroup cached = cache.getGroup(id);
+        if (cached != null && !cached.getRealm().equals(realm.getId())) {
+            cached = null;
+        }
+
+        if (cached == null) {
+            Long loaded = UpdateCounter.current();
+            GroupModel model = getDelegate().getGroupById(id, realm);
+            if (model == null) return null;
+            if (groupInvalidations.contains(id)) return model;
+            cached = new RevisionedCachedGroup(loaded, realm, model);
+            cache.addCachedGroup(cached);
+
+        } else if (groupInvalidations.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.getApplication(id);
+        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 = UpdateCounter.current();
+            ClientModel model = getDelegate().getClientById(id, realm);
+            if (model == null) return null;
+            if (appInvalidations.contains(id)) return model;
+            cached = new RevisionedCachedClient(loaded, cache, getDelegate(), realm, model);
+            cache.addCachedClient(cached);
+        } else if (appInvalidations.contains(id)) {
+            return getDelegate().getClientById(id, realm);
+        } else if (managedApplications.containsKey(id)) {
+            return managedApplications.get(id);
+        }
+        ClientAdapter adapter = new ClientAdapter(realm, cached, this, cache);
+        managedApplications.put(id, adapter);
+        return adapter;
+    }
+    @Override
+    public ClientTemplateModel getClientTemplateById(String id, RealmModel realm) {
+        CachedClientTemplate cached = cache.getClientTemplate(id);
+        if (cached != null && !cached.getRealm().equals(realm.getId())) {
+            cached = null;
+        }
+
+        if (cached == null) {
+            Long loaded = UpdateCounter.current();
+            ClientTemplateModel model = getDelegate().getClientTemplateById(id, realm);
+            if (model == null) return null;
+            if (clientTemplateInvalidations.contains(id)) return model;
+            cached = new RevisionedCachedClientTemplate(loaded, cache, getDelegate(), realm, model);
+            cache.addCachedClientTemplate(cached);
+        } else if (clientTemplateInvalidations.contains(id)) {
+            return getDelegate().getClientTemplateById(id, realm);
+        } else if (managedClientTemplates.containsKey(id)) {
+            return managedClientTemplates.get(id);
+        }
+        ClientTemplateModel adapter = new ClientTemplateAdapter(realm, cached, this, cache);
+        managedClientTemplates.put(id, adapter);
+        return adapter;
+    }
+
+}
diff --git a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/counter/RevisionedCacheRealmProviderFactory.java b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/counter/RevisionedCacheRealmProviderFactory.java
new file mode 100755
index 0000000..d50eb0b
--- /dev/null
+++ b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/counter/RevisionedCacheRealmProviderFactory.java
@@ -0,0 +1,161 @@
+/*
+ * 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.counter;
+
+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;
+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;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
+ */
+public class RevisionedCacheRealmProviderFactory implements CacheRealmProviderFactory {
+
+    private static final Logger log = Logger.getLogger(RevisionedCacheRealmProviderFactory.class);
+
+    protected volatile RevisionedRealmCache realmCache;
+
+    protected final ConcurrentHashMap<String, String> realmLookup = new ConcurrentHashMap<>();
+
+    @Override
+    public CacheRealmProvider create(KeycloakSession session) {
+        lazyInit(session);
+        return new RevisionedCacheRealmProvider(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<String, Long> counterCache = session.getProvider(InfinispanConnectionProvider.class).getCache(RevisionedConnectionProviderFactory.VERSION_CACHE_NAME);
+                    cache.addListener(new CacheListener());
+                    realmCache = new RevisionedRealmCache(cache, counterCache, realmLookup);
+                }
+            }
+        }
+    }
+
+    @Override
+    public void init(Config.Scope config) {
+    }
+
+    @Override
+    public void postInit(KeycloakSessionFactory factory) {
+
+    }
+
+    @Override
+    public void close() {
+    }
+
+    @Override
+    public String getId() {
+        return "infinispan-revisioned";
+    }
+
+    @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.evictCachedRoleById(r);
+                }
+
+                for (String c : realm.getClients().values()) {
+                    realmCache.evictCachedApplicationById(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.evictCachedRoleById(r);
+                }
+
+                log.tracev("Client removed client={0}", client.getId());
+            }
+        }
+    }
+}
diff --git a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/counter/RevisionedConnectionProviderFactory.java b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/counter/RevisionedConnectionProviderFactory.java
new file mode 100755
index 0000000..845a907
--- /dev/null
+++ b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/counter/RevisionedConnectionProviderFactory.java
@@ -0,0 +1,47 @@
+/*
+ * 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.counter;
+
+import org.infinispan.configuration.cache.Configuration;
+import org.infinispan.configuration.cache.ConfigurationBuilder;
+import org.jboss.logging.Logger;
+import org.keycloak.connections.infinispan.DefaultInfinispanConnectionProviderFactory;
+
+/**
+ * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
+ */
+public class RevisionedConnectionProviderFactory extends DefaultInfinispanConnectionProviderFactory {
+    public static final String VERSION_CACHE_NAME = "realmVersions";
+
+    protected static final Logger logger = Logger.getLogger(RevisionedConnectionProviderFactory.class);
+
+    @Override
+    public String getId() {
+        return "revisioned";
+    }
+
+
+    protected void initEmbedded() {
+        super.initEmbedded();
+        ConfigurationBuilder counterConfigBuilder = new ConfigurationBuilder();
+        Configuration counterCacheConfiguration = counterConfigBuilder.build();
+
+        cacheManager.defineConfiguration(VERSION_CACHE_NAME, counterCacheConfiguration);
+    }
+
+}
diff --git a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/counter/RevisionedRealmCache.java b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/counter/RevisionedRealmCache.java
new file mode 100755
index 0000000..f32836e
--- /dev/null
+++ b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/counter/RevisionedRealmCache.java
@@ -0,0 +1,255 @@
+/*
+ * 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.counter;
+
+import org.infinispan.Cache;
+import org.jboss.logging.Logger;
+import org.keycloak.models.cache.RealmCache;
+import org.keycloak.models.cache.entities.CachedClient;
+import org.keycloak.models.cache.entities.CachedClientTemplate;
+import org.keycloak.models.cache.entities.CachedGroup;
+import org.keycloak.models.cache.entities.CachedRealm;
+import org.keycloak.models.cache.entities.CachedRole;
+
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.atomic.AtomicLong;
+
+/**
+ * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
+ */
+public class RevisionedRealmCache implements RealmCache {
+
+    protected static final Logger logger = Logger.getLogger(RevisionedRealmCache.class);
+
+    protected final Cache<String, Long> revisions;
+    protected final Cache<String, Object> cache;
+
+    protected final ConcurrentHashMap<String, String> realmLookup;
+
+    public RevisionedRealmCache(Cache<String, Object> cache, Cache<String, Long> revisions, ConcurrentHashMap<String, String> realmLookup) {
+        this.cache = cache;
+        this.realmLookup = realmLookup;
+        this.revisions = revisions;
+    }
+
+    public Cache<String, Object> getCache() {
+        return cache;
+    }
+
+    private <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("stale rev: {0} o.rev: {1}", rev.longValue(), oRev);
+            return null;
+        }
+        return o != null && type.isInstance(o) ? type.cast(o) : null;
+    }
+
+    protected Object invalidateObject(String id) {
+        Object removed = cache.remove(id);
+        revisions.put(id, UpdateCounter.next());
+        return removed;
+    }
+
+    protected void addRevisioned(String id, Revisioned object) {
+        Long rev = revisions.get(id);
+        if (rev == null) {
+           logger.tracev("rev was null in addRevisioned, adding one");
+            rev = UpdateCounter.next();
+            revisions.put(id, rev);
+            return;
+        }
+        cache.putForExternalRead(id, object);
+    }
+
+
+
+
+
+
+
+    @Override
+    public void clear() {
+        cache.clear();
+    }
+
+    @Override
+    public CachedRealm getCachedRealm(String id) {
+        return get(id, CachedRealm.class);
+    }
+
+    @Override
+    public void invalidateCachedRealm(CachedRealm realm) {
+        logger.tracev("Invalidating realm {0}", realm.getId());
+        invalidateObject(realm.getId());
+        realmLookup.remove(realm.getName());
+    }
+
+    @Override
+    public void invalidateCachedRealmById(String id) {
+        CachedRealm cached = (CachedRealm) invalidateObject(id);
+        if (cached != null) realmLookup.remove(cached.getName());
+    }
+
+    @Override
+    public void addCachedRealm(CachedRealm realm) {
+        logger.tracev("Adding realm {0}", realm.getId());
+        addRevisioned(realm.getId(), (Revisioned) realm);
+        realmLookup.put(realm.getName(), realm.getId());
+    }
+
+    @Override
+    public CachedRealm getCachedRealmByName(String name) {
+        String id = realmLookup.get(name);
+        return id != null ? getCachedRealm(id) : null;
+    }
+
+    @Override
+    public CachedClient getApplication(String id) {
+        return get(id, CachedClient.class);
+    }
+
+    @Override
+    public void invalidateApplication(CachedClient app) {
+        logger.tracev("Removing application {0}", app.getId());
+        invalidateObject(app.getId());
+    }
+
+    @Override
+    public void addCachedClient(CachedClient app) {
+        logger.tracev("Adding application {0}", app.getId());
+        addRevisioned(app.getId(), (Revisioned) app);
+    }
+
+    @Override
+    public void invalidateCachedApplicationById(String id) {
+        CachedClient client = (CachedClient)invalidateObject(id);
+        if (client != null) logger.tracev("Removing application {0}", client.getClientId());
+    }
+
+    @Override
+    public void evictCachedApplicationById(String id) {
+        logger.tracev("Evicting application {0}", id);
+        cache.evict(id);
+    }
+
+    @Override
+    public CachedGroup getGroup(String id) {
+        return get(id, CachedGroup.class);
+    }
+
+    @Override
+    public void invalidateGroup(CachedGroup role) {
+        logger.tracev("Removing group {0}", role.getId());
+        invalidateObject(role.getId());
+    }
+
+    @Override
+    public void addCachedGroup(CachedGroup role) {
+        logger.tracev("Adding group {0}", role.getId());
+        addRevisioned(role.getId(), (Revisioned) role);
+    }
+
+    @Override
+    public void invalidateCachedGroupById(String id) {
+        logger.tracev("Removing group {0}", id);
+        invalidateObject(id);
+
+    }
+
+    @Override
+    public void invalidateGroupById(String id) {
+        logger.tracev("Removing group {0}", id);
+        invalidateObject(id);
+    }
+
+    @Override
+    public CachedRole getRole(String id) {
+        return get(id, CachedRole.class);
+    }
+
+    @Override
+    public void invalidateRole(CachedRole role) {
+        logger.tracev("Removing role {0}", role.getId());
+        invalidateObject(role.getId());
+    }
+
+    @Override
+    public void invalidateRoleById(String id) {
+        logger.tracev("Removing role {0}", id);
+        invalidateObject(id);
+    }
+
+    @Override
+    public void evictCachedRoleById(String id) {
+        logger.tracev("Evicting role {0}", id);
+        cache.evict(id);
+    }
+
+    @Override
+    public void addCachedRole(CachedRole role) {
+        logger.tracev("Adding role {0}", role.getId());
+        addRevisioned(role.getId(), (Revisioned) role);
+    }
+
+    @Override
+    public void invalidateCachedRoleById(String id) {
+        logger.tracev("Removing role {0}", id);
+        invalidateObject(id);
+    }
+
+    @Override
+    public CachedClientTemplate getClientTemplate(String id) {
+        return get(id, CachedClientTemplate.class);
+    }
+
+    @Override
+    public void invalidateClientTemplate(CachedClientTemplate app) {
+        logger.tracev("Removing client template {0}", app.getId());
+        invalidateObject(app.getId());
+    }
+
+    @Override
+    public void addCachedClientTemplate(CachedClientTemplate app) {
+        logger.tracev("Adding client template {0}", app.getId());
+        addRevisioned(app.getId(), (Revisioned) app);
+    }
+
+    @Override
+    public void invalidateCachedClientTemplateById(String id) {
+        logger.tracev("Removing client template {0}", id);
+        invalidateObject(id);
+    }
+
+    @Override
+    public void evictCachedClientTemplateById(String id) {
+        logger.tracev("Evicting client template {0}", id);
+        cache.evict(id);
+    }
+
+
+}
diff --git a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/counter/UpdateCounter.java b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/counter/UpdateCounter.java
new file mode 100755
index 0000000..88d598b
--- /dev/null
+++ b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/counter/UpdateCounter.java
@@ -0,0 +1,20 @@
+package org.keycloak.models.cache.infinispan.counter;
+
+import java.util.concurrent.atomic.AtomicLong;
+
+/**
+ * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
+ */
+public class UpdateCounter {
+
+    private static final AtomicLong counter = new AtomicLong();
+
+    public static long current() {
+        return counter.get();
+    }
+
+    public static long next() {
+        return counter.incrementAndGet();
+    }
+
+}
\ No newline at end of file
diff --git a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/locking/LockingCacheRealmProvider.java b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/locking/LockingCacheRealmProvider.java
new file mode 100755
index 0000000..ea451b0
--- /dev/null
+++ b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/locking/LockingCacheRealmProvider.java
@@ -0,0 +1,463 @@
+/*
+ * 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.locking;
+
+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.entities.CachedClient;
+import org.keycloak.models.cache.entities.CachedClientTemplate;
+import org.keycloak.models.cache.entities.CachedGroup;
+import org.keycloak.models.cache.entities.CachedRealm;
+import org.keycloak.models.cache.entities.CachedRole;
+import org.keycloak.models.cache.infinispan.ClientAdapter;
+import org.keycloak.models.cache.infinispan.ClientTemplateAdapter;
+import org.keycloak.models.cache.infinispan.GroupAdapter;
+import org.keycloak.models.cache.infinispan.RealmAdapter;
+import org.keycloak.models.cache.infinispan.RoleAdapter;
+import org.keycloak.models.cache.infinispan.counter.entities.RevisionedCachedClient;
+import org.keycloak.models.cache.infinispan.counter.entities.RevisionedCachedClientRole;
+import org.keycloak.models.cache.infinispan.counter.entities.RevisionedCachedClientTemplate;
+import org.keycloak.models.cache.infinispan.counter.entities.RevisionedCachedGroup;
+import org.keycloak.models.cache.infinispan.counter.entities.RevisionedCachedRealm;
+import org.keycloak.models.cache.infinispan.counter.entities.RevisionedCachedRealmRole;
+
+import 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;
+
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public class LockingCacheRealmProvider implements CacheRealmProvider {
+    protected static final Logger logger = Logger.getLogger(LockingCacheRealmProvider.class);
+    protected LockingRealmCache cache;
+    protected KeycloakSession session;
+    protected RealmProvider delegate;
+    protected boolean transactionActive;
+    protected boolean setRollbackOnly;
+
+    protected Set<String> realmInvalidations = new HashSet<>();
+    protected Set<String> appInvalidations = new HashSet<>();
+    protected Set<String> clientTemplateInvalidations = new HashSet<>();
+    protected Set<String> roleInvalidations = new HashSet<>();
+    protected Set<String> groupInvalidations = new HashSet<>();
+    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 boolean clearAll;
+
+    public LockingCacheRealmProvider(LockingRealmCache 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) {
+        realmInvalidations.add(id);
+    }
+
+    @Override
+    public void registerApplicationInvalidation(String id) {
+        appInvalidations.add(id);
+    }
+    @Override
+    public void registerClientTemplateInvalidation(String id) {
+        clientTemplateInvalidations.add(id);
+    }
+
+    @Override
+    public void registerRoleInvalidation(String id) {
+        roleInvalidations.add(id);
+    }
+
+    @Override
+    public void registerGroupInvalidation(String id) {
+        groupInvalidations.add(id);
+
+    }
+
+    protected void runInvalidations() {
+        for (String id : realmInvalidations) {
+            cache.invalidateCachedRealmById(id);
+        }
+        for (String id : roleInvalidations) {
+            cache.invalidateRoleById(id);
+        }
+        for (String id : groupInvalidations) {
+            cache.invalidateGroupById(id);
+        }
+        for (String id : appInvalidations) {
+            cache.invalidateCachedApplicationById(id);
+        }
+        for (String id : clientTemplateInvalidations) {
+            cache.invalidateCachedClientTemplateById(id);
+        }
+    }
+
+    private KeycloakTransaction getPrepareTransaction() {
+        return new KeycloakTransaction() {
+            @Override
+            public void begin() {
+                transactionActive = true;
+            }
+
+            @Override
+            public void commit() {
+                if (delegate == null) return;
+                List<String> invalidates = new LinkedList<>();
+                for (String id : realmInvalidations) {
+                    invalidates.add(id);
+                }
+                for (String id : roleInvalidations) {
+                    invalidates.add(id);
+                }
+                for (String id : groupInvalidations) {
+                    invalidates.add(id);
+                }
+                for (String id : appInvalidations) {
+                    invalidates.add(id);
+                }
+                for (String id : clientTemplateInvalidations) {
+                    invalidates.add(id);
+                }
+
+                Collections.sort(invalidates); // lock ordering
+                cache.getRevisions().startBatch();
+                for (String id : invalidates) {
+                    cache.getRevisions().getAdvancedCache().lock(id);
+                }
+
+            }
+
+            @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.getCachedRealm(id);
+        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 (realmInvalidations.contains(id)) return model;
+            cached = new RevisionedCachedRealm(loaded, cache, this, model);
+            cache.addCachedRealm(cached);
+        } else if (realmInvalidations.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) {
+        CachedRealm cached = cache.getCachedRealmByName(name);
+        if (cached != null) {
+            logger.tracev("by name cache hit: {0}", cached.getName());
+        }
+        if (cached == null) {
+            RealmModel model = getDelegate().getRealmByName(name);
+            if (model == null) return null;
+            if (realmInvalidations.contains(model.getId())) return model;
+            cached = new RevisionedCachedRealm(null, cache, this, model);
+            cache.addCachedRealm(cached);
+        } else if (realmInvalidations.contains(cached.getId())) {
+            return getDelegate().getRealmByName(name);
+        } else if (managedRealms.containsKey(cached.getId())) {
+            return managedRealms.get(cached.getId());
+        }
+        RealmAdapter adapter = new RealmAdapter(cached, this);
+        managedRealms.put(cached.getId(), adapter);
+        return adapter;
+    }
+
+    @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) {
+        cache.invalidateCachedRealmById(id);
+
+        RealmModel realm = getDelegate().getRealm(id);
+        Set<RoleModel> realmRoles = null;
+        if (realm != null) {
+            realmRoles = realm.getRoles();
+        }
+
+        boolean didIt = getDelegate().removeRealm(id);
+        realmInvalidations.add(id);
+
+        // TODO: Temporary workaround to invalidate cached realm roles
+        if (didIt && realmRoles != null) {
+            for (RoleModel role : realmRoles) {
+                roleInvalidations.add(role.getId());
+            }
+        }
+
+        return didIt;
+    }
+
+    @Override
+    public void close() {
+        if (delegate != null) delegate.close();
+    }
+
+    @Override
+    public RoleModel getRoleById(String id, RealmModel realm) {
+        CachedRole cached = cache.getRole(id);
+        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 (roleInvalidations.contains(id)) return model;
+            if (model.getContainer() instanceof ClientModel) {
+                cached = new RevisionedCachedClientRole(loaded, ((ClientModel) model.getContainer()).getId(), model, realm);
+            } else {
+                cached = new RevisionedCachedRealmRole(loaded, model, realm);
+            }
+            cache.addCachedRole(cached);
+
+        } else if (roleInvalidations.contains(id)) {
+            return getDelegate().getRoleById(id, realm);
+        } else if (managedRoles.containsKey(id)) {
+            return managedRoles.get(id);
+        }
+        RoleAdapter adapter = new RoleAdapter(cached, cache, this, realm);
+        managedRoles.put(id, adapter);
+        return adapter;
+    }
+
+    @Override
+    public GroupModel getGroupById(String id, RealmModel realm) {
+        CachedGroup cached = cache.getGroup(id);
+        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 (groupInvalidations.contains(id)) return model;
+            cached = new RevisionedCachedGroup(loaded, realm, model);
+            cache.addCachedGroup(cached);
+
+        } else if (groupInvalidations.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.getApplication(id);
+        if (cached != null && !cached.getRealm().equals(realm.getId())) {
+            cached = null;
+        }
+        if (cached != null && cached.getClientId().equals("client")) {
+            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 (appInvalidations.contains(id)) return model;
+            cached = new RevisionedCachedClient(loaded, cache, getDelegate(), realm, model);
+            cache.addCachedClient(cached);
+        } else if (appInvalidations.contains(id)) {
+            return getDelegate().getClientById(id, realm);
+        } else if (managedApplications.containsKey(id)) {
+            return managedApplications.get(id);
+        }
+        ClientAdapter adapter = new ClientAdapter(realm, cached, this, cache);
+        managedApplications.put(id, adapter);
+        return adapter;
+    }
+    @Override
+    public ClientTemplateModel getClientTemplateById(String id, RealmModel realm) {
+        CachedClientTemplate cached = cache.getClientTemplate(id);
+        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 (clientTemplateInvalidations.contains(id)) return model;
+            cached = new RevisionedCachedClientTemplate(loaded, cache, getDelegate(), realm, model);
+            cache.addCachedClientTemplate(cached);
+        } else if (clientTemplateInvalidations.contains(id)) {
+            return getDelegate().getClientTemplateById(id, realm);
+        } else if (managedClientTemplates.containsKey(id)) {
+            return managedClientTemplates.get(id);
+        }
+        ClientTemplateModel adapter = new ClientTemplateAdapter(realm, cached, this, cache);
+        managedClientTemplates.put(id, adapter);
+        return adapter;
+    }
+
+}
diff --git a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/locking/LockingCacheRealmProviderFactory.java b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/locking/LockingCacheRealmProviderFactory.java
new file mode 100755
index 0000000..f143ea3
--- /dev/null
+++ b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/locking/LockingCacheRealmProviderFactory.java
@@ -0,0 +1,161 @@
+/*
+ * 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.locking;
+
+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;
+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;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
+ */
+public class LockingCacheRealmProviderFactory implements CacheRealmProviderFactory {
+
+    private static final Logger log = Logger.getLogger(LockingCacheRealmProviderFactory.class);
+
+    protected volatile LockingRealmCache realmCache;
+
+    protected final ConcurrentHashMap<String, String> realmLookup = new ConcurrentHashMap<>();
+
+    @Override
+    public CacheRealmProvider create(KeycloakSession session) {
+        lazyInit(session);
+        return new LockingCacheRealmProvider(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<String, Long> counterCache = session.getProvider(InfinispanConnectionProvider.class).getCache(LockingConnectionProviderFactory.VERSION_CACHE_NAME);
+                    cache.addListener(new CacheListener());
+                    realmCache = new LockingRealmCache(cache, counterCache, realmLookup);
+                }
+            }
+        }
+    }
+
+    @Override
+    public void init(Config.Scope config) {
+    }
+
+    @Override
+    public void postInit(KeycloakSessionFactory factory) {
+
+    }
+
+    @Override
+    public void close() {
+    }
+
+    @Override
+    public String getId() {
+        return "infinispan-locking";
+    }
+
+    @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.evictCachedRoleById(r);
+                }
+
+                for (String c : realm.getClients().values()) {
+                    realmCache.evictCachedApplicationById(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.evictCachedRoleById(r);
+                }
+
+                log.tracev("Client removed client={0}", client.getId());
+            }
+        }
+    }
+}
diff --git a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/locking/LockingConnectionProviderFactory.java b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/locking/LockingConnectionProviderFactory.java
new file mode 100755
index 0000000..68da34d
--- /dev/null
+++ b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/locking/LockingConnectionProviderFactory.java
@@ -0,0 +1,54 @@
+/*
+ * 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.locking;
+
+import org.infinispan.configuration.cache.Configuration;
+import org.infinispan.configuration.cache.ConfigurationBuilder;
+import org.infinispan.transaction.LockingMode;
+import org.infinispan.transaction.TransactionMode;
+import org.infinispan.transaction.lookup.DummyTransactionManagerLookup;
+import org.jboss.logging.Logger;
+import org.keycloak.connections.infinispan.DefaultInfinispanConnectionProviderFactory;
+
+/**
+ * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
+ */
+public class LockingConnectionProviderFactory extends DefaultInfinispanConnectionProviderFactory {
+    public static final String VERSION_CACHE_NAME = "realmVersions";
+
+    protected static final Logger logger = Logger.getLogger(LockingConnectionProviderFactory.class);
+
+    @Override
+    public String getId() {
+        return "locking";
+    }
+
+
+    protected void initEmbedded() {
+        super.initEmbedded();
+        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(VERSION_CACHE_NAME, counterCacheConfiguration);
+    }
+
+}
diff --git a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/locking/LockingRealmCache.java b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/locking/LockingRealmCache.java
new file mode 100755
index 0000000..9c8e784
--- /dev/null
+++ b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/locking/LockingRealmCache.java
@@ -0,0 +1,294 @@
+/*
+ * 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.locking;
+
+import org.infinispan.Cache;
+import org.jboss.logging.Logger;
+import org.keycloak.models.cache.RealmCache;
+import org.keycloak.models.cache.entities.CachedClient;
+import org.keycloak.models.cache.entities.CachedClientTemplate;
+import org.keycloak.models.cache.entities.CachedGroup;
+import org.keycloak.models.cache.entities.CachedRealm;
+import org.keycloak.models.cache.entities.CachedRole;
+import org.keycloak.models.cache.infinispan.counter.Revisioned;
+
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.atomic.AtomicLong;
+
+/**
+ * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
+ */
+public class LockingRealmCache implements RealmCache {
+
+    protected static final Logger logger = Logger.getLogger(LockingRealmCache.class);
+
+    protected final Cache<String, Long> revisions;
+    protected final Cache<String, Object> cache;
+    final AtomicLong realmCounter = new AtomicLong();
+    final AtomicLong clientCounter = new AtomicLong();
+    final AtomicLong clientTemplateCounter = new AtomicLong();
+    final AtomicLong roleCounter = new AtomicLong();
+    final AtomicLong groupCounter = new AtomicLong();
+
+    protected final ConcurrentHashMap<String, String> realmLookup;
+
+    public LockingRealmCache(Cache<String, Object> cache, Cache<String, Long> revisions, ConcurrentHashMap<String, String> realmLookup) {
+        this.cache = cache;
+        this.realmLookup = realmLookup;
+        this.revisions = revisions;
+    }
+
+    public Cache<String, Object> getCache() {
+        return cache;
+    }
+
+    public Cache<String, Long> getRevisions() {
+        return revisions;
+    }
+
+    public void startRevisionBatch() {
+        revisions.startBatch();
+    }
+
+    public void endRevisionBatch() {
+        try {
+            revisions.endBatch(true);
+        } catch (Exception e) {
+        }
+
+    }
+
+    private <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;
+    }
+
+    protected Object invalidateObject(String id, AtomicLong counter) {
+        Object removed = cache.remove(id);
+        revisions.put(id, counter.incrementAndGet());
+        return removed;
+    }
+
+    protected void addRevisioned(String id, Revisioned object, AtomicLong counter) {
+        //startRevisionBatch();
+        try {
+            //revisions.getAdvancedCache().lock(id);
+            Long rev = revisions.get(id);
+            if (rev == null) {
+                rev = counter.incrementAndGet();
+                revisions.put(id, rev);
+                return;
+            }
+            revisions.startBatch();
+            revisions.getAdvancedCache().lock(id);
+            rev = revisions.get(id);
+            if (rev == null) {
+                rev = counter.incrementAndGet();
+                revisions.put(id, rev);
+                return;
+            }
+            if (rev.equals(object.getRevision())) {
+                cache.putForExternalRead(id, object);
+            }
+        } finally {
+            endRevisionBatch();
+        }
+
+    }
+
+
+
+
+    public Long getCurrentRevision(String id) {
+        return revisions.get(id);
+    }
+    @Override
+    public void clear() {
+        cache.clear();
+    }
+
+    @Override
+    public CachedRealm getCachedRealm(String id) {
+        return get(id, CachedRealm.class);
+    }
+
+    @Override
+    public void invalidateCachedRealm(CachedRealm realm) {
+        logger.tracev("Invalidating realm {0}", realm.getId());
+        invalidateObject(realm.getId(), realmCounter);
+        realmLookup.remove(realm.getName());
+    }
+
+    @Override
+    public void invalidateCachedRealmById(String id) {
+        CachedRealm cached = (CachedRealm) invalidateObject(id, realmCounter);
+        if (cached != null) realmLookup.remove(cached.getName());
+    }
+
+    @Override
+    public void addCachedRealm(CachedRealm realm) {
+        logger.tracev("Adding realm {0}", realm.getId());
+        addRevisioned(realm.getId(), (Revisioned) realm, realmCounter);
+        realmLookup.put(realm.getName(), realm.getId());
+    }
+
+
+    @Override
+    public CachedRealm getCachedRealmByName(String name) {
+        String id = realmLookup.get(name);
+        return id != null ? getCachedRealm(id) : null;
+    }
+
+    @Override
+    public CachedClient getApplication(String id) {
+        return get(id, CachedClient.class);
+    }
+
+    @Override
+    public void invalidateApplication(CachedClient app) {
+        logger.tracev("Removing application {0}", app.getId());
+        invalidateObject(app.getId(), clientCounter);
+    }
+
+    @Override
+    public void addCachedClient(CachedClient app) {
+        logger.tracev("Adding application {0}", app.getId());
+        addRevisioned(app.getId(), (Revisioned) app, clientCounter);
+    }
+
+    @Override
+    public void invalidateCachedApplicationById(String id) {
+        CachedClient client = (CachedClient)invalidateObject(id, clientCounter);
+        if (client != null) logger.tracev("Removing application {0}", client.getClientId());
+    }
+
+    @Override
+    public void evictCachedApplicationById(String id) {
+        logger.tracev("Evicting application {0}", id);
+        cache.evict(id);
+    }
+
+    @Override
+    public CachedGroup getGroup(String id) {
+        return get(id, CachedGroup.class);
+    }
+
+    @Override
+    public void invalidateGroup(CachedGroup role) {
+        logger.tracev("Removing group {0}", role.getId());
+        invalidateObject(role.getId(), groupCounter);
+    }
+
+    @Override
+    public void addCachedGroup(CachedGroup role) {
+        logger.tracev("Adding group {0}", role.getId());
+        addRevisioned(role.getId(), (Revisioned) role, groupCounter);
+    }
+
+    @Override
+    public void invalidateCachedGroupById(String id) {
+        logger.tracev("Removing group {0}", id);
+        invalidateObject(id, groupCounter);
+
+    }
+
+    @Override
+    public void invalidateGroupById(String id) {
+        logger.tracev("Removing group {0}", id);
+        invalidateObject(id, groupCounter);
+    }
+
+    @Override
+    public CachedRole getRole(String id) {
+        return get(id, CachedRole.class);
+    }
+
+    @Override
+    public void invalidateRole(CachedRole role) {
+        logger.tracev("Removing role {0}", role.getId());
+        invalidateObject(role.getId(), roleCounter);
+    }
+
+    @Override
+    public void invalidateRoleById(String id) {
+        logger.tracev("Removing role {0}", id);
+        invalidateObject(id, roleCounter);
+    }
+
+    @Override
+    public void evictCachedRoleById(String id) {
+        logger.tracev("Evicting role {0}", id);
+        cache.evict(id);
+    }
+
+    @Override
+    public void addCachedRole(CachedRole role) {
+        logger.tracev("Adding role {0}", role.getId());
+        addRevisioned(role.getId(), (Revisioned) role, roleCounter);
+    }
+
+    @Override
+    public void invalidateCachedRoleById(String id) {
+        logger.tracev("Removing role {0}", id);
+        invalidateObject(id, roleCounter);
+    }
+
+    @Override
+    public CachedClientTemplate getClientTemplate(String id) {
+        return get(id, CachedClientTemplate.class);
+    }
+
+    @Override
+    public void invalidateClientTemplate(CachedClientTemplate app) {
+        logger.tracev("Removing client template {0}", app.getId());
+        invalidateObject(app.getId(), clientTemplateCounter);
+    }
+
+    @Override
+    public void addCachedClientTemplate(CachedClientTemplate app) {
+        logger.tracev("Adding client template {0}", app.getId());
+        addRevisioned(app.getId(), (Revisioned) app, clientTemplateCounter);
+    }
+
+    @Override
+    public void invalidateCachedClientTemplateById(String id) {
+        logger.tracev("Removing client template {0}", id);
+        invalidateObject(id, clientTemplateCounter);
+    }
+
+    @Override
+    public void evictCachedClientTemplateById(String id) {
+        logger.tracev("Evicting client template {0}", id);
+        cache.evict(id);
+    }
+
+
+}
diff --git a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/skewed/RepeatableReadWriteSkewCacheRealmProviderFactory.java b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/skewed/RepeatableReadWriteSkewCacheRealmProviderFactory.java
new file mode 100755
index 0000000..7dd49c5
--- /dev/null
+++ b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/skewed/RepeatableReadWriteSkewCacheRealmProviderFactory.java
@@ -0,0 +1,160 @@
+/*
+ * 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.skewed;
+
+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;
+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;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
+ */
+public class RepeatableReadWriteSkewCacheRealmProviderFactory implements CacheRealmProviderFactory {
+
+    private static final Logger log = Logger.getLogger(RepeatableReadWriteSkewCacheRealmProviderFactory.class);
+
+    protected volatile RepeatableReadWriteSkewRealmCache realmCache;
+
+    protected final ConcurrentHashMap<String, String> realmLookup = new ConcurrentHashMap<>();
+
+    @Override
+    public CacheRealmProvider create(KeycloakSession session) {
+        lazyInit(session);
+        return new RepeatableReadWriteSkewRealmCacheProvider(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 RepeatableReadWriteSkewRealmCache(cache, realmLookup);
+                }
+            }
+        }
+    }
+
+    @Override
+    public void init(Config.Scope config) {
+    }
+
+    @Override
+    public void postInit(KeycloakSessionFactory factory) {
+
+    }
+
+    @Override
+    public void close() {
+    }
+
+    @Override
+    public String getId() {
+        return "infinispan-versioned";
+    }
+
+    @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.evictCachedRoleById(r);
+                }
+
+                for (String c : realm.getClients().values()) {
+                    realmCache.evictCachedApplicationById(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.evictCachedRoleById(r);
+                }
+
+                log.tracev("Client removed client={0}", client.getId());
+            }
+        }
+    }
+}
diff --git a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/skewed/RepeatableReadWriteSkewConnectionProviderFactory.java b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/skewed/RepeatableReadWriteSkewConnectionProviderFactory.java
new file mode 100755
index 0000000..b389347
--- /dev/null
+++ b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/skewed/RepeatableReadWriteSkewConnectionProviderFactory.java
@@ -0,0 +1,164 @@
+/*
+ * 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.skewed;
+
+import org.infinispan.configuration.cache.CacheMode;
+import org.infinispan.configuration.cache.Configuration;
+import org.infinispan.configuration.cache.ConfigurationBuilder;
+import org.infinispan.configuration.cache.VersioningScheme;
+import org.infinispan.configuration.global.GlobalConfigurationBuilder;
+import org.infinispan.manager.DefaultCacheManager;
+import org.infinispan.manager.EmbeddedCacheManager;
+import org.infinispan.transaction.lookup.DummyTransactionManagerLookup;
+import org.infinispan.util.concurrent.IsolationLevel;
+import org.jboss.logging.Logger;
+import org.keycloak.Config;
+import org.keycloak.connections.infinispan.DefaultInfinispanConnectionProvider;
+import org.keycloak.connections.infinispan.InfinispanConnectionProvider;
+import org.keycloak.connections.infinispan.InfinispanConnectionProviderFactory;
+import org.keycloak.models.KeycloakSession;
+import org.keycloak.models.KeycloakSessionFactory;
+
+import javax.naming.InitialContext;
+
+/**
+ * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
+ */
+public class RepeatableReadWriteSkewConnectionProviderFactory implements InfinispanConnectionProviderFactory {
+
+    protected static final Logger logger = Logger.getLogger(RepeatableReadWriteSkewConnectionProviderFactory.class);
+
+    private Config.Scope config;
+
+    private EmbeddedCacheManager cacheManager;
+
+    private boolean containerManaged;
+
+    @Override
+    public InfinispanConnectionProvider create(KeycloakSession session) {
+        lazyInit();
+
+        return new DefaultInfinispanConnectionProvider(cacheManager);
+    }
+
+    @Override
+    public void close() {
+        if (cacheManager != null && !containerManaged) {
+            cacheManager.stop();
+        }
+        cacheManager = null;
+    }
+
+    @Override
+    public String getId() {
+        return "versioned";
+    }
+
+    @Override
+    public void init(Config.Scope config) {
+        this.config = config;
+    }
+
+    @Override
+    public void postInit(KeycloakSessionFactory factory) {
+
+    }
+
+    private void lazyInit() {
+        if (cacheManager == null) {
+            synchronized (this) {
+                if (cacheManager == null) {
+                    String cacheContainer = config.get("cacheContainer");
+                    if (cacheContainer != null) {
+                        initContainerManaged(cacheContainer);
+                    } else {
+                        initEmbedded();
+                    }
+                }
+            }
+        }
+    }
+
+    private void initContainerManaged(String cacheContainerLookup) {
+        try {
+            cacheManager = (EmbeddedCacheManager) new InitialContext().lookup(cacheContainerLookup);
+            containerManaged = true;
+
+            logger.debugv("Using container managed Infinispan cache container, lookup={1}", cacheContainerLookup);
+        } catch (Exception e) {
+            throw new RuntimeException("Failed to retrieve cache container", e);
+        }
+    }
+
+    private void initEmbedded() {
+        GlobalConfigurationBuilder gcb = new GlobalConfigurationBuilder();
+
+        boolean clustered = config.getBoolean("clustered", false);
+        boolean async = config.getBoolean("async", true);
+        boolean allowDuplicateJMXDomains = config.getBoolean("allowDuplicateJMXDomains", true);
+
+        if (clustered) {
+            gcb.transport().defaultTransport();
+        }
+        gcb.globalJmxStatistics().allowDuplicateDomains(allowDuplicateJMXDomains);
+
+        cacheManager = new DefaultCacheManager(gcb.build());
+        containerManaged = false;
+
+        logger.debug("Started embedded Infinispan cache container");
+
+        ConfigurationBuilder invalidationConfigBuilder = new ConfigurationBuilder();
+        if (clustered) {
+            invalidationConfigBuilder.clustering().cacheMode(async ? CacheMode.INVALIDATION_ASYNC : CacheMode.INVALIDATION_SYNC);
+        }
+
+        invalidationConfigBuilder.transaction().transactionManagerLookup(new DummyTransactionManagerLookup());
+        invalidationConfigBuilder.locking().isolationLevel(IsolationLevel.REPEATABLE_READ).writeSkewCheck(true).versioning().enable().scheme(VersioningScheme.SIMPLE);
+        cacheManager.defineConfiguration(InfinispanConnectionProvider.REALM_CACHE_NAME, invalidationConfigBuilder.build());
+
+        ConfigurationBuilder userConfigBuilder = new ConfigurationBuilder();
+        if (clustered) {
+            userConfigBuilder.clustering().cacheMode(async ? CacheMode.INVALIDATION_ASYNC : CacheMode.INVALIDATION_SYNC);
+        }
+        Configuration userCacheConfiguration = userConfigBuilder.build();
+
+        cacheManager.defineConfiguration(InfinispanConnectionProvider.USER_CACHE_NAME, userCacheConfiguration);
+
+        ConfigurationBuilder sessionConfigBuilder = new ConfigurationBuilder();
+        if (clustered) {
+            String sessionsMode = config.get("sessionsMode", "distributed");
+            if (sessionsMode.equalsIgnoreCase("replicated")) {
+                sessionConfigBuilder.clustering().cacheMode(async ? CacheMode.REPL_ASYNC : CacheMode.REPL_SYNC);
+            } else if (sessionsMode.equalsIgnoreCase("distributed")) {
+                sessionConfigBuilder.clustering().cacheMode(async ? CacheMode.DIST_ASYNC : CacheMode.DIST_SYNC);
+            } else {
+                throw new RuntimeException("Invalid value for sessionsMode");
+            }
+
+            sessionConfigBuilder.clustering().hash()
+                    .numOwners(config.getInt("sessionsOwners", 2))
+                    .numSegments(config.getInt("sessionsSegments", 60)).build();
+        }
+
+        Configuration sessionCacheConfiguration = sessionConfigBuilder.build();
+        cacheManager.defineConfiguration(InfinispanConnectionProvider.SESSION_CACHE_NAME, sessionCacheConfiguration);
+        cacheManager.defineConfiguration(InfinispanConnectionProvider.OFFLINE_SESSION_CACHE_NAME, sessionCacheConfiguration);
+        cacheManager.defineConfiguration(InfinispanConnectionProvider.LOGIN_FAILURE_CACHE_NAME, sessionCacheConfiguration);
+    }
+
+}
diff --git a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/skewed/RepeatableReadWriteSkewRealmCache.java b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/skewed/RepeatableReadWriteSkewRealmCache.java
new file mode 100755
index 0000000..18e5d86
--- /dev/null
+++ b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/skewed/RepeatableReadWriteSkewRealmCache.java
@@ -0,0 +1,269 @@
+/*
+ * 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.skewed;
+
+import org.infinispan.Cache;
+import org.jboss.logging.Logger;
+import org.keycloak.models.cache.RealmCache;
+import org.keycloak.models.cache.entities.CachedClient;
+import org.keycloak.models.cache.entities.CachedClientTemplate;
+import org.keycloak.models.cache.entities.CachedGroup;
+import org.keycloak.models.cache.entities.CachedRealm;
+import org.keycloak.models.cache.entities.CachedRole;
+
+import javax.transaction.NotSupportedException;
+import javax.transaction.Status;
+import javax.transaction.SystemException;
+import java.util.concurrent.ConcurrentHashMap;
+
+/**
+ * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
+ */
+public class RepeatableReadWriteSkewRealmCache implements RealmCache {
+
+    protected static final Logger logger = Logger.getLogger(RepeatableReadWriteSkewRealmCache.class);
+
+    protected final Cache<String, Object> cache;
+    protected final ConcurrentHashMap<String, String> realmLookup;
+
+    public RepeatableReadWriteSkewRealmCache(Cache<String, Object> cache, ConcurrentHashMap<String, String> realmLookup) {
+        this.cache = cache;
+        this.realmLookup = realmLookup;
+    }
+
+    public Cache<String, Object> getCache() {
+        return cache;
+    }
+
+    public void startBatch() {
+        logger.trace("*** START BATCH ***");
+        try {
+            if (cache.getAdvancedCache().getTransactionManager().getStatus() == Status.STATUS_NO_TRANSACTION) {
+                cache.getAdvancedCache().getTransactionManager().begin();
+            }
+        } catch (NotSupportedException e) {
+            throw new RuntimeException(e);
+        } catch (SystemException e) {
+            throw new RuntimeException(e);
+        }
+
+    }
+
+    public void endBatch(boolean commit) {
+        logger.trace("*** END BATCH ***");
+        try {
+            if (cache.getAdvancedCache().getTransactionManager().getStatus() == Status.STATUS_ACTIVE) {
+                if (commit) {
+                    cache.getAdvancedCache().getTransactionManager().commit();
+
+                } else {
+                    cache.getAdvancedCache().getTransactionManager().rollback();
+
+                }
+            }
+        } catch (Exception e) {
+            //throw new RuntimeException(e);
+        }
+    }
+
+    @Override
+    public void clear() {
+        cache.clear();
+    }
+
+    @Override
+    public CachedRealm getCachedRealm(String id) {
+        return get(id, CachedRealm.class);
+    }
+
+    @Override
+    public void invalidateCachedRealm(CachedRealm realm) {
+        logger.tracev("Invalidating realm {0}", realm.getId());
+        invalidate(realm.getId());
+        realmLookup.remove(realm.getName());
+    }
+
+    protected Object invalidate(String id) {
+        startBatch();
+        Object rtn = cache.remove(id);
+        logger.trace("*** END BATCH ***");
+        try {
+            if (cache.getAdvancedCache().getTransactionManager().getStatus() == Status.STATUS_ACTIVE) {
+                if (true) {
+                    cache.getAdvancedCache().getTransactionManager().commit();
+
+                } else {
+                    cache.getAdvancedCache().getTransactionManager().rollback();
+
+                }
+            }
+        } catch (Exception e) {
+            logger.trace("Failed to commit invalidate");
+        }
+        return rtn;
+    }
+
+    @Override
+    public void invalidateCachedRealmById(String id) {
+        CachedRealm cached = (CachedRealm) invalidate(id);
+        if (cached != null) realmLookup.remove(cached.getName());
+    }
+
+    @Override
+    public void addCachedRealm(CachedRealm realm) {
+        logger.tracev("Adding realm {0}", realm.getId());
+        cache.putForExternalRead(realm.getId(), realm);
+        realmLookup.put(realm.getName(), realm.getId());
+    }
+
+    @Override
+    public CachedRealm getCachedRealmByName(String name) {
+        String id = realmLookup.get(name);
+        return id != null ? getCachedRealm(id) : null;
+    }
+
+    @Override
+    public CachedClient getApplication(String id) {
+        return get(id, CachedClient.class);
+    }
+
+    @Override
+    public void invalidateApplication(CachedClient app) {
+        logger.tracev("Removing application {0}", app.getId());
+        invalidate(app.getId());
+    }
+
+    @Override
+    public void addCachedClient(CachedClient app) {
+        logger.tracev("Adding application {0}", app.getId());
+        cache.putForExternalRead(app.getId(), app);
+    }
+
+    @Override
+    public void invalidateCachedApplicationById(String id) {
+        logger.tracev("Removing application {0}", id);
+        invalidate(id);
+    }
+
+    @Override
+    public void evictCachedApplicationById(String id) {
+        logger.tracev("Evicting application {0}", id);
+        cache.evict(id);
+    }
+
+    @Override
+    public CachedGroup getGroup(String id) {
+        return get(id, CachedGroup.class);
+    }
+
+    @Override
+    public void invalidateGroup(CachedGroup role) {
+        logger.tracev("Removing group {0}", role.getId());
+        invalidate(role.getId());
+    }
+
+    @Override
+    public void addCachedGroup(CachedGroup role) {
+        logger.tracev("Adding group {0}", role.getId());
+        cache.putForExternalRead(role.getId(), role);
+    }
+
+    @Override
+    public void invalidateCachedGroupById(String id) {
+        logger.tracev("Removing group {0}", id);
+        invalidate(id);
+
+    }
+
+    @Override
+    public void invalidateGroupById(String id) {
+        logger.tracev("Removing group {0}", id);
+        invalidate(id);
+    }
+
+    @Override
+    public CachedRole getRole(String id) {
+        return get(id, CachedRole.class);
+    }
+
+    @Override
+    public void invalidateRole(CachedRole role) {
+        logger.tracev("Removing role {0}", role.getId());
+        invalidate(role.getId());
+    }
+
+    @Override
+    public void invalidateRoleById(String id) {
+        logger.tracev("Removing role {0}", id);
+        invalidate(id);
+    }
+
+    @Override
+    public void evictCachedRoleById(String id) {
+        logger.tracev("Evicting role {0}", id);
+        cache.evict(id);
+    }
+
+    @Override
+    public void addCachedRole(CachedRole role) {
+        logger.tracev("Adding role {0}", role.getId());
+        cache.putForExternalRead(role.getId(), role);
+    }
+
+    @Override
+    public void invalidateCachedRoleById(String id) {
+        logger.tracev("Removing role {0}", id);
+        invalidate(id);
+    }
+
+    private <T> T get(String id, Class<T> type) {
+        Object o = cache.get(id);
+        return o != null && type.isInstance(o) ? type.cast(o) : null;
+    }
+
+    @Override
+    public CachedClientTemplate getClientTemplate(String id) {
+        return get(id, CachedClientTemplate.class);
+    }
+
+    @Override
+    public void invalidateClientTemplate(CachedClientTemplate app) {
+        logger.tracev("Removing client template {0}", app.getId());
+        invalidate(app.getId());
+    }
+
+    @Override
+    public void addCachedClientTemplate(CachedClientTemplate app) {
+        logger.tracev("Adding client template {0}", app.getId());
+        cache.putForExternalRead(app.getId(), app);
+    }
+
+    @Override
+    public void invalidateCachedClientTemplateById(String id) {
+        logger.tracev("Removing client template {0}", id);
+        invalidate(id);
+    }
+
+    @Override
+    public void evictCachedClientTemplateById(String id) {
+        logger.tracev("Evicting client template {0}", id);
+        invalidate(id);
+    }
+
+
+}
diff --git a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/skewed/RepeatableReadWriteSkewRealmCacheProvider.java b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/skewed/RepeatableReadWriteSkewRealmCacheProvider.java
new file mode 100755
index 0000000..5ff2ff8
--- /dev/null
+++ b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/skewed/RepeatableReadWriteSkewRealmCacheProvider.java
@@ -0,0 +1,472 @@
+/*
+ * 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.skewed;
+
+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.entities.CachedClient;
+import org.keycloak.models.cache.entities.CachedClientRole;
+import org.keycloak.models.cache.entities.CachedClientTemplate;
+import org.keycloak.models.cache.entities.CachedGroup;
+import org.keycloak.models.cache.entities.CachedRealm;
+import org.keycloak.models.cache.entities.CachedRealmRole;
+import org.keycloak.models.cache.entities.CachedRole;
+import org.keycloak.models.cache.infinispan.ClientAdapter;
+import org.keycloak.models.cache.infinispan.ClientTemplateAdapter;
+import org.keycloak.models.cache.infinispan.GroupAdapter;
+import org.keycloak.models.cache.infinispan.RealmAdapter;
+import org.keycloak.models.cache.infinispan.RoleAdapter;
+
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+
+/**
+ * DO NOT USE THIS!!
+ *
+ * Tries unsuccessfully to use Infinispan with REPEATABLE_READ, write-skew-checking
+ *
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public class RepeatableReadWriteSkewRealmCacheProvider implements CacheRealmProvider {
+    protected static final Logger logger = Logger.getLogger(RepeatableReadWriteSkewRealmCacheProvider.class);
+
+    protected RepeatableReadWriteSkewRealmCache cache;
+    protected KeycloakSession session;
+    protected RealmProvider delegate;
+    protected boolean transactionActive;
+    protected boolean setRollbackOnly;
+
+    protected Set<String> realmInvalidations = new HashSet<>();
+    protected Set<String> appInvalidations = new HashSet<>();
+    protected Set<String> clientTemplateInvalidations = new HashSet<>();
+    protected Set<String> roleInvalidations = new HashSet<>();
+    protected Set<String> groupInvalidations = new HashSet<>();
+    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 boolean clearAll;
+
+    public RepeatableReadWriteSkewRealmCacheProvider(RepeatableReadWriteSkewRealmCache cache, KeycloakSession session) {
+        this.cache = cache;
+        this.session = session;
+
+        session.getTransaction().enlistAfterCompletion(getTransaction());
+    }
+
+    @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) {
+        realmInvalidations.add(id);
+    }
+
+    @Override
+    public void registerApplicationInvalidation(String id) {
+        appInvalidations.add(id);
+    }
+    @Override
+    public void registerClientTemplateInvalidation(String id) {
+        clientTemplateInvalidations.add(id);
+    }
+
+    @Override
+    public void registerRoleInvalidation(String id) {
+        roleInvalidations.add(id);
+    }
+
+    @Override
+    public void registerGroupInvalidation(String id) {
+        groupInvalidations.add(id);
+
+    }
+
+    protected void runInvalidations() {
+        for (String id : realmInvalidations) {
+            cache.invalidateCachedRealmById(id);
+        }
+        for (String id : roleInvalidations) {
+            cache.invalidateRoleById(id);
+        }
+        for (String id : groupInvalidations) {
+            cache.invalidateGroupById(id);
+        }
+        for (String id : appInvalidations) {
+            cache.invalidateCachedApplicationById(id);
+        }
+        for (String id : clientTemplateInvalidations) {
+            cache.invalidateCachedClientTemplateById(id);
+        }
+    }
+
+    private KeycloakTransaction getTransaction() {
+        return new KeycloakTransaction() {
+            @Override
+            public void begin() {
+                transactionActive = true;
+            }
+
+            @Override
+            public void commit() {
+                if (delegate == null) return;
+                if (clearAll) {
+                    cache.clear();
+                }
+                runInvalidations();
+                transactionActive = false;
+            }
+
+            @Override
+            public void rollback() {
+                setRollbackOnly = true;
+                runInvalidations();
+                transactionActive = false;
+            }
+
+            @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) {
+        //cache.startBatch();
+        cache.startBatch();
+        boolean batchEnded = false;
+        try {
+            CachedRealm cached = cache.getCachedRealm(id);
+            boolean wasNull = cached == null;
+            if (cached == null) {
+                RealmModel model = getDelegate().getRealm(id);
+                if (model == null) return null;
+                if (realmInvalidations.contains(id)) return model;
+                cached = new CachedRealm(cache, this, model);
+                cache.addCachedRealm(cached);
+                try {
+                    batchEnded = true;
+                    cache.endBatch(true);
+                    logger.trace("returning new cached realm");
+                } catch (Exception exception) {
+                    logger.trace("failed to add to cache", exception);
+                    return model;
+                }
+            } else if (realmInvalidations.contains(id)) {
+                return getDelegate().getRealm(id);
+            } else if (managedRealms.containsKey(id)) {
+                return managedRealms.get(id);
+            }
+            if (!wasNull) logger.trace("returning cached realm: " + cached.getName());
+            RealmAdapter adapter = new RealmAdapter(cached, this);
+            managedRealms.put(id, adapter);
+            return adapter;
+        } finally {
+            if (!batchEnded) cache.endBatch(true);
+        }
+    }
+
+    @Override
+    public RealmModel getRealmByName(String name) {
+        cache.startBatch();
+        boolean batchEnded = false;
+        try {
+            CachedRealm cached = cache.getCachedRealmByName(name);
+            boolean wasNull = cached == null;
+            if (cached == null) {
+                RealmModel model = getDelegate().getRealmByName(name);
+                if (model == null) return null;
+                if (realmInvalidations.contains(model.getId())) return model;
+                cached = new CachedRealm(cache, this, model);
+                cache.addCachedRealm(cached);
+                try {
+                    batchEnded = true;
+                    cache.endBatch(true);
+                    logger.trace("returning new cached realm: " + cached.getName());
+                } catch (Exception exception) {
+                    logger.trace("failed to add to cache", exception);
+                    return model;
+                }
+            } else if (realmInvalidations.contains(cached.getId())) {
+                return getDelegate().getRealmByName(name);
+            } else if (managedRealms.containsKey(cached.getId())) {
+                return managedRealms.get(cached.getId());
+            }
+            if (!wasNull) logger.trace("returning cached realm: " + cached.getName());
+            RealmAdapter adapter = new RealmAdapter(cached, this);
+            managedRealms.put(cached.getId(), adapter);
+            return adapter;
+        } finally {
+            if (!batchEnded) cache.endBatch(true);
+
+        }
+    }
+
+    @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) {
+        cache.invalidateCachedRealmById(id);
+
+        RealmModel realm = getDelegate().getRealm(id);
+        Set<RoleModel> realmRoles = null;
+        if (realm != null) {
+            realmRoles = realm.getRoles();
+        }
+
+        boolean didIt = getDelegate().removeRealm(id);
+        realmInvalidations.add(id);
+
+        // TODO: Temporary workaround to invalidate cached realm roles
+        if (didIt && realmRoles != null) {
+            for (RoleModel role : realmRoles) {
+                roleInvalidations.add(role.getId());
+            }
+        }
+
+        return didIt;
+    }
+
+    @Override
+    public void close() {
+        if (delegate != null) delegate.close();
+    }
+
+    @Override
+    public RoleModel getRoleById(String id, RealmModel realm) {
+        cache.startBatch();
+        boolean batchEnded = false;
+        try {
+            CachedRole cached = cache.getRole(id);
+            if (cached != null && !cached.getRealm().equals(realm.getId())) {
+                cached = null;
+            }
+
+            if (cached == null) {
+                RoleModel model = getDelegate().getRoleById(id, realm);
+                if (model == null) return null;
+                if (roleInvalidations.contains(id)) return model;
+                if (model.getContainer() instanceof ClientModel) {
+                    cached = new CachedClientRole(((ClientModel) model.getContainer()).getId(), model, realm);
+                } else {
+                    cached = new CachedRealmRole(model, realm);
+                }
+                cache.addCachedRole(cached);
+                try {
+                    batchEnded = true;
+                    cache.endBatch(true);
+                } catch (Exception exception) {
+                    logger.trace("failed to add to cache", exception);
+                    return model;
+                }
+
+            } else if (roleInvalidations.contains(id)) {
+                return getDelegate().getRoleById(id, realm);
+            } else if (managedRoles.containsKey(id)) {
+                return managedRoles.get(id);
+            }
+            RoleAdapter adapter = new RoleAdapter(cached, cache, this, realm);
+            managedRoles.put(id, adapter);
+            return adapter;
+        } finally {
+            if (!batchEnded) cache.endBatch(true);
+
+        }
+    }
+
+    @Override
+    public GroupModel getGroupById(String id, RealmModel realm) {
+        cache.startBatch();
+        boolean batchEnded = false;
+        try {
+            CachedGroup cached = cache.getGroup(id);
+            if (cached != null && !cached.getRealm().equals(realm.getId())) {
+                cached = null;
+            }
+
+            if (cached == null) {
+                GroupModel model = getDelegate().getGroupById(id, realm);
+                if (model == null) return null;
+                if (groupInvalidations.contains(id)) return model;
+                cached = new CachedGroup(realm, model);
+                cache.addCachedGroup(cached);
+                try {
+                    batchEnded = true;
+                    cache.endBatch(true);
+                } catch (Exception exception) {
+                    logger.trace("failed to add to cache", exception);
+                    return model;
+                }
+
+            } else if (groupInvalidations.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;
+        } finally {
+            if (!batchEnded) cache.endBatch(true);
+
+        }
+    }
+
+    @Override
+    public ClientModel getClientById(String id, RealmModel realm) {
+        cache.startBatch();
+        boolean batchEnded = false;
+        CachedClient cached = cache.getApplication(id);
+        try {
+            if (cached != null && !cached.getRealm().equals(realm.getId())) {
+                cached = null;
+            }
+
+            if (cached == null) {
+                ClientModel model = getDelegate().getClientById(id, realm);
+                if (model == null) return null;
+                if (appInvalidations.contains(id)) return model;
+                cached = new CachedClient(cache, getDelegate(), realm, model);
+                cache.addCachedClient(cached);
+                try {
+                    batchEnded = true;
+                    cache.endBatch(true);
+                } catch (Exception exception) {
+                    logger.trace("failed to add to cache", exception);
+                    return model;
+                }
+            } else if (appInvalidations.contains(id)) {
+                return getDelegate().getClientById(id, realm);
+            } else if (managedApplications.containsKey(id)) {
+                return managedApplications.get(id);
+            }
+            ClientAdapter adapter = new ClientAdapter(realm, cached, this, cache);
+            managedApplications.put(id, adapter);
+            return adapter;
+        } finally {
+            if (!batchEnded) cache.endBatch(true);
+
+        }
+    }
+    @Override
+    public ClientTemplateModel getClientTemplateById(String id, RealmModel realm) {
+        cache.startBatch();
+        boolean batchEnded = false;
+        try {
+            CachedClientTemplate cached = cache.getClientTemplate(id);
+            if (cached != null && !cached.getRealm().equals(realm.getId())) {
+                cached = null;
+            }
+
+            if (cached == null) {
+                ClientTemplateModel model = getDelegate().getClientTemplateById(id, realm);
+                if (model == null) return null;
+                if (clientTemplateInvalidations.contains(id)) return model;
+                cached = new CachedClientTemplate(cache, getDelegate(), realm, model);
+                cache.addCachedClientTemplate(cached);
+                try {
+                    batchEnded = true;
+                    cache.endBatch(true);
+                } catch (Exception exception) {
+                    logger.trace("failed to add to cache", exception);
+                    return model;
+                }
+            } else if (clientTemplateInvalidations.contains(id)) {
+                return getDelegate().getClientTemplateById(id, realm);
+            } else if (managedClientTemplates.containsKey(id)) {
+                return managedClientTemplates.get(id);
+            }
+            ClientTemplateModel adapter = new ClientTemplateAdapter(realm, cached, this, cache);
+            managedClientTemplates.put(id, adapter);
+            return adapter;
+        } finally {
+            if (!batchEnded) cache.endBatch(true);
+
+        }
+    }
+
+}
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
old mode 100644
new mode 100755
index 58a6bda..ef880ec
--- 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
@@ -15,4 +15,6 @@
 # limitations under the License.
 #
 
-org.keycloak.connections.infinispan.DefaultInfinispanConnectionProviderFactory
\ No newline at end of file
+org.keycloak.connections.infinispan.DefaultInfinispanConnectionProviderFactory
+org.keycloak.models.cache.infinispan.counter.RevisionedConnectionProviderFactory
+org.keycloak.models.cache.infinispan.locking.LockingConnectionProviderFactory
\ No newline at end of file
diff --git a/model/infinispan/src/main/resources/META-INF/services/org.keycloak.models.cache.CacheRealmProviderFactory b/model/infinispan/src/main/resources/META-INF/services/org.keycloak.models.cache.CacheRealmProviderFactory
index 1b79a2f..6554765 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
@@ -15,4 +15,6 @@
 # limitations under the License.
 #
 
-org.keycloak.models.cache.infinispan.InfinispanCacheRealmProviderFactory
\ No newline at end of file
+org.keycloak.models.cache.infinispan.InfinispanCacheRealmProviderFactory
+org.keycloak.models.cache.infinispan.counter.RevisionedCacheRealmProviderFactory
+org.keycloak.models.cache.infinispan.locking.LockingCacheRealmProviderFactory
\ No newline at end of file
diff --git a/model/infinispan/src/test/java/org/keycloak/models/sessions/infinispan/initializer/ConcurrencyLockingTest.java b/model/infinispan/src/test/java/org/keycloak/models/sessions/infinispan/initializer/ConcurrencyLockingTest.java
new file mode 100755
index 0000000..aa26b5f
--- /dev/null
+++ b/model/infinispan/src/test/java/org/keycloak/models/sessions/infinispan/initializer/ConcurrencyLockingTest.java
@@ -0,0 +1,88 @@
+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.cache.VersioningScheme;
+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.infinispan.util.concurrent.IsolationLevel;
+import org.junit.Ignore;
+import org.junit.Test;
+import org.keycloak.connections.infinispan.InfinispanConnectionProvider;
+
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+@Ignore
+public class ConcurrencyLockingTest {
+
+    @Test
+    public void testLocking() throws Exception {
+        final DefaultCacheManager cacheManager = getVersionedCacheManager();
+        Cache<String, String> cache = cacheManager.getCache("COUNTER_CACHE");
+        cache.put("key", "init");
+        ExecutorService executor = Executors.newSingleThreadExecutor();
+        executor.execute(new Runnable() {
+            @Override
+            public void run() {
+                Cache<String, String> cache = cacheManager.getCache("COUNTER_CACHE");
+                cache.startBatch();
+                System.out.println("thread lock");
+                cache.getAdvancedCache().lock("key");
+                try {
+                    Thread.sleep(100000);
+                } catch (InterruptedException e) {
+                    throw new RuntimeException(e);
+                }
+                cache.endBatch(true);
+
+            }
+        });
+        Thread.sleep(10);
+        cache.startBatch();
+        cache.getAdvancedCache().lock("key");
+        cache.put("key", "1234");
+        System.out.println("after put");
+        cache.endBatch(true);
+
+        Thread.sleep(1000000);
+
+
+
+    }
+
+    protected DefaultCacheManager getVersionedCacheManager() {
+        GlobalConfigurationBuilder gcb = new GlobalConfigurationBuilder();
+
+
+        boolean allowDuplicateJMXDomains = true;
+
+        gcb.globalJmxStatistics().allowDuplicateDomains(allowDuplicateJMXDomains);
+
+        final DefaultCacheManager cacheManager = new DefaultCacheManager(gcb.build());
+        ConfigurationBuilder invalidationConfigBuilder = new ConfigurationBuilder();
+        Configuration invalidationCacheConfiguration = invalidationConfigBuilder.build();
+        cacheManager.defineConfiguration(InfinispanConnectionProvider.REALM_CACHE_NAME, invalidationCacheConfiguration);
+
+        ConfigurationBuilder counterConfigBuilder = new ConfigurationBuilder();
+        counterConfigBuilder.invocationBatching().enable();
+        counterConfigBuilder.transaction().transactionManagerLookup(new DummyTransactionManagerLookup());
+        counterConfigBuilder.transaction().lockingMode(LockingMode.PESSIMISTIC);
+        Configuration counterCacheConfiguration = counterConfigBuilder.build();
+
+        cacheManager.defineConfiguration("COUNTER_CACHE", counterCacheConfiguration);
+        return cacheManager;
+    }
+
+}
diff --git a/model/infinispan/src/test/java/org/keycloak/models/sessions/infinispan/initializer/ConcurrencyVersioningTest.java b/model/infinispan/src/test/java/org/keycloak/models/sessions/infinispan/initializer/ConcurrencyVersioningTest.java
new file mode 100755
index 0000000..48a0e36
--- /dev/null
+++ b/model/infinispan/src/test/java/org/keycloak/models/sessions/infinispan/initializer/ConcurrencyVersioningTest.java
@@ -0,0 +1,269 @@
+package org.keycloak.models.sessions.infinispan.initializer;
+
+import org.infinispan.Cache;
+import org.infinispan.commons.CacheException;
+import org.infinispan.configuration.cache.CacheMode;
+import org.infinispan.configuration.cache.Configuration;
+import org.infinispan.configuration.cache.ConfigurationBuilder;
+import org.infinispan.configuration.cache.VersioningScheme;
+import org.infinispan.configuration.global.GlobalConfigurationBuilder;
+import org.infinispan.manager.DefaultCacheManager;
+import org.infinispan.manager.EmbeddedCacheManager;
+import org.infinispan.transaction.TransactionMode;
+import org.infinispan.transaction.TransactionProtocol;
+import org.infinispan.transaction.lookup.DummyTransactionManagerLookup;
+import org.infinispan.util.concurrent.IsolationLevel;
+import org.junit.Assert;
+import org.junit.Ignore;
+import org.junit.Test;
+import org.keycloak.connections.infinispan.InfinispanConnectionProvider;
+
+import javax.transaction.NotSupportedException;
+import javax.transaction.Status;
+import javax.transaction.SystemException;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+
+/**
+ * Unit tests to make sure our model caching concurrency model will work.
+ *
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+@Ignore
+public class ConcurrencyVersioningTest {
+
+    public static abstract class AbstractThread implements Runnable {
+        EmbeddedCacheManager cacheManager;
+        boolean success;
+        CountDownLatch latch = new CountDownLatch(1);
+
+        public AbstractThread(EmbeddedCacheManager cacheManager) {
+            this.cacheManager = cacheManager;
+        }
+
+        public boolean isSuccess() {
+            return success;
+        }
+
+        public CountDownLatch getLatch() {
+            return latch;
+        }
+    }
+
+    public static class RemoveThread extends AbstractThread {
+        public RemoveThread(EmbeddedCacheManager cacheManager) {
+            super(cacheManager);
+        }
+
+        public void run() {
+            Cache<String, String> cache = cacheManager.getCache(InfinispanConnectionProvider.REALM_CACHE_NAME);
+            try {
+                startBatch(cache);
+                cache.remove("key");
+                //cache.getAdvancedCache().getTransactionManager().commit();
+                endBatch(cache);
+                success = true;
+            } catch (Exception e) {
+                success = false;
+            }
+            latch.countDown();
+        }
+
+    }
+
+
+    public static class UpdateThread extends AbstractThread {
+        public UpdateThread(EmbeddedCacheManager cacheManager) {
+            super(cacheManager);
+        }
+
+        public void run() {
+            Cache<String, String> cache = cacheManager.getCache(InfinispanConnectionProvider.REALM_CACHE_NAME);
+            try {
+                startBatch(cache);
+                cache.putForExternalRead("key", "value2");
+                //cache.getAdvancedCache().getTransactionManager().commit();
+                endBatch(cache);
+                success = true;
+            } catch (Exception e) {
+                success = false;
+            }
+            latch.countDown();
+        }
+
+    }
+
+    /**
+     * Tests that if remove executes before put, then put still succeeds.
+     *
+     * @throws Exception
+     */
+    @Test
+    public void testGetRemovePutOnNonExisting() throws Exception {
+        final DefaultCacheManager cacheManager = getVersionedCacheManager();
+        ExecutorService executor = Executors.newSingleThreadExecutor();
+
+        RemoveThread removeThread = new RemoveThread(cacheManager);
+
+        Cache<String, String> cache = cacheManager.getCache(InfinispanConnectionProvider.REALM_CACHE_NAME);
+        cache.remove("key");
+        startBatch(cache);
+        cache.get("key");
+        executor.execute(removeThread);
+        removeThread.getLatch().await();
+        cache.putForExternalRead("key", "value1");
+        endBatch(cache);
+        Assert.assertEquals(cache.get("key"), "value1");
+        Assert.assertTrue(removeThread.isSuccess());
+    }
+
+
+    /**
+     * Test that if a put of an existing key is removed after the put and before tx commit, it is evicted
+     *
+     * @throws Exception
+     */
+    @Test
+    public void testGetRemovePutOnExisting() throws Exception {
+        final DefaultCacheManager cacheManager = getVersionedCacheManager();
+        ExecutorService executor = Executors.newSingleThreadExecutor();
+
+        RemoveThread removeThread = new RemoveThread(cacheManager);
+
+        Cache<String, String> cache = cacheManager.getCache(InfinispanConnectionProvider.REALM_CACHE_NAME);
+        cache.put("key", "value0");
+        startBatch(cache);
+        cache.get("key");
+        executor.execute(removeThread);
+        removeThread.getLatch().await();
+        cache.put("key", "value1");
+        try {
+            endBatch(cache);
+            Assert.fail("Write skew should be detected");
+        } catch (Exception e) {
+
+
+        }
+        Assert.assertNull(cache.get("key"));
+        Assert.assertTrue(removeThread.isSuccess());
+    }
+
+    /**
+     * Test that if a put of an existing key is removed after the put and before tx commit, it is evicted
+     *
+     * @throws Exception
+     */
+    @Test
+    public void testGetRemovePutEternalOnExisting() throws Exception {
+        final DefaultCacheManager cacheManager = getVersionedCacheManager();
+        ExecutorService executor = Executors.newSingleThreadExecutor();
+
+        RemoveThread removeThread = new RemoveThread(cacheManager);
+
+        Cache<String, String> cache = cacheManager.getCache(InfinispanConnectionProvider.REALM_CACHE_NAME);
+        cache.put("key", "value0");
+        startBatch(cache);
+        cache.get("key");
+        executor.execute(removeThread);
+        cache.putForExternalRead("key", "value1");
+        removeThread.getLatch().await();
+        try {
+            endBatch(cache);
+//            Assert.fail("Write skew should be detected");
+        } catch (Exception e) {
+
+        }
+        Assert.assertNull(cache.get("key"));
+        Assert.assertTrue(removeThread.isSuccess());
+    }
+
+    @Test
+    public void testPutExternalRemoveOnExisting() throws Exception {
+        final DefaultCacheManager cacheManager = getVersionedCacheManager();
+        ExecutorService executor = Executors.newSingleThreadExecutor();
+
+        RemoveThread removeThread = new RemoveThread(cacheManager);
+
+        Cache<String, String> cache = cacheManager.getCache(InfinispanConnectionProvider.REALM_CACHE_NAME);
+        cache.put("key", "value0");
+        startBatch(cache);
+        cache.putForExternalRead("key", "value1");
+        executor.execute(removeThread);
+        removeThread.getLatch().await();
+        try {
+            endBatch(cache);
+//            Assert.fail("Write skew should be detected");
+        } catch (Exception e) {
+
+        }
+        Assert.assertNull(cache.get("key"));
+        Assert.assertTrue(removeThread.isSuccess());
+    }
+
+
+    public static void startBatch(Cache<String, String> cache) {
+        try {
+            if (cache.getAdvancedCache().getTransactionManager().getStatus() == Status.STATUS_NO_TRANSACTION) {
+                System.out.println("begin");
+                cache.getAdvancedCache().getTransactionManager().begin();
+            }
+        } catch (NotSupportedException e) {
+            throw new RuntimeException(e);
+        } catch (SystemException e) {
+            throw new RuntimeException(e);
+        }
+
+    }
+
+    public static void endBatch(Cache<String, String> cache) {
+        boolean commit = true;
+        try {
+            if (cache.getAdvancedCache().getTransactionManager().getStatus() == Status.STATUS_ACTIVE) {
+                if (commit) {
+                    cache.getAdvancedCache().getTransactionManager().commit();
+
+                } else {
+                    cache.getAdvancedCache().getTransactionManager().rollback();
+
+                }
+            }
+        } catch (Exception e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+
+    protected DefaultCacheManager getVersionedCacheManager() {
+        GlobalConfigurationBuilder gcb = new GlobalConfigurationBuilder();
+
+
+        boolean clustered = false;
+        boolean async = false;
+        boolean allowDuplicateJMXDomains = true;
+
+        if (clustered) {
+            gcb.transport().defaultTransport();
+        }
+        gcb.globalJmxStatistics().allowDuplicateDomains(allowDuplicateJMXDomains);
+
+        final DefaultCacheManager cacheManager = new DefaultCacheManager(gcb.build());
+        ConfigurationBuilder invalidationConfigBuilder = new ConfigurationBuilder();
+        invalidationConfigBuilder
+                //.invocationBatching().enable()
+                .transaction().transactionMode(TransactionMode.TRANSACTIONAL)
+                .transaction().transactionManagerLookup(new DummyTransactionManagerLookup())
+                .locking().isolationLevel(IsolationLevel.REPEATABLE_READ).writeSkewCheck(true).versioning().enable().scheme(VersioningScheme.SIMPLE);
+
+
+        //invalidationConfigBuilder.locking().isolationLevel(IsolationLevel.REPEATABLE_READ).writeSkewCheck(true).versioning().enable().scheme(VersioningScheme.SIMPLE);
+
+        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;
+    }
+}
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 5453164..311540b 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
@@ -633,8 +633,8 @@ public class ClientAdapter implements ClientModel {
         roleEntity.setClient(entity);
         roleEntity.setClientRole(true);
         roleEntity.setRealmId(realm.getId());
-        em.persist(roleEntity);
         entity.getRoles().add(roleEntity);
+        em.persist(roleEntity);
         em.flush();
         return new RoleAdapter(realm, em, roleEntity);
     }
@@ -667,12 +667,21 @@ 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;
+
     }
 
     @Override
diff --git a/model/jpa/src/main/java/org/keycloak/models/jpa/entities/AuthenticationExecutionEntity.java b/model/jpa/src/main/java/org/keycloak/models/jpa/entities/AuthenticationExecutionEntity.java
index 71ecd94..8e759a4 100755
--- a/model/jpa/src/main/java/org/keycloak/models/jpa/entities/AuthenticationExecutionEntity.java
+++ b/model/jpa/src/main/java/org/keycloak/models/jpa/entities/AuthenticationExecutionEntity.java
@@ -19,6 +19,8 @@ package org.keycloak.models.jpa.entities;
 
 import org.keycloak.models.AuthenticationExecutionModel;
 
+import javax.persistence.Access;
+import javax.persistence.AccessType;
 import javax.persistence.Column;
 import javax.persistence.Entity;
 import javax.persistence.FetchType;
@@ -43,6 +45,7 @@ import javax.persistence.Table;
 public class AuthenticationExecutionEntity {
     @Id
     @Column(name="ID", length = 36)
+    @Access(AccessType.PROPERTY) // we do this because relationships often fetch id, but not entity.  This avoids an extra SQL
     protected String id;
 
     @ManyToOne(fetch = FetchType.LAZY)
diff --git a/model/jpa/src/main/java/org/keycloak/models/jpa/entities/AuthenticationFlowEntity.java b/model/jpa/src/main/java/org/keycloak/models/jpa/entities/AuthenticationFlowEntity.java
index 886dd43..402acf1 100755
--- a/model/jpa/src/main/java/org/keycloak/models/jpa/entities/AuthenticationFlowEntity.java
+++ b/model/jpa/src/main/java/org/keycloak/models/jpa/entities/AuthenticationFlowEntity.java
@@ -17,6 +17,8 @@
 
 package org.keycloak.models.jpa.entities;
 
+import javax.persistence.Access;
+import javax.persistence.AccessType;
 import javax.persistence.CascadeType;
 import javax.persistence.Column;
 import javax.persistence.Entity;
@@ -44,6 +46,7 @@ import java.util.Collection;
 public class AuthenticationFlowEntity {
     @Id
     @Column(name="ID", length = 36)
+    @Access(AccessType.PROPERTY) // we do this because relationships often fetch id, but not entity.  This avoids an extra SQL
     protected String id;
 
     @ManyToOne(fetch = FetchType.LAZY)
diff --git a/model/jpa/src/main/java/org/keycloak/models/jpa/entities/AuthenticatorConfigEntity.java b/model/jpa/src/main/java/org/keycloak/models/jpa/entities/AuthenticatorConfigEntity.java
index a35c03e..9057683 100755
--- a/model/jpa/src/main/java/org/keycloak/models/jpa/entities/AuthenticatorConfigEntity.java
+++ b/model/jpa/src/main/java/org/keycloak/models/jpa/entities/AuthenticatorConfigEntity.java
@@ -17,6 +17,8 @@
 
 package org.keycloak.models.jpa.entities;
 
+import javax.persistence.Access;
+import javax.persistence.AccessType;
 import javax.persistence.CollectionTable;
 import javax.persistence.Column;
 import javax.persistence.ElementCollection;
@@ -40,6 +42,7 @@ import java.util.Map;
 public class AuthenticatorConfigEntity {
     @Id
     @Column(name="ID", length = 36)
+    @Access(AccessType.PROPERTY) // we do this because relationships often fetch id, but not entity.  This avoids an extra SQL
     protected String id;
 
     @Column(name="ALIAS")
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 848401a..15544c6 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
@@ -17,6 +17,8 @@
 
 package org.keycloak.models.jpa.entities;
 
+import javax.persistence.Access;
+import javax.persistence.AccessType;
 import javax.persistence.CascadeType;
 import javax.persistence.CollectionTable;
 import javax.persistence.Column;
@@ -28,6 +30,8 @@ import javax.persistence.JoinColumn;
 import javax.persistence.JoinTable;
 import javax.persistence.ManyToOne;
 import javax.persistence.MapKeyColumn;
+import javax.persistence.NamedQueries;
+import javax.persistence.NamedQuery;
 import javax.persistence.OneToMany;
 import javax.persistence.Table;
 import javax.persistence.UniqueConstraint;
@@ -44,10 +48,14 @@ import java.util.Set;
  */
 @Entity
 @Table(name="CLIENT", uniqueConstraints = {@UniqueConstraint(columnNames = {"REALM_ID", "CLIENT_ID"})})
+@NamedQueries({
+        @NamedQuery(name="getClientsByRealm", query="select client from ClientEntity client where client.realm = :realm"),
+})
 public class ClientEntity {
 
     @Id
     @Column(name="ID", length = 36)
+    @Access(AccessType.PROPERTY) // we do this because relationships often fetch id, but not entity.  This avoids an extra SQL
     private String id;
     @Column(name = "NAME")
     private String name;
@@ -146,7 +154,7 @@ public class ClientEntity {
     @Column(name="NODE_REREG_TIMEOUT")
     private int nodeReRegistrationTimeout;
 
-    @OneToMany(fetch = FetchType.EAGER, cascade ={CascadeType.REMOVE}, orphanRemoval = true, mappedBy = "client")
+    @OneToMany(fetch = FetchType.LAZY, cascade ={CascadeType.REMOVE}, mappedBy = "client")
     Collection<RoleEntity> roles = new ArrayList<RoleEntity>();
 
     @OneToMany(fetch = FetchType.LAZY, cascade ={CascadeType.REMOVE}, orphanRemoval = true)
diff --git a/model/jpa/src/main/java/org/keycloak/models/jpa/entities/ClientTemplateEntity.java b/model/jpa/src/main/java/org/keycloak/models/jpa/entities/ClientTemplateEntity.java
index 1f27816..280a257 100755
--- a/model/jpa/src/main/java/org/keycloak/models/jpa/entities/ClientTemplateEntity.java
+++ b/model/jpa/src/main/java/org/keycloak/models/jpa/entities/ClientTemplateEntity.java
@@ -17,6 +17,8 @@
 
 package org.keycloak.models.jpa.entities;
 
+import javax.persistence.Access;
+import javax.persistence.AccessType;
 import javax.persistence.CascadeType;
 import javax.persistence.CollectionTable;
 import javax.persistence.Column;
@@ -48,6 +50,7 @@ public class ClientTemplateEntity {
 
     @Id
     @Column(name="ID", length = 36)
+    @Access(AccessType.PROPERTY) // we do this because relationships often fetch id, but not entity.  This avoids an extra SQL
     private String id;
     @Column(name = "NAME")
     private String name;
diff --git a/model/jpa/src/main/java/org/keycloak/models/jpa/entities/CredentialEntity.java b/model/jpa/src/main/java/org/keycloak/models/jpa/entities/CredentialEntity.java
index 4e54c51..ceb284c 100755
--- a/model/jpa/src/main/java/org/keycloak/models/jpa/entities/CredentialEntity.java
+++ b/model/jpa/src/main/java/org/keycloak/models/jpa/entities/CredentialEntity.java
@@ -17,6 +17,8 @@
 
 package org.keycloak.models.jpa.entities;
 
+import javax.persistence.Access;
+import javax.persistence.AccessType;
 import javax.persistence.Column;
 import javax.persistence.Entity;
 import javax.persistence.FetchType;
@@ -42,6 +44,7 @@ import javax.persistence.Table;
 public class CredentialEntity {
     @Id
     @Column(name="ID", length = 36)
+    @Access(AccessType.PROPERTY) // we do this because relationships often fetch id, but not entity.  This avoids an extra SQL
     protected String id;
 
     @Column(name="TYPE")
diff --git a/model/jpa/src/main/java/org/keycloak/models/jpa/entities/GroupAttributeEntity.java b/model/jpa/src/main/java/org/keycloak/models/jpa/entities/GroupAttributeEntity.java
index b784d82..a5dbfc4 100755
--- a/model/jpa/src/main/java/org/keycloak/models/jpa/entities/GroupAttributeEntity.java
+++ b/model/jpa/src/main/java/org/keycloak/models/jpa/entities/GroupAttributeEntity.java
@@ -17,6 +17,8 @@
 
 package org.keycloak.models.jpa.entities;
 
+import javax.persistence.Access;
+import javax.persistence.AccessType;
 import javax.persistence.Column;
 import javax.persistence.Entity;
 import javax.persistence.FetchType;
@@ -42,6 +44,7 @@ public class GroupAttributeEntity {
 
     @Id
     @Column(name="ID", length = 36)
+    @Access(AccessType.PROPERTY) // we do this because relationships often fetch id, but not entity.  This avoids an extra SQL
     protected String id;
 
     @ManyToOne(fetch= FetchType.LAZY)
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 420929a..9eadbc8 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
@@ -17,6 +17,8 @@
 
 package org.keycloak.models.jpa.entities;
 
+import javax.persistence.Access;
+import javax.persistence.AccessType;
 import javax.persistence.CascadeType;
 import javax.persistence.Column;
 import javax.persistence.Entity;
@@ -47,6 +49,7 @@ import java.util.Collection;
 public class GroupEntity {
     @Id
     @Column(name="ID", length = 36)
+    @Access(AccessType.PROPERTY) // we do this because relationships often fetch id, but not entity.  This avoids an extra SQL
     protected String id;
 
     @Column(name = "NAME")
diff --git a/model/jpa/src/main/java/org/keycloak/models/jpa/entities/IdentityProviderEntity.java b/model/jpa/src/main/java/org/keycloak/models/jpa/entities/IdentityProviderEntity.java
index 6074663..77ca614 100755
--- a/model/jpa/src/main/java/org/keycloak/models/jpa/entities/IdentityProviderEntity.java
+++ b/model/jpa/src/main/java/org/keycloak/models/jpa/entities/IdentityProviderEntity.java
@@ -17,6 +17,8 @@
 
 package org.keycloak.models.jpa.entities;
 
+import javax.persistence.Access;
+import javax.persistence.AccessType;
 import javax.persistence.CollectionTable;
 import javax.persistence.Column;
 import javax.persistence.ElementCollection;
@@ -44,6 +46,7 @@ public class IdentityProviderEntity {
 
     @Id
     @Column(name="INTERNAL_ID", length = 36)
+    @Access(AccessType.PROPERTY) // we do this because relationships often fetch id, but not entity.  This avoids an extra SQL
     protected String internalId;
 
     @ManyToOne(fetch = FetchType.LAZY)
diff --git a/model/jpa/src/main/java/org/keycloak/models/jpa/entities/IdentityProviderMapperEntity.java b/model/jpa/src/main/java/org/keycloak/models/jpa/entities/IdentityProviderMapperEntity.java
index a7afa8d..2011e93 100755
--- a/model/jpa/src/main/java/org/keycloak/models/jpa/entities/IdentityProviderMapperEntity.java
+++ b/model/jpa/src/main/java/org/keycloak/models/jpa/entities/IdentityProviderMapperEntity.java
@@ -17,6 +17,8 @@
 
 package org.keycloak.models.jpa.entities;
 
+import javax.persistence.Access;
+import javax.persistence.AccessType;
 import javax.persistence.CollectionTable;
 import javax.persistence.Column;
 import javax.persistence.ElementCollection;
@@ -39,6 +41,7 @@ public class IdentityProviderMapperEntity {
 
     @Id
     @Column(name="ID", length = 36)
+    @Access(AccessType.PROPERTY) // we do this because relationships often fetch id, but not entity.  This avoids an extra SQL
     protected String id;
 
     @Column(name="NAME")
diff --git a/model/jpa/src/main/java/org/keycloak/models/jpa/entities/MigrationModelEntity.java b/model/jpa/src/main/java/org/keycloak/models/jpa/entities/MigrationModelEntity.java
index 148d009..ce4d845 100755
--- a/model/jpa/src/main/java/org/keycloak/models/jpa/entities/MigrationModelEntity.java
+++ b/model/jpa/src/main/java/org/keycloak/models/jpa/entities/MigrationModelEntity.java
@@ -17,6 +17,8 @@
 
 package org.keycloak.models.jpa.entities;
 
+import javax.persistence.Access;
+import javax.persistence.AccessType;
 import javax.persistence.Column;
 import javax.persistence.Entity;
 import javax.persistence.FetchType;
@@ -37,6 +39,7 @@ public class MigrationModelEntity {
     public static final String SINGLETON_ID = "SINGLETON";
     @Id
     @Column(name="ID", length = 36)
+    @Access(AccessType.PROPERTY) // we do this because relationships often fetch id, but not entity.  This avoids an extra SQL
     private String id;
 
     @Column(name="VERSION", length = 36)
diff --git a/model/jpa/src/main/java/org/keycloak/models/jpa/entities/ProtocolMapperEntity.java b/model/jpa/src/main/java/org/keycloak/models/jpa/entities/ProtocolMapperEntity.java
index bb51ad9..b6e3071 100755
--- a/model/jpa/src/main/java/org/keycloak/models/jpa/entities/ProtocolMapperEntity.java
+++ b/model/jpa/src/main/java/org/keycloak/models/jpa/entities/ProtocolMapperEntity.java
@@ -17,6 +17,8 @@
 
 package org.keycloak.models.jpa.entities;
 
+import javax.persistence.Access;
+import javax.persistence.AccessType;
 import javax.persistence.CollectionTable;
 import javax.persistence.Column;
 import javax.persistence.ElementCollection;
@@ -39,6 +41,7 @@ public class ProtocolMapperEntity {
 
     @Id
     @Column(name="ID", length = 36)
+    @Access(AccessType.PROPERTY) // we do this because relationships often fetch id, but not entity.  This avoids an extra SQL
     protected String id;
 
     @Column(name="NAME")
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 da639d4..76aa238 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
@@ -17,6 +17,8 @@
 
 package org.keycloak.models.jpa.entities;
 
+import javax.persistence.Access;
+import javax.persistence.AccessType;
 import javax.persistence.CascadeType;
 import javax.persistence.CollectionTable;
 import javax.persistence.Column;
@@ -53,6 +55,7 @@ import java.util.Set;
 public class RealmEntity {
     @Id
     @Column(name="ID", length = 36)
+    @Access(AccessType.PROPERTY) // we do this because relationships often fetch id, but not entity.  This avoids an extra SQL
     protected String id;
 
     @Column(name="NAME", unique = true)
@@ -138,22 +141,19 @@ public class RealmEntity {
     @OneToMany(cascade ={CascadeType.REMOVE}, orphanRemoval = true, mappedBy = "realm")
     Collection<RequiredCredentialEntity> requiredCredentials = new ArrayList<RequiredCredentialEntity>();
 
-    @OneToMany(cascade ={CascadeType.REMOVE}, orphanRemoval = true)
-    @JoinTable(name="FED_PROVIDERS", joinColumns={ @JoinColumn(name="REALM_ID") }, inverseJoinColumns={ @JoinColumn(name = "USERFEDERATIONPROVIDERS_ID") })
+    @OneToMany(cascade ={CascadeType.REMOVE}, orphanRemoval = true, mappedBy = "realm")
     List<UserFederationProviderEntity> userFederationProviders = new ArrayList<UserFederationProviderEntity>();
 
     @OneToMany(cascade ={CascadeType.REMOVE}, orphanRemoval = true, mappedBy = "realm")
     Collection<UserFederationMapperEntity> userFederationMappers = new ArrayList<UserFederationMapperEntity>();
 
-    @OneToMany(fetch = FetchType.LAZY, cascade ={CascadeType.REMOVE}, orphanRemoval = true)
-    @JoinTable(name="REALM_CLIENT", joinColumns={ @JoinColumn(name="REALM_ID") }, inverseJoinColumns={ @JoinColumn(name="CLIENT_ID") })
+    @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)
-    @JoinTable(name="REALM_CLIENT_TEMPLATE", joinColumns={ @JoinColumn(name="REALM_ID") }, inverseJoinColumns={ @JoinColumn(name="CLIENT_TEMPLATE_ID") })
+    @OneToMany(fetch = FetchType.LAZY, cascade ={CascadeType.REMOVE}, orphanRemoval = true, mappedBy = "realm")
     Collection<ClientTemplateEntity> clientTemplates = new ArrayList<>();
 
-    @OneToMany(fetch = FetchType.LAZY, cascade ={CascadeType.REMOVE}, orphanRemoval = true, mappedBy = "realm")
+    @OneToMany(fetch = FetchType.LAZY, cascade ={CascadeType.REMOVE}, mappedBy = "realm")
     Collection<RoleEntity> roles = new ArrayList<RoleEntity>();
 
     @ElementCollection
@@ -421,7 +421,6 @@ public class RealmEntity {
     public void setRequiredCredentials(Collection<RequiredCredentialEntity> requiredCredentials) {
         this.requiredCredentials = requiredCredentials;
     }
-
     public Collection<ClientEntity> getClients() {
         return clients;
     }
diff --git a/model/jpa/src/main/java/org/keycloak/models/jpa/entities/RequiredActionProviderEntity.java b/model/jpa/src/main/java/org/keycloak/models/jpa/entities/RequiredActionProviderEntity.java
index 95d3373..d8b43fb 100755
--- a/model/jpa/src/main/java/org/keycloak/models/jpa/entities/RequiredActionProviderEntity.java
+++ b/model/jpa/src/main/java/org/keycloak/models/jpa/entities/RequiredActionProviderEntity.java
@@ -17,6 +17,8 @@
 
 package org.keycloak.models.jpa.entities;
 
+import javax.persistence.Access;
+import javax.persistence.AccessType;
 import javax.persistence.CollectionTable;
 import javax.persistence.Column;
 import javax.persistence.ElementCollection;
@@ -42,6 +44,7 @@ import java.util.Map;
 public class RequiredActionProviderEntity {
     @Id
     @Column(name="ID", length = 36)
+    @Access(AccessType.PROPERTY) // we do this because relationships often fetch id, but not entity.  This avoids an extra SQL
     protected String id;
 
     @Column(name="ALIAS")
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 1b96cae..b94a743 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
@@ -17,6 +17,11 @@
 
 package org.keycloak.models.jpa.entities;
 
+import org.hibernate.annotations.DynamicInsert;
+import org.hibernate.annotations.DynamicUpdate;
+
+import javax.persistence.Access;
+import javax.persistence.AccessType;
 import javax.persistence.Column;
 import javax.persistence.Entity;
 import javax.persistence.FetchType;
@@ -37,10 +42,13 @@ import java.util.Collection;
  * @version $Revision: 1 $
  */
 @Entity
+//@DynamicInsert
+//@DynamicUpdate
 @Table(name="KEYCLOAK_ROLE", uniqueConstraints = {
         @UniqueConstraint(columnNames = { "NAME", "CLIENT_REALM_CONSTRAINT" })
 })
 @NamedQueries({
+        @NamedQuery(name="getClientRoles", query="select role from RoleEntity role where role.client = :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")
 })
@@ -48,6 +56,7 @@ import java.util.Collection;
 public class RoleEntity {
     @Id
     @Column(name="ID", length = 36)
+    @Access(AccessType.PROPERTY) // we do this because relationships often fetch id, but not entity.  This avoids an extra SQL
     private String id;
 
     @Column(name = "NAME")
diff --git a/model/jpa/src/main/java/org/keycloak/models/jpa/entities/UserAttributeEntity.java b/model/jpa/src/main/java/org/keycloak/models/jpa/entities/UserAttributeEntity.java
index e09c1bb..263757f 100755
--- a/model/jpa/src/main/java/org/keycloak/models/jpa/entities/UserAttributeEntity.java
+++ b/model/jpa/src/main/java/org/keycloak/models/jpa/entities/UserAttributeEntity.java
@@ -17,6 +17,8 @@
 
 package org.keycloak.models.jpa.entities;
 
+import javax.persistence.Access;
+import javax.persistence.AccessType;
 import javax.persistence.CollectionTable;
 import javax.persistence.Column;
 import javax.persistence.ElementCollection;
@@ -48,6 +50,7 @@ public class UserAttributeEntity {
 
     @Id
     @Column(name="ID", length = 36)
+    @Access(AccessType.PROPERTY) // we do this because relationships often fetch id, but not entity.  This avoids an extra SQL
     protected String id;
 
     @ManyToOne(fetch= FetchType.LAZY)
diff --git a/model/jpa/src/main/java/org/keycloak/models/jpa/entities/UserConsentEntity.java b/model/jpa/src/main/java/org/keycloak/models/jpa/entities/UserConsentEntity.java
index a349d80..c1098e2 100755
--- a/model/jpa/src/main/java/org/keycloak/models/jpa/entities/UserConsentEntity.java
+++ b/model/jpa/src/main/java/org/keycloak/models/jpa/entities/UserConsentEntity.java
@@ -20,6 +20,8 @@ package org.keycloak.models.jpa.entities;
 import java.util.ArrayList;
 import java.util.Collection;
 
+import javax.persistence.Access;
+import javax.persistence.AccessType;
 import javax.persistence.CascadeType;
 import javax.persistence.Column;
 import javax.persistence.Entity;
@@ -51,6 +53,7 @@ public class UserConsentEntity {
 
     @Id
     @Column(name="ID", length = 36)
+    @Access(AccessType.PROPERTY) // we do this because relationships often fetch id, but not entity.  This avoids an extra SQL
     protected String id;
 
     @ManyToOne(fetch= FetchType.LAZY)
diff --git a/model/jpa/src/main/java/org/keycloak/models/jpa/entities/UserEntity.java b/model/jpa/src/main/java/org/keycloak/models/jpa/entities/UserEntity.java
index b1c8c04..d110a17 100755
--- a/model/jpa/src/main/java/org/keycloak/models/jpa/entities/UserEntity.java
+++ b/model/jpa/src/main/java/org/keycloak/models/jpa/entities/UserEntity.java
@@ -19,6 +19,8 @@ package org.keycloak.models.jpa.entities;
 
 import org.keycloak.models.utils.KeycloakModelUtils;
 
+import javax.persistence.Access;
+import javax.persistence.AccessType;
 import javax.persistence.CascadeType;
 import javax.persistence.Column;
 import javax.persistence.Entity;
@@ -59,6 +61,7 @@ import java.util.Collection;
 public class UserEntity {
     @Id
     @Column(name="ID", length = 36)
+    @Access(AccessType.PROPERTY) // we do this because relationships often fetch id, but not entity.  This avoids an extra SQL
     protected String id;
 
     @Column(name = "USERNAME")
diff --git a/model/jpa/src/main/java/org/keycloak/models/jpa/entities/UserFederationMapperEntity.java b/model/jpa/src/main/java/org/keycloak/models/jpa/entities/UserFederationMapperEntity.java
index 54989f2..41350f6 100755
--- a/model/jpa/src/main/java/org/keycloak/models/jpa/entities/UserFederationMapperEntity.java
+++ b/model/jpa/src/main/java/org/keycloak/models/jpa/entities/UserFederationMapperEntity.java
@@ -19,6 +19,8 @@ package org.keycloak.models.jpa.entities;
 
 import java.util.Map;
 
+import javax.persistence.Access;
+import javax.persistence.AccessType;
 import javax.persistence.CollectionTable;
 import javax.persistence.Column;
 import javax.persistence.ElementCollection;
@@ -41,6 +43,7 @@ public class UserFederationMapperEntity {
 
     @Id
     @Column(name="ID", length = 36)
+    @Access(AccessType.PROPERTY) // we do this because relationships often fetch id, but not entity.  This avoids an extra SQL
     protected String id;
 
     @Column(name="NAME")
diff --git a/model/jpa/src/main/java/org/keycloak/models/jpa/entities/UserFederationProviderEntity.java b/model/jpa/src/main/java/org/keycloak/models/jpa/entities/UserFederationProviderEntity.java
index 7b841ca..f7cab7f 100755
--- a/model/jpa/src/main/java/org/keycloak/models/jpa/entities/UserFederationProviderEntity.java
+++ b/model/jpa/src/main/java/org/keycloak/models/jpa/entities/UserFederationProviderEntity.java
@@ -17,6 +17,8 @@
 
 package org.keycloak.models.jpa.entities;
 
+import javax.persistence.Access;
+import javax.persistence.AccessType;
 import javax.persistence.CollectionTable;
 import javax.persistence.Column;
 import javax.persistence.ElementCollection;
@@ -39,6 +41,7 @@ public class UserFederationProviderEntity {
 
     @Id
     @Column(name="ID", length = 36)
+    @Access(AccessType.PROPERTY) // we do this because relationships often fetch id, but not entity.  This avoids an extra SQL
     protected String id;
 
     @ManyToOne(fetch = FetchType.LAZY)
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 be5cd9d..86bc5a2 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
@@ -124,9 +124,18 @@ public class JpaRealmProvider implements RealmProvider {
                 .setParameter("realm", realm).executeUpdate();
         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());
         }
+        */
         for (ClientTemplateEntity a : new LinkedList<>(realm.getClientTemplates())) {
             adapter.removeClientTemplate(a.getId());
         }
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 d93c44c..4d632ec 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
@@ -729,12 +729,14 @@ public class RealmAdapter implements RealmModel {
 
     @Override
     public List<ClientModel> getClients() {
-        List<ClientModel> list = new ArrayList<ClientModel>();
-        if (realm.getClients() == null) return list;
-        for (ClientEntity entity : realm.getClients()) {
+        List<ClientModel> list = new LinkedList<>();
+        TypedQuery<ClientEntity> query = em.createNamedQuery("getClientsByRealm", ClientEntity.class);
+        query.setParameter("realm", realm);
+        List<ClientEntity> clients = query.getResultList();
+        for (ClientEntity entity : clients) {
             list.add(new ClientAdapter(this, em, session, entity));
         }
-        return list;
+      return list;
     }
 
     @Override
@@ -794,11 +796,9 @@ public class RealmAdapter implements RealmModel {
                 clientEntity = a;
             }
         }
-        if (client == null) {
-            return false;
-        }
-        em.remove(clientEntity);
+        if (clientEntity == null) return false;
         em.createNamedQuery("deleteScopeMappingByClient").setParameter("client", clientEntity).executeUpdate();
+        em.remove(clientEntity);
         em.flush();
 
         return true;
@@ -1017,6 +1017,7 @@ public class RealmAdapter implements RealmModel {
             entity.setFullSyncPeriod(model.getFullSyncPeriod());
             entity.setChangedSyncPeriod(model.getChangedSyncPeriod());
             entity.setLastSync(model.getLastSync());
+            entity.setRealm(realm);
             em.persist(entity);
             realm.getUserFederationProviders().add(entity);
 
diff --git a/model/jpa/src/main/resources/META-INF/jpa-changelog-1.9.0.xml b/model/jpa/src/main/resources/META-INF/jpa-changelog-1.9.0.xml
old mode 100644
new mode 100755
index 38374e7..99b47bc
--- a/model/jpa/src/main/resources/META-INF/jpa-changelog-1.9.0.xml
+++ b/model/jpa/src/main/resources/META-INF/jpa-changelog-1.9.0.xml
@@ -60,5 +60,21 @@
             <where>ACCESS_TOKEN_LIFE_IMPLICIT is NULL</where>
         </update>
 
+        <dropUniqueConstraint tableName="REALM_CLIENT" constraintName="UK_M6QGA3RFME47335JY8JXYXH3I"  />
+        <dropForeignKeyConstraint baseTableName="REALM_CLIENT" constraintName="FK_93S3P0DIUXAWWQQSA528UBY2Q"/>
+        <dropForeignKeyConstraint baseTableName="REALM_CLIENT" constraintName="FK_M6QGA3RFME47335JY8JXYXH3I"/>
+        <dropTable tableName="REALM_CLIENT" cascadeConstraints="true"/>
+        <dropForeignKeyConstraint baseTableName="REALM_CLIENT_TEMPLATE" constraintName="FK_RLM_CLI_TMPLT_RLM" />
+        <dropForeignKeyConstraint baseTableName="REALM_CLIENT_TEMPLATE" constraintName="FK_RLM_CLI_TMPLT_CLI"/>
+        <dropTable tableName="REALM_CLIENT_TEMPLATE" cascadeConstraints="true"/>
+
+        <dropUniqueConstraint tableName="FED_PROVIDERS" constraintName="UK_DCCIRJLIPU1478VQC89DID88C" />
+        <dropForeignKeyConstraint baseTableName="FED_PROVIDERS" constraintName="FK_213LYQ09FKXQ8K8NY8DY3737T"/>
+        <dropForeignKeyConstraint baseTableName="FED_PROVIDERS" constraintName="FK_DCCIRJLIPU1478VQC89DID88C"/>
+        <dropTable tableName="FED_PROVIDERS" cascadeConstraints="true"/>
+
+
+
+
     </changeSet>
 </databaseChangeLog>
\ No newline at end of file

pom.xml 0(+0 -0)

diff --git a/pom.xml b/pom.xml
old mode 100644
new mode 100755
diff --git a/server-spi/src/main/java/org/keycloak/models/cache/entities/CachedClient.java b/server-spi/src/main/java/org/keycloak/models/cache/entities/CachedClient.java
index 3cd37af..ca3e35b 100755
--- a/server-spi/src/main/java/org/keycloak/models/cache/entities/CachedClient.java
+++ b/server-spi/src/main/java/org/keycloak/models/cache/entities/CachedClient.java
@@ -39,43 +39,43 @@ import java.util.TreeMap;
  */
 public class CachedClient implements Serializable {
 
-    private String id;
-    private String clientId;
-    private String name;
-    private String description;
-    private String realm;
-    private Set<String> redirectUris = new HashSet<String>();
-    private boolean enabled;
-    private String clientAuthenticatorType;
-    private String secret;
-    private String registrationToken;
-    private String protocol;
-    private Map<String, String> attributes = new HashMap<String, String>();
-    private boolean publicClient;
-    private boolean fullScopeAllowed;
-    private boolean frontchannelLogout;
-    private int notBefore;
-    private Set<String> scope = new HashSet<String>();
-    private Set<String> webOrigins = new HashSet<String>();
-    private Set<ProtocolMapperModel> protocolMappers = new HashSet<ProtocolMapperModel>();
-    private boolean surrogateAuthRequired;
-    private String managementUrl;
-    private String rootUrl;
-    private String baseUrl;
-    private List<String> defaultRoles = new LinkedList<String>();
-    private boolean bearerOnly;
-    private boolean consentRequired;
-    private boolean standardFlowEnabled;
-    private boolean implicitFlowEnabled;
-    private boolean directAccessGrantsEnabled;
-    private boolean serviceAccountsEnabled;
-    private Map<String, String> roles = new HashMap<String, String>();
-    private int nodeReRegistrationTimeout;
-    private Map<String, Integer> registeredNodes;
-    private String clientTemplate;
-    private boolean useTemplateScope;
-    private boolean useTemplateConfig;
-    private boolean useTemplateMappers;
+    protected String id;
+    protected String clientId;
+    protected String name;
+    protected String description;
+    protected String realm;
+    protected Set<String> redirectUris = new HashSet<String>();
+    protected boolean enabled;
+    protected String clientAuthenticatorType;
+    protected String secret;
+    protected String registrationToken;
+    protected String protocol;
+    protected Map<String, String> attributes = new HashMap<String, String>();
+    protected boolean publicClient;
+    protected boolean fullScopeAllowed;
+    protected boolean frontchannelLogout;
+    protected int notBefore;
+    protected Set<String> scope = new HashSet<String>();
+    protected Set<String> webOrigins = new HashSet<String>();
+    protected Set<ProtocolMapperModel> protocolMappers = new HashSet<ProtocolMapperModel>();
+    protected boolean surrogateAuthRequired;
+    protected String managementUrl;
+    protected String rootUrl;
+    protected String baseUrl;
+    protected List<String> defaultRoles = new LinkedList<String>();
+    protected boolean bearerOnly;
+    protected boolean consentRequired;
+    protected boolean standardFlowEnabled;
+    protected boolean implicitFlowEnabled;
+    protected boolean directAccessGrantsEnabled;
+    protected boolean serviceAccountsEnabled;
+    protected Map<String, String> roles = new HashMap<String, String>();
+    protected int nodeReRegistrationTimeout;
+    protected Map<String, Integer> registeredNodes;
+    protected String clientTemplate;
+    protected boolean useTemplateScope;
+    protected boolean useTemplateConfig;
+    protected boolean useTemplateMappers;
 
     public CachedClient(RealmCache cache, RealmProvider delegate, RealmModel realm, ClientModel model) {
         id = model.getId();
@@ -112,10 +112,7 @@ public class CachedClient implements Serializable {
         implicitFlowEnabled = model.isImplicitFlowEnabled();
         directAccessGrantsEnabled = model.isDirectAccessGrantsEnabled();
         serviceAccountsEnabled = model.isServiceAccountsEnabled();
-        for (RoleModel role : model.getRoles()) {
-            roles.put(role.getName(), role.getId());
-            cache.addCachedRole(new CachedClientRole(id, role, realm));
-        }
+        cacheRoles(cache, realm, model);
 
         nodeReRegistrationTimeout = model.getNodeReRegistrationTimeout();
         registeredNodes = new TreeMap<String, Integer>(model.getRegisteredNodes());
@@ -126,6 +123,14 @@ public class CachedClient implements Serializable {
         useTemplateMappers = model.useTemplateMappers();
         useTemplateScope = model.useTemplateScope();
     }
+
+    protected void cacheRoles(RealmCache cache, RealmModel realm, ClientModel model) {
+        for (RoleModel role : model.getRoles()) {
+            roles.put(role.getName(), role.getId());
+            cache.addCachedRole(new CachedClientRole(id, role, realm));
+        }
+    }
+
     public String getId() {
         return id;
     }
diff --git a/server-spi/src/main/java/org/keycloak/models/cache/entities/CachedRealm.java b/server-spi/src/main/java/org/keycloak/models/cache/entities/CachedRealm.java
index dd19d6e..42ed3bc 100755
--- a/server-spi/src/main/java/org/keycloak/models/cache/entities/CachedRealm.java
+++ b/server-spi/src/main/java/org/keycloak/models/cache/entities/CachedRealm.java
@@ -53,90 +53,90 @@ import java.util.Set;
  */
 public class CachedRealm implements Serializable {
 
-    private String id;
-    private String name;
-    private String displayName;
-    private String displayNameHtml;
-    private boolean enabled;
-    private SslRequired sslRequired;
-    private boolean registrationAllowed;
-    private boolean registrationEmailAsUsername;
-    private boolean rememberMe;
-    private boolean verifyEmail;
-    private boolean resetPasswordAllowed;
-    private boolean identityFederationEnabled;
-    private boolean editUsernameAllowed;
+    protected String id;
+    protected String name;
+    protected String displayName;
+    protected String displayNameHtml;
+    protected boolean enabled;
+    protected SslRequired sslRequired;
+    protected boolean registrationAllowed;
+    protected boolean registrationEmailAsUsername;
+    protected boolean rememberMe;
+    protected boolean verifyEmail;
+    protected boolean resetPasswordAllowed;
+    protected boolean identityFederationEnabled;
+    protected boolean editUsernameAllowed;
     //--- brute force settings
-    private boolean bruteForceProtected;
-    private int maxFailureWaitSeconds;
-    private int minimumQuickLoginWaitSeconds;
-    private int waitIncrementSeconds;
-    private long quickLoginCheckMilliSeconds;
-    private int maxDeltaTimeSeconds;
-    private int failureFactor;
+    protected boolean bruteForceProtected;
+    protected int maxFailureWaitSeconds;
+    protected int minimumQuickLoginWaitSeconds;
+    protected int waitIncrementSeconds;
+    protected long quickLoginCheckMilliSeconds;
+    protected int maxDeltaTimeSeconds;
+    protected int failureFactor;
     //--- end brute force settings
 
-    private boolean revokeRefreshToken;
-    private int ssoSessionIdleTimeout;
-    private int ssoSessionMaxLifespan;
-    private int offlineSessionIdleTimeout;
-    private int accessTokenLifespan;
-    private int accessTokenLifespanForImplicitFlow;
-    private int accessCodeLifespan;
-    private int accessCodeLifespanUserAction;
-    private int accessCodeLifespanLogin;
-    private int notBefore;
-    private PasswordPolicy passwordPolicy;
-    private OTPPolicy otpPolicy;
-
-    private String publicKeyPem;
-    private String privateKeyPem;
-    private String certificatePem;
-    private String codeSecret;
-
-    private String loginTheme;
-    private String accountTheme;
-    private String adminTheme;
-    private String emailTheme;
-    private String masterAdminClient;
-
-    private List<RequiredCredentialModel> requiredCredentials = new ArrayList<RequiredCredentialModel>();
-    private List<UserFederationProviderModel> userFederationProviders = new ArrayList<UserFederationProviderModel>();
-    private MultivaluedHashMap<String, UserFederationMapperModel> userFederationMappers = new MultivaluedHashMap<String, UserFederationMapperModel>();
-    private List<IdentityProviderModel> identityProviders = new ArrayList<IdentityProviderModel>();
-
-    private Map<String, String> browserSecurityHeaders = new HashMap<String, String>();
-    private Map<String, String> smtpConfig = new HashMap<String, String>();
-    private Map<String, AuthenticationFlowModel> authenticationFlows = new HashMap<>();
-    private Map<String, AuthenticatorConfigModel> authenticatorConfigs = new HashMap<>();
-    private Map<String, RequiredActionProviderModel> requiredActionProviders = new HashMap<>();
-    private Map<String, RequiredActionProviderModel> requiredActionProvidersByAlias = new HashMap<>();
-    private MultivaluedHashMap<String, AuthenticationExecutionModel> authenticationExecutions = new MultivaluedHashMap<>();
-    private Map<String, AuthenticationExecutionModel> executionsById = new HashMap<>();
-
-    private AuthenticationFlowModel browserFlow;
-    private AuthenticationFlowModel registrationFlow;
-    private AuthenticationFlowModel directGrantFlow;
-    private AuthenticationFlowModel resetCredentialsFlow;
-    private AuthenticationFlowModel clientAuthenticationFlow;
-
-    private boolean eventsEnabled;
-    private long eventsExpiration;
-    private Set<String> eventsListeners = new HashSet<String>();
-    private Set<String> enabledEventTypes = new HashSet<String>();
+    protected boolean revokeRefreshToken;
+    protected int ssoSessionIdleTimeout;
+    protected int ssoSessionMaxLifespan;
+    protected int offlineSessionIdleTimeout;
+    protected int accessTokenLifespan;
+    protected int accessTokenLifespanForImplicitFlow;
+    protected int accessCodeLifespan;
+    protected int accessCodeLifespanUserAction;
+    protected int accessCodeLifespanLogin;
+    protected int notBefore;
+    protected PasswordPolicy passwordPolicy;
+    protected OTPPolicy otpPolicy;
+
+    protected String publicKeyPem;
+    protected String privateKeyPem;
+    protected String certificatePem;
+    protected String codeSecret;
+
+    protected String loginTheme;
+    protected String accountTheme;
+    protected String adminTheme;
+    protected String emailTheme;
+    protected String masterAdminClient;
+
+    protected List<RequiredCredentialModel> requiredCredentials = new ArrayList<RequiredCredentialModel>();
+    protected List<UserFederationProviderModel> userFederationProviders = new ArrayList<UserFederationProviderModel>();
+    protected MultivaluedHashMap<String, UserFederationMapperModel> userFederationMappers = new MultivaluedHashMap<String, UserFederationMapperModel>();
+    protected List<IdentityProviderModel> identityProviders = new ArrayList<IdentityProviderModel>();
+
+    protected Map<String, String> browserSecurityHeaders = new HashMap<String, String>();
+    protected Map<String, String> smtpConfig = new HashMap<String, String>();
+    protected Map<String, AuthenticationFlowModel> authenticationFlows = new HashMap<>();
+    protected Map<String, AuthenticatorConfigModel> authenticatorConfigs = new HashMap<>();
+    protected Map<String, RequiredActionProviderModel> requiredActionProviders = new HashMap<>();
+    protected Map<String, RequiredActionProviderModel> requiredActionProvidersByAlias = new HashMap<>();
+    protected MultivaluedHashMap<String, AuthenticationExecutionModel> authenticationExecutions = new MultivaluedHashMap<>();
+    protected Map<String, AuthenticationExecutionModel> executionsById = new HashMap<>();
+
+    protected AuthenticationFlowModel browserFlow;
+    protected AuthenticationFlowModel registrationFlow;
+    protected AuthenticationFlowModel directGrantFlow;
+    protected AuthenticationFlowModel resetCredentialsFlow;
+    protected AuthenticationFlowModel clientAuthenticationFlow;
+
+    protected boolean eventsEnabled;
+    protected long eventsExpiration;
+    protected Set<String> eventsListeners = new HashSet<String>();
+    protected Set<String> enabledEventTypes = new HashSet<String>();
     protected boolean adminEventsEnabled;
     protected Set<String> adminEnabledEventOperations = new HashSet<String>();
     protected boolean adminEventsDetailsEnabled;
-    private List<String> defaultRoles = new LinkedList<String>();
-    private List<String> defaultGroups = new LinkedList<String>();
-    private Set<String> groups = new HashSet<String>();
-    private Map<String, String> realmRoles = new HashMap<String, String>();
-    private Map<String, String> clients = new HashMap<String, String>();
-    private List<String> clientTemplates= new LinkedList<>();
-    private boolean internationalizationEnabled;
-    private Set<String> supportedLocales = new HashSet<String>();
-    private String defaultLocale;
-    private MultivaluedHashMap<String, IdentityProviderMapperModel> identityProviderMappers = new MultivaluedHashMap<>();
+    protected List<String> defaultRoles = new LinkedList<String>();
+    protected List<String> defaultGroups = new LinkedList<String>();
+    protected Set<String> groups = new HashSet<String>();
+    protected Map<String, String> realmRoles = new HashMap<String, String>();
+    protected Map<String, String> clients = new HashMap<String, String>();
+    protected List<String> clientTemplates= new LinkedList<>();
+    protected boolean internationalizationEnabled;
+    protected Set<String> supportedLocales = new HashSet<String>();
+    protected String defaultLocale;
+    protected MultivaluedHashMap<String, IdentityProviderMapperModel> identityProviderMappers = new MultivaluedHashMap<>();
 
     public CachedRealm() {
     }
@@ -221,23 +221,11 @@ public class CachedRealm implements Serializable {
         ClientModel masterAdminClient = model.getMasterAdminClient();
         this.masterAdminClient = (masterAdminClient != null) ? masterAdminClient.getId() : null;
 
-        for (RoleModel role : model.getRoles()) {
-            realmRoles.put(role.getName(), role.getId());
-            CachedRole cachedRole = new CachedRealmRole(role, model);
-            cache.addCachedRole(cachedRole);
-        }
+        cacheRealmRoles(cache, model);
 
-        for (ClientModel client : model.getClients()) {
-            clients.put(client.getClientId(), client.getId());
-            CachedClient cachedClient = new CachedClient(cache, delegate, model, client);
-            cache.addCachedClient(cachedClient);
-        }
+        cacheClients(cache, delegate, model);
 
-        for (ClientTemplateModel template : model.getClientTemplates()) {
-            clientTemplates.add(template.getId());
-            CachedClientTemplate cachedClient = new CachedClientTemplate(cache, delegate, model, template);
-            cache.addCachedClientTemplate(cachedClient);
-        }
+        cacheClientTemplates(cache, delegate, model);
 
         internationalizationEnabled = model.isInternationalizationEnabled();
         supportedLocales.addAll(model.getSupportedLocales());
@@ -273,6 +261,30 @@ public class CachedRealm implements Serializable {
 
     }
 
+    protected void cacheClientTemplates(RealmCache cache, RealmProvider delegate, RealmModel model) {
+        for (ClientTemplateModel template : model.getClientTemplates()) {
+            clientTemplates.add(template.getId());
+            CachedClientTemplate cachedClient = new CachedClientTemplate(cache, delegate, model, template);
+            cache.addCachedClientTemplate(cachedClient);
+        }
+    }
+
+    protected void cacheClients(RealmCache cache, RealmProvider delegate, RealmModel model) {
+        for (ClientModel client : model.getClients()) {
+            clients.put(client.getClientId(), client.getId());
+            CachedClient cachedClient = new CachedClient(cache, delegate, model, client);
+            cache.addCachedClient(cachedClient);
+        }
+    }
+
+    protected void cacheRealmRoles(RealmCache cache, RealmModel model) {
+        for (RoleModel role : model.getRoles()) {
+            realmRoles.put(role.getName(), role.getId());
+            CachedRole cachedRole = new CachedRealmRole(role, model);
+            cache.addCachedRole(cachedRole);
+        }
+    }
+
 
     public String getId() {
         return id;
diff --git a/server-spi/src/main/java/org/keycloak/models/KeycloakTransactionManager.java b/server-spi/src/main/java/org/keycloak/models/KeycloakTransactionManager.java
index 2b2a783..0e2dcbe 100755
--- a/server-spi/src/main/java/org/keycloak/models/KeycloakTransactionManager.java
+++ b/server-spi/src/main/java/org/keycloak/models/KeycloakTransactionManager.java
@@ -26,4 +26,5 @@ public interface KeycloakTransactionManager extends KeycloakTransaction {
     void enlist(KeycloakTransaction transaction);
     void enlistAfterCompletion(KeycloakTransaction transaction);
 
+    void enlistPrepare(KeycloakTransaction transaction);
 }
diff --git a/services/src/main/java/org/keycloak/services/DefaultKeycloakTransactionManager.java b/services/src/main/java/org/keycloak/services/DefaultKeycloakTransactionManager.java
index 23ae6fa..fca6a9e 100755
--- a/services/src/main/java/org/keycloak/services/DefaultKeycloakTransactionManager.java
+++ b/services/src/main/java/org/keycloak/services/DefaultKeycloakTransactionManager.java
@@ -30,6 +30,7 @@ public class DefaultKeycloakTransactionManager implements KeycloakTransactionMan
 
     public static final ServicesLogger logger = ServicesLogger.ROOT_LOGGER;
 
+    private List<KeycloakTransaction> prepare = new LinkedList<KeycloakTransaction>();
     private List<KeycloakTransaction> transactions = new LinkedList<KeycloakTransaction>();
     private List<KeycloakTransaction> afterCompletion = new LinkedList<KeycloakTransaction>();
     private boolean active;
@@ -54,6 +55,15 @@ public class DefaultKeycloakTransactionManager implements KeycloakTransactionMan
     }
 
     @Override
+    public void enlistPrepare(KeycloakTransaction transaction) {
+        if (active && !transaction.isActive()) {
+            transaction.begin();
+        }
+
+        prepare.add(transaction);
+    }
+
+    @Override
     public void begin() {
         if (active) {
              throw new IllegalStateException("Transaction already active");
@@ -69,6 +79,17 @@ public class DefaultKeycloakTransactionManager implements KeycloakTransactionMan
     @Override
     public void commit() {
         RuntimeException exception = null;
+        for (KeycloakTransaction tx : prepare) {
+            try {
+                tx.commit();
+            } catch (RuntimeException e) {
+                exception = exception == null ? e : exception;
+            }
+        }
+        if (exception != null) {
+            rollback(exception);
+            return;
+        }
         for (KeycloakTransaction tx : transactions) {
             try {
                 tx.commit();
@@ -105,6 +126,10 @@ public class DefaultKeycloakTransactionManager implements KeycloakTransactionMan
     @Override
     public void rollback() {
         RuntimeException exception = null;
+        rollback(exception);
+    }
+
+    protected void rollback(RuntimeException exception) {
         for (KeycloakTransaction tx : transactions) {
             try {
                 tx.rollback();
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 2f52649..f510f04 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
@@ -18,6 +18,7 @@
 package org.keycloak.testsuite.admin;
 
 import org.jboss.logging.Logger;
+import org.junit.Assert;
 import org.junit.Ignore;
 import org.junit.Test;
 import org.keycloak.admin.client.Keycloak;
@@ -30,6 +31,8 @@ import javax.ws.rs.core.Response;
 import java.util.LinkedList;
 import java.util.List;
 import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
 import java.util.concurrent.atomic.AtomicReference;
 import java.util.concurrent.locks.Lock;
 import java.util.concurrent.locks.ReentrantLock;
@@ -39,19 +42,55 @@ import static org.junit.Assert.*;
 /**
  * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
  */
-@Ignore
 public class ConcurrencyTest extends AbstractClientTest {
 
     private static final Logger log = Logger.getLogger(ConcurrencyTest.class);
 
-    private static final int DEFAULT_THREADS = 3;
-    private static final int DEFAULT_ITERATIONS = 10;
+    private static final int DEFAULT_THREADS = 1;
+    private static final int DEFAULT_ITERATIONS = 5;
 
     // If enabled only one request is allowed at the time. Useful for checking that test is working.
     private static final boolean SYNCHRONIZED = false;
 
+    boolean passedCreateClient = false;
+    boolean passedCreateRole = false;
+
+    //@Test
+    public void testAllConcurrently() throws Throwable {
+        Thread client = new Thread(new Runnable() {
+            @Override
+            public void run() {
+                try {
+                    createClient();
+                    passedCreateClient = true;
+                } catch (Throwable throwable) {
+                    throw new RuntimeException(throwable);
+                }
+            }
+        });
+        Thread role = new Thread(new Runnable() {
+            @Override
+            public void run() {
+                try {
+                    createRole();
+                    passedCreateRole = true;
+                } catch (Throwable throwable) {
+                    throw new RuntimeException(throwable);
+                }
+            }
+        });
+
+        client.start();
+        role.start();
+        client.join();
+        role.join();
+        Assert.assertTrue(passedCreateClient);
+        Assert.assertTrue(passedCreateRole);
+    }
+
     @Test
     public void createClient() throws Throwable {
+        long start = System.currentTimeMillis();
         run(new KeycloakRunnable() {
             @Override
             public void run(Keycloak keycloak, RealmResource realm, int threadNum, int iterationNum) {
@@ -75,10 +114,14 @@ public class ConcurrencyTest extends AbstractClientTest {
                 }
             }
         });
+        long end = System.currentTimeMillis() - start;
+        System.out.println("createClient took " + end);
+
     }
 
     @Test
     public void createRole() throws Throwable {
+        long start = System.currentTimeMillis();
         run(new KeycloakRunnable() {
             @Override
             public void run(Keycloak keycloak, RealmResource realm, int threadNum, int iterationNum) {
@@ -88,16 +131,22 @@ public class ConcurrencyTest extends AbstractClientTest {
                 assertNotNull(realm.roles().get(name).toRepresentation());
             }
         });
+        long end = System.currentTimeMillis() - start;
+        System.out.println("createRole took " + end);
+
     }
 
     @Test
     public void createClientRole() throws Throwable {
+        long start = System.currentTimeMillis();
         ClientRepresentation c = new ClientRepresentation();
         c.setClientId("client");
         Response response = realm.clients().create(c);
         final String clientId = ApiUtil.getCreatedId(response);
         response.close();
 
+        System.out.println("*********************************************");
+
         run(new KeycloakRunnable() {
             @Override
             public void run(Keycloak keycloak, RealmResource realm, int threadNum, int iterationNum) {
@@ -110,6 +159,10 @@ public class ConcurrencyTest extends AbstractClientTest {
                 assertNotNull(client.roles().get(name).toRepresentation());
             }
         });
+        long end = System.currentTimeMillis() - start;
+        System.out.println("createClientRole took " + end);
+        System.out.println("*********************************************");
+
     }
 
     private void run(final KeycloakRunnable runnable) throws Throwable {
diff --git a/testsuite/integration/src/test/resources/log4j.properties b/testsuite/integration/src/test/resources/log4j.properties
index 68b1ba6..91c9646 100755
--- a/testsuite/integration/src/test/resources/log4j.properties
+++ b/testsuite/integration/src/test/resources/log4j.properties
@@ -30,7 +30,9 @@ log4j.logger.org.keycloak=info
 # log4j.logger.org.keycloak.services.DefaultKeycloakSessionFactory=debug
 # log4j.logger.org.keycloak.provider.ProviderManager=debug
 # log4j.logger.org.keycloak.provider.FileSystemProviderLoaderFactory=debug
-
+#log4j.logger.org.infinispan.transaction.impl.TransactionCoordinator=OFF
+#log4j.logger.org.infinispan.transaction.tm.DummyTransaction=OFF
+#log4j.logger.org.infinispan.container.entries.RepeatableReadEntry=OFF
 # Broker logging
 keycloak.testsuite.logging.level=info
 log4j.logger.org.keycloak.testsuite=${keycloak.testsuite.logging.level}
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 401fc6b..a2f93cd 100755
--- a/testsuite/integration/src/test/resources/META-INF/keycloak-server.json
+++ b/testsuite/integration/src/test/resources/META-INF/keycloak-server.json
@@ -32,12 +32,6 @@
         }
     },
 
-    "realmCache": {
-        "infinispan" : {
-            "enabled": true
-        }
-    },
-
     "timer": {
         "provider": "basic"
     },
@@ -84,8 +78,16 @@
         }
     },
 
+    "realmCache": {
+        "provider": "infinispan-locking",
+        "infinispan-locking" : {
+            "enabled": true
+        }
+    },
+
     "connectionsInfinispan": {
-        "default": {
+        "provider": "locking",
+        "locking": {
             "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
old mode 100644
new mode 100755
index 0978231..5cef402
--- 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
@@ -26,14 +26,6 @@
         "provider": "${keycloak.userSessionPersister.provider:jpa}"
     },
 
-    "userSessions": {
-        "provider" : "${keycloak.userSessions.provider:infinispan}"
-    },
-
-    "realmCache": {
-        "provider": "${keycloak.realm.cache.provider:infinispan}"
-    },
-
     "userCache": {
         "provider": "${keycloak.user.cache.provider:infinispan}",
         "mem": {
@@ -41,6 +33,10 @@
         }
     },
 
+    "userSessions": {
+        "provider" : "${keycloak.userSessions.provider:infinispan}"
+    },
+
     "timer": {
         "provider": "basic"
     },
@@ -99,6 +95,23 @@
         }
     },
 
+    "realmCache": {
+        "provider": "infinispan-locking",
+        "infinispan-locking" : {
+            "enabled": true
+        }
+    },
+
+    "connectionsInfinispan": {
+        "provider": "locking",
+        "locking": {
+            "clustered": "${keycloak.connectionsInfinispan.clustered:false}",
+            "async": "${keycloak.connectionsInfinispan.async:true}",
+            "sessionsOwners": "${keycloak.connectionsInfinispan.sessionsOwners:2}"
+        }
+    },
+
+
     "truststore": {
         "file": {
             "file": "${keycloak.truststore.file:src/main/keystore/keycloak.truststore}",
diff --git a/wildfly/server-subsystem/src/main/java/org/keycloak/subsystem/server/extension/KeycloakServerDeploymentProcessor.java b/wildfly/server-subsystem/src/main/java/org/keycloak/subsystem/server/extension/KeycloakServerDeploymentProcessor.java
old mode 100644
new mode 100755
index 8f86b52..1d2230c
--- a/wildfly/server-subsystem/src/main/java/org/keycloak/subsystem/server/extension/KeycloakServerDeploymentProcessor.java
+++ b/wildfly/server-subsystem/src/main/java/org/keycloak/subsystem/server/extension/KeycloakServerDeploymentProcessor.java
@@ -62,6 +62,7 @@ public class KeycloakServerDeploymentProcessor implements DeploymentUnitProcesso
             st.addDependency(cacheContainerService.append("sessions"));
             st.addDependency(cacheContainerService.append("offlineSessions"));
             st.addDependency(cacheContainerService.append("loginFailures"));
+            st.addDependency(cacheContainerService.append("realmVersions"));
         }
     }
 
diff --git a/wildfly/server-subsystem/src/main/resources/subsystem-templates/keycloak-infinispan.xml b/wildfly/server-subsystem/src/main/resources/subsystem-templates/keycloak-infinispan.xml
old mode 100644
new mode 100755
index 57cd94b..74fbae3
--- a/wildfly/server-subsystem/src/main/resources/subsystem-templates/keycloak-infinispan.xml
+++ b/wildfly/server-subsystem/src/main/resources/subsystem-templates/keycloak-infinispan.xml
@@ -30,6 +30,9 @@
                 <local-cache name="sessions"/>
                 <local-cache name="offlineSessions"/>
                 <local-cache name="loginFailures"/>
+                <local-cache name="realmVersions">
+                    <transaction mode="BATCH" locking="PESSIMISTIC"/>
+                </local-cache>
             </cache-container>
             <cache-container name="server" default-cache="default" module="org.wildfly.clustering.server">
                 <local-cache name="default">
@@ -87,6 +90,9 @@
                 <distributed-cache name="sessions" mode="SYNC" owners="1"/>
                 <distributed-cache name="offlineSessions" mode="SYNC" owners="1"/>
                 <distributed-cache name="loginFailures" mode="SYNC" owners="1"/>
+                <local-cache name="realmVersions">
+                    <transaction mode="BATCH" locking="PESSIMISTIC"/>
+                </local-cache>
             </cache-container>
             <cache-container name="server" aliases="singleton cluster" default-cache="default" module="org.wildfly.clustering.server">
                 <transport lock-timeout="60000"/>