keycloak-uncached

KEYCLOAK-2179 Cache fixes

12/2/2015 6:42:48 PM

Details

diff --git a/model/invalidation-cache/infinispan/src/main/java/org/keycloak/models/cache/infinispan/ClientAdapter.java b/model/invalidation-cache/infinispan/src/main/java/org/keycloak/models/cache/infinispan/ClientAdapter.java
index 20c8725..2f7e61f 100755
--- a/model/invalidation-cache/infinispan/src/main/java/org/keycloak/models/cache/infinispan/ClientAdapter.java
+++ b/model/invalidation-cache/infinispan/src/main/java/org/keycloak/models/cache/infinispan/ClientAdapter.java
@@ -29,7 +29,7 @@ public class ClientAdapter implements ClientModel {
     private void getDelegateForUpdate() {
         if (updated == null) {
             cacheSession.registerApplicationInvalidation(getId());
-            updated = updated = cacheSession.getDelegate().getClientById(getId(), cachedRealm);
+            updated = cacheSession.getDelegate().getClientById(getId(), cachedRealm);
             if (updated == null) throw new IllegalStateException("Not found in database");
         }
     }
diff --git a/model/invalidation-cache/infinispan/src/main/java/org/keycloak/models/cache/infinispan/InfinispanCacheRealmProviderFactory.java b/model/invalidation-cache/infinispan/src/main/java/org/keycloak/models/cache/infinispan/InfinispanCacheRealmProviderFactory.java
index 38fddbd..5a76153 100755
--- a/model/invalidation-cache/infinispan/src/main/java/org/keycloak/models/cache/infinispan/InfinispanCacheRealmProviderFactory.java
+++ b/model/invalidation-cache/infinispan/src/main/java/org/keycloak/models/cache/infinispan/InfinispanCacheRealmProviderFactory.java
@@ -1,6 +1,16 @@
 package org.keycloak.models.cache.infinispan;
 
 import org.infinispan.Cache;
+import org.infinispan.notifications.Listener;
+import org.infinispan.notifications.cachelistener.annotation.CacheEntriesEvicted;
+import org.infinispan.notifications.cachelistener.annotation.CacheEntryCreated;
+import org.infinispan.notifications.cachelistener.annotation.CacheEntryInvalidated;
+import org.infinispan.notifications.cachelistener.annotation.CacheEntryRemoved;
+import org.infinispan.notifications.cachelistener.event.CacheEntriesEvictedEvent;
+import org.infinispan.notifications.cachelistener.event.CacheEntryCreatedEvent;
+import org.infinispan.notifications.cachelistener.event.CacheEntryInvalidatedEvent;
+import org.infinispan.notifications.cachelistener.event.CacheEntryRemovedEvent;
+import org.jboss.logging.Logger;
 import org.keycloak.Config;
 import org.keycloak.connections.infinispan.InfinispanConnectionProvider;
 import org.keycloak.models.KeycloakSession;
@@ -8,6 +18,7 @@ import org.keycloak.models.KeycloakSessionFactory;
 import org.keycloak.models.cache.CacheRealmProvider;
 import org.keycloak.models.cache.CacheRealmProviderFactory;
 import org.keycloak.models.cache.RealmCache;
+import org.keycloak.models.cache.entities.CachedRealm;
 import org.keycloak.models.cache.entities.CachedUser;
 
 import java.util.concurrent.ConcurrentHashMap;
@@ -18,10 +29,14 @@ import java.util.concurrent.ConcurrentHashMap;
  */
 public class InfinispanCacheRealmProviderFactory implements CacheRealmProviderFactory {
 
-    protected final ConcurrentHashMap<String, String> realmLookup = new ConcurrentHashMap<String, String>();
+    private static final Logger log = Logger.getLogger(InfinispanCacheRealmProviderFactory.class);
 
     protected volatile InfinispanRealmCache realmCache;
 
+    protected final ConcurrentHashMap<String, String> realmLookup = new ConcurrentHashMap<>();
+
+    private boolean isNewInfinispan;
+
     @Override
     public CacheRealmProvider create(KeycloakSession session) {
         lazyInit(session);
@@ -32,13 +47,25 @@ public class InfinispanCacheRealmProviderFactory implements CacheRealmProviderFa
         if (realmCache == null) {
             synchronized (this) {
                 if (realmCache == null) {
+                    checkIspnVersion();
+
                     Cache<String, Object> cache = session.getProvider(InfinispanConnectionProvider.class).getCache(InfinispanConnectionProvider.REALM_CACHE_NAME);
+                    cache.addListener(new CacheListener());
                     realmCache = new InfinispanRealmCache(cache, realmLookup);
                 }
             }
         }
     }
 
+    protected void checkIspnVersion() {
+        try {
+            CacheEntryCreatedEvent.class.getMethod("getValue");
+            isNewInfinispan = true;
+        } catch (NoSuchMethodException nsme) {
+            isNewInfinispan = false;
+        }
+    }
+
     @Override
     public void init(Config.Scope config) {
     }
@@ -57,4 +84,71 @@ public class InfinispanCacheRealmProviderFactory implements CacheRealmProviderFa
         return "infinispan";
     }
 
+    @Listener
+    public class CacheListener {
+
+        @CacheEntryCreated
+        public void created(CacheEntryCreatedEvent<String, Object> event) {
+            if (!event.isPre()) {
+                Object object;
+
+                // Try optimized version if available
+                if (isNewInfinispan) {
+                    object = event.getValue();
+                } else {
+                    String id = event.getKey();
+                    object = event.getCache().get(id);
+                }
+
+                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 c : realm.getClients().values()) {
+                    realmCache.evictCachedApplicationById(c);
+                }
+
+                log.tracev("Realm removed realm={0}", realm.getName());
+            }
+        }
+    }
 }
diff --git a/model/invalidation-cache/infinispan/src/main/java/org/keycloak/models/cache/infinispan/InfinispanRealmCache.java b/model/invalidation-cache/infinispan/src/main/java/org/keycloak/models/cache/infinispan/InfinispanRealmCache.java
index 8458369..8155801 100755
--- a/model/invalidation-cache/infinispan/src/main/java/org/keycloak/models/cache/infinispan/InfinispanRealmCache.java
+++ b/model/invalidation-cache/infinispan/src/main/java/org/keycloak/models/cache/infinispan/InfinispanRealmCache.java
@@ -104,6 +104,12 @@ public class InfinispanRealmCache implements RealmCache {
     }
 
     @Override
+    public void evictCachedApplicationById(String id) {
+        logger.tracev("Evicting application {0}", id);
+        cache.evict(id);
+    }
+
+    @Override
     public CachedGroup getGroup(String id) {
         if (!enabled) return null;
         return get(id, CachedGroup.class);
diff --git a/model/invalidation-cache/model-adapters/src/main/java/org/keycloak/models/cache/RealmCache.java b/model/invalidation-cache/model-adapters/src/main/java/org/keycloak/models/cache/RealmCache.java
index df74b53..007daac 100755
--- a/model/invalidation-cache/model-adapters/src/main/java/org/keycloak/models/cache/RealmCache.java
+++ b/model/invalidation-cache/model-adapters/src/main/java/org/keycloak/models/cache/RealmCache.java
@@ -26,6 +26,8 @@ public interface RealmCache {
 
     void invalidateApplication(CachedClient app);
 
+    void evictCachedApplicationById(String id);
+
     void addCachedClient(CachedClient app);
 
     void invalidateCachedApplicationById(String id);
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/model/CacheTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/model/CacheTest.java
index 597e7dd..a9b907c 100755
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/model/CacheTest.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/model/CacheTest.java
@@ -7,10 +7,12 @@ import org.junit.Assert;
 import org.junit.ClassRule;
 import org.junit.Test;
 import org.keycloak.models.*;
+import org.keycloak.models.cache.infinispan.ClientAdapter;
 import org.keycloak.models.cache.infinispan.RealmAdapter;
 import org.keycloak.testsuite.rule.KeycloakRule;
 
 import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
 
 /**
  * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
@@ -27,10 +29,12 @@ public class CacheTest {
             // load up cache
             KeycloakSession session = kc.startSession();
             RealmModel realm = session.realms().getRealmByName("test");
+            assertTrue(realm instanceof RealmAdapter);
             ClientModel testApp = realm.getClientByClientId("test-app");
+            assertTrue(testApp instanceof ClientAdapter);
             assertNotNull(testApp);
             appId = testApp.getId();
-            Assert.assertTrue(testApp.isEnabled());
+            assertTrue(testApp.isEnabled());
             kc.stopSession(session, true);
         }
         {
@@ -40,16 +44,18 @@ public class CacheTest {
             // KEYCLOAK-1240 - obtain the realm via session.realms().getRealms()
             RealmModel realm = null;
             List<RealmModel> realms = session.realms().getRealms();
+
             for (RealmModel current : realms) {
+                assertTrue(current instanceof RealmAdapter);
                 if ("test".equals(current.getName())) {
                     realm = current;
                     break;
                 }
             }
 
-            Assert.assertTrue(realm instanceof RealmAdapter);
             realm.setAccessCodeLifespanLogin(200);
             ClientModel testApp = realm.getClientByClientId("test-app");
+
             assertNotNull(testApp);
             testApp.setEnabled(false);
             kc.stopSession(session, true);
@@ -62,10 +68,7 @@ public class CacheTest {
             ClientModel testApp = session.realms().getClientById(appId, realm);
             Assert.assertFalse(testApp.isEnabled());
             kc.stopSession(session, true);
-
         }
-
-
     }
 
     @Test