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 20c87251a19..2f7e61f0f39 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 38fddbd4a84..5a761537938 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 realmLookup = new ConcurrentHashMap(); + private static final Logger log = Logger.getLogger(InfinispanCacheRealmProviderFactory.class); protected volatile InfinispanRealmCache realmCache; + protected final ConcurrentHashMap 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 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 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 event) { + if (event.isPre()) { + Object object = event.getValue(); + if (object != null) { + remove(object); + } + } + } + + @CacheEntryInvalidated + public void removed(CacheEntryInvalidatedEvent event) { + if (event.isPre()) { + Object object = event.getValue(); + if (object != null) { + remove(object); + } + } + } + + @CacheEntriesEvicted + public void userEvicted(CacheEntriesEvictedEvent 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 845836965d6..81558012b41 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 @@ -103,6 +103,12 @@ public class InfinispanRealmCache implements RealmCache { cache.remove(id); } + @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; 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 df74b536af2..007daac7795 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 597e7dd5d44..a9b907c6122 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 Bill Burke @@ -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 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