keycloak-aplcache

caching

1/29/2018 3:28:17 PM

Details

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 01cfd8c..3176506 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
@@ -39,14 +39,12 @@ import java.util.Set;
 public class ClientAdapter implements ClientModel {
     protected RealmCacheSession cacheSession;
     protected RealmModel cachedRealm;
-    protected RealmCache cache;
 
     protected ClientModel updated;
     protected CachedClient cached;
 
-    public ClientAdapter(RealmModel cachedRealm, CachedClient cached, RealmCacheSession cacheSession, RealmCache cache) {
+    public ClientAdapter(RealmModel cachedRealm, CachedClient cached, RealmCacheSession cacheSession) {
         this.cachedRealm = cachedRealm;
-        this.cache = cache;
         this.cacheSession = cacheSession;
         this.cached = cached;
     }
diff --git a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/entities/AbstractRevisioned.java b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/entities/AbstractRevisioned.java
index 22fef96..8c88df7 100755
--- a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/entities/AbstractRevisioned.java
+++ b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/entities/AbstractRevisioned.java
@@ -1,6 +1,7 @@
 package org.keycloak.models.cache.infinispan.entities;
 
 import org.keycloak.common.util.Time;
+import org.keycloak.models.cache.CachedObject;
 
 import java.io.Serializable;
 
@@ -8,7 +9,7 @@ import java.io.Serializable;
  * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
  * @version $Revision: 1 $
  */
-public class AbstractRevisioned implements Revisioned, Serializable {
+public class AbstractRevisioned implements Revisioned, Serializable, CachedObject {
     private String id;
     private Long revision;
     private final long cacheTimestamp = Time.currentTimeMillis();
@@ -38,6 +39,7 @@ public class AbstractRevisioned implements Revisioned, Serializable {
      *
      * @return
      */
+    @Override
     public long getCacheTimestamp() {
         return cacheTimestamp;
     }
diff --git a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/RealmAdapter.java b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/RealmAdapter.java
index b80bbe3..17cacc8 100755
--- a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/RealmAdapter.java
+++ b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/RealmAdapter.java
@@ -24,6 +24,7 @@ import org.keycloak.models.*;
 import org.keycloak.models.cache.CachedRealmModel;
 import org.keycloak.models.cache.infinispan.entities.CachedRealm;
 import org.keycloak.storage.UserStorageProvider;
+import org.keycloak.storage.client.ClientStorageProvider;
 
 import java.util.*;
 import java.util.concurrent.ConcurrentHashMap;
@@ -36,7 +37,6 @@ public class RealmAdapter implements CachedRealmModel {
     protected CachedRealm cached;
     protected RealmCacheSession cacheSession;
     protected volatile RealmModel updated;
-    protected RealmCache cache;
     protected KeycloakSession session;
 
     public RealmAdapter(KeycloakSession session, CachedRealm cached, RealmCacheSession cacheSession) {
@@ -1323,35 +1323,37 @@ public class RealmAdapter implements CachedRealmModel {
     @Override
     public ComponentModel addComponentModel(ComponentModel model) {
         getDelegateForUpdate();
-        evictUsers(model);
+        executeEvictions(model);
         return updated.addComponentModel(model);
     }
 
     @Override
     public ComponentModel importComponentModel(ComponentModel model) {
         getDelegateForUpdate();
-        evictUsers(model);
+        executeEvictions(model);
         return updated.importComponentModel(model);
     }
 
-    public void evictUsers(ComponentModel model) {
-        String parentId = model.getParentId();
-        evictUsers(parentId);
-    }
+    public void executeEvictions(ComponentModel model) {
+        if (model == null) return;
+        // test that this is a realm component
+        if (model.getParentId() != null && !model.getParentId().equals(getId())) return;
 
-    public void evictUsers(String parentId) {
-        if (parentId != null && !parentId.equals(getId())) {
-            ComponentModel parent = getComponent(parentId);
-            if (parent != null && UserStorageProvider.class.getName().equals(parent.getProviderType())) {
-                session.userCache().evict(this);
-            }
+        // invalidate entire user cache if we're dealing with user storage SPI
+        if (UserStorageProvider.class.getName().equals(model.getProviderType())) {
+            session.userCache().evict(this);
+        }
+        // invalidate entire realm if we're dealing with client storage SPI
+        // entire realm because of client roles, client lists, and clients
+        if (ClientStorageProvider.class.getName().equals(model.getProviderType())) {
+            cacheSession.evictRealmOnRemoval(this);
         }
     }
 
     @Override
     public void updateComponent(ComponentModel component) {
         getDelegateForUpdate();
-        evictUsers(component);
+        executeEvictions(component);
         updated.updateComponent(component);
 
     }
@@ -1359,7 +1361,7 @@ public class RealmAdapter implements CachedRealmModel {
     @Override
     public void removeComponent(ComponentModel component) {
         getDelegateForUpdate();
-        evictUsers(component);
+        executeEvictions(component);
         updated.removeComponent(component);
 
     }
@@ -1367,7 +1369,6 @@ public class RealmAdapter implements CachedRealmModel {
     @Override
     public void removeComponents(String parentId) {
         getDelegateForUpdate();
-        evictUsers(parentId);
         updated.removeComponents(parentId);
 
     }
diff --git a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/RealmCacheSession.java b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/RealmCacheSession.java
index 289e3a1..018213f 100755
--- a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/RealmCacheSession.java
+++ b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/RealmCacheSession.java
@@ -19,6 +19,7 @@ package org.keycloak.models.cache.infinispan;
 
 import org.jboss.logging.Logger;
 import org.keycloak.cluster.ClusterProvider;
+import org.keycloak.component.ComponentModel;
 import org.keycloak.migration.MigrationModel;
 import org.keycloak.models.*;
 import org.keycloak.models.cache.CacheRealmProvider;
@@ -26,6 +27,9 @@ import org.keycloak.models.cache.CachedRealmModel;
 import org.keycloak.models.cache.infinispan.entities.*;
 import org.keycloak.models.cache.infinispan.events.*;
 import org.keycloak.models.utils.KeycloakModelUtils;
+import org.keycloak.storage.CacheableStorageProviderModel;
+import org.keycloak.storage.StorageId;
+import org.keycloak.storage.client.ClientStorageProviderModel;
 
 import java.util.*;
 
@@ -100,7 +104,7 @@ public class RealmCacheSession implements CacheRealmProvider {
     protected boolean setRollbackOnly;
 
     protected Map<String, RealmAdapter> managedRealms = new HashMap<>();
-    protected Map<String, ClientAdapter> managedApplications = new HashMap<>();
+    protected Map<String, ClientModel> managedApplications = new HashMap<>();
     protected Map<String, ClientTemplateAdapter> managedClientTemplates = new HashMap<>();
     protected Map<String, RoleAdapter> managedRoles = new HashMap<>();
     protected Map<String, GroupAdapter> managedGroups = new HashMap<>();
@@ -173,8 +177,8 @@ public class RealmCacheSession implements CacheRealmProvider {
 
     private void invalidateClient(String id) {
         invalidations.add(id);
-        ClientAdapter adapter = managedApplications.get(id);
-        if (adapter != null) adapter.invalidate();
+        ClientModel adapter = managedApplications.get(id);
+        if (adapter != null && adapter instanceof ClientAdapter) ((ClientAdapter)adapter).invalidate();
     }
 
     @Override
@@ -204,9 +208,9 @@ public class RealmCacheSession implements CacheRealmProvider {
         invalidations.addAll(newInvalidations);
         // need to make sure that scope and group mapping clients and groups are invalidated
         for (String id : newInvalidations) {
-            ClientAdapter adapter = managedApplications.get(id);
-            if (adapter != null) {
-                adapter.invalidate();
+            ClientModel adapter = managedApplications.get(id);
+            if (adapter != null && adapter instanceof ClientAdapter){
+                ((ClientAdapter)adapter).invalidate();
                 continue;
             }
             GroupAdapter group = managedGroups.get(id);
@@ -329,7 +333,6 @@ public class RealmCacheSession implements CacheRealmProvider {
             @Override
             public void commit() {
                 try {
-                    if (realmDelegate == null) return;
                     if (clearAll) {
                         cache.clear();
                     }
@@ -470,12 +473,16 @@ public class RealmCacheSession implements CacheRealmProvider {
         RealmModel realm = getRealm(id);
         if (realm == null) return false;
 
-        cache.invalidateObject(id);
-        invalidationEvents.add(RealmRemovedEvent.create(id, realm.getName()));
-        cache.realmRemoval(id, realm.getName(), invalidations);
+        evictRealmOnRemoval(realm);
         return getRealmDelegate().removeRealm(id);
     }
 
+    public void evictRealmOnRemoval(RealmModel realm) {
+        cache.invalidateObject(realm.getId());
+        invalidationEvents.add(RealmRemovedEvent.create(realm.getId(), realm.getName()));
+        cache.realmRemoval(realm.getId(), realm.getName(), invalidations);
+    }
+
 
     @Override
     public ClientModel addClient(RealmModel realm, String clientId) {
@@ -1020,20 +1027,78 @@ public class RealmCacheSession implements CacheRealmProvider {
             Long loaded = cache.getCurrentRevision(id);
             ClientModel model = getClientDelegate().getClientById(id, realm);
             if (model == null) return null;
-            if (invalidations.contains(id)) return model;
-            cached = new CachedClient(loaded, realm, model);
-            logger.tracev("adding client by id cache miss: {0}", cached.getClientId());
-            cache.addRevisioned(cached, startupRevision);
+            ClientModel adapter = cacheClient(realm, model, loaded);
+            managedApplications.put(id, adapter);
+            return adapter;
         } else if (invalidations.contains(id)) {
             return getRealmDelegate().getClientById(id, realm);
         } else if (managedApplications.containsKey(id)) {
             return managedApplications.get(id);
         }
-        ClientAdapter adapter = new ClientAdapter(realm, cached, this, null);
+        ClientModel adapter = validateCache(realm, cached);
         managedApplications.put(id, adapter);
         return adapter;
     }
 
+    protected ClientModel cacheClient(RealmModel realm, ClientModel delegate, Long revision) {
+        if (invalidations.contains(delegate.getId())) return delegate;
+        StorageId storageId = new StorageId(delegate.getId());
+        CachedClient cached = null;
+        ClientAdapter adapter = null;
+
+        if (!storageId.isLocal()) {
+            ComponentModel component = realm.getComponent(storageId.getProviderId());
+            ClientStorageProviderModel model = new ClientStorageProviderModel(component);
+            if (!model.isEnabled()) {
+                return delegate;
+            }
+            ClientStorageProviderModel.CachePolicy policy = model.getCachePolicy();
+            if (policy != null && policy == ClientStorageProviderModel.CachePolicy.NO_CACHE) {
+                return delegate;
+            }
+
+            cached = new CachedClient(revision, realm, delegate);
+            adapter = new ClientAdapter(realm, cached, this);
+
+            long lifespan = model.getLifespan();
+            if (lifespan > 0) {
+                cache.addRevisioned(cached, startupRevision, lifespan);
+            } else {
+                cache.addRevisioned(cached, startupRevision);
+            }
+        } else {
+            cached = new CachedClient(revision, realm, delegate);
+            adapter = new ClientAdapter(realm, cached, this);
+            cache.addRevisioned(cached, startupRevision);
+        }
+
+        return adapter;
+    }
+
+
+    protected ClientModel validateCache(RealmModel realm, CachedClient cached) {
+        if (!realm.getId().equals(cached.getRealm())) {
+            return null;
+        }
+
+        StorageId storageId = new StorageId(cached.getId());
+        if (!storageId.isLocal()) {
+            ComponentModel component = realm.getComponent(storageId.getProviderId());
+            ClientStorageProviderModel model = new ClientStorageProviderModel(component);
+
+            // although we do set a timeout, Infinispan has no guarantees when the user will be evicted
+            // its also hard to test stuff
+            if (model.shouldInvalidate(cached)) {
+                registerClientInvalidation(cached.getId(), cached.getClientId(), realm.getId());
+                return getClientDelegate().getClientById(cached.getId(), realm);
+            }
+        }
+        ClientAdapter adapter = new ClientAdapter(realm, cached, this);
+
+        return adapter;
+    }
+
+
     @Override
     public ClientModel getClientByClientId(String clientId, RealmModel realm) {
         String cacheKey = getClientByClientIdCacheKey(clientId, realm.getId());
diff --git a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/UserCacheSession.java b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/UserCacheSession.java
index 4b5309a..e2fcc83 100755
--- a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/UserCacheSession.java
+++ b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/UserCacheSession.java
@@ -19,6 +19,7 @@ package org.keycloak.models.cache.infinispan;
 
 import org.jboss.logging.Logger;
 import org.keycloak.cluster.ClusterProvider;
+import org.keycloak.models.cache.CachedObject;
 import org.keycloak.models.cache.infinispan.events.InvalidationEvent;
 import org.keycloak.common.constants.ServiceAccountConstants;
 import org.keycloak.common.util.Time;
@@ -49,6 +50,7 @@ import org.keycloak.models.cache.infinispan.events.UserFederationLinkUpdatedEven
 import org.keycloak.models.cache.infinispan.events.UserFullInvalidationEvent;
 import org.keycloak.models.cache.infinispan.events.UserUpdatedEvent;
 import org.keycloak.models.utils.ReadOnlyUserModelDelegate;
+import org.keycloak.storage.CacheableStorageProviderModel;
 import org.keycloak.storage.StorageId;
 import org.keycloak.storage.UserStorageProvider;
 import org.keycloak.storage.UserStorageProviderModel;
@@ -144,7 +146,6 @@ public class UserCacheSession implements UserCache {
 
             @Override
             public void commit() {
-                if (delegate == null) return;
                 runInvalidations();
                 transactionActive = false;
             }
@@ -296,46 +297,11 @@ public class UserCacheSession implements UserCache {
 
         if (!storageId.isLocal()) {
             ComponentModel component = realm.getComponent(storageId.getProviderId());
-            UserStorageProviderModel model = new UserStorageProviderModel(component);
+            CacheableStorageProviderModel model = new CacheableStorageProviderModel(component);
 
             // although we do set a timeout, Infinispan has no guarantees when the user will be evicted
             // its also hard to test stuff
-            boolean invalidate = false;
-            if (!model.isEnabled()) {
-                invalidate = true;
-            } else {
-                UserStorageProviderModel.CachePolicy policy = model.getCachePolicy();
-                if (policy != null) {
-                    //String currentTime = DateFormat.getDateTimeInstance(DateFormat.FULL, DateFormat.FULL).format(new Date(Time.currentTimeMillis()));
-                    if (policy == UserStorageProviderModel.CachePolicy.NO_CACHE) {
-                        invalidate = true;
-                    } else if (cached.getCacheTimestamp() < model.getCacheInvalidBefore()) {
-                        invalidate = true;
-                    } else if (policy == UserStorageProviderModel.CachePolicy.MAX_LIFESPAN) {
-                        if (cached.getCacheTimestamp() + model.getMaxLifespan() < Time.currentTimeMillis()) {
-                            invalidate = true;
-                        }
-                    } else if (policy == UserStorageProviderModel.CachePolicy.EVICT_DAILY) {
-                        long dailyTimeout = dailyTimeout(model.getEvictionHour(), model.getEvictionMinute());
-                        dailyTimeout = dailyTimeout - (24 * 60 * 60 * 1000);
-                        //String timeout = DateFormat.getDateTimeInstance(DateFormat.FULL, DateFormat.FULL).format(new Date(dailyTimeout));
-                        //String stamp = DateFormat.getDateTimeInstance(DateFormat.FULL, DateFormat.FULL).format(new Date(cached.getCacheTimestamp()));
-                        if (cached.getCacheTimestamp() <= dailyTimeout) {
-                            invalidate = true;
-                        }
-                    } else if (policy == UserStorageProviderModel.CachePolicy.EVICT_WEEKLY) {
-                        int oneWeek = 7 * 24 * 60 * 60 * 1000;
-                        long weeklyTimeout = weeklyTimeout(model.getEvictionDay(), model.getEvictionHour(), model.getEvictionMinute());
-                        long lastTimeout = weeklyTimeout - oneWeek;
-                        //String timeout = DateFormat.getDateTimeInstance(DateFormat.FULL, DateFormat.FULL).format(new Date(weeklyTimeout));
-                        //String stamp = DateFormat.getDateTimeInstance(DateFormat.FULL, DateFormat.FULL).format(new Date(cached.getCacheTimestamp()));
-                        if (cached.getCacheTimestamp() <= lastTimeout) {
-                            invalidate = true;
-                        }
-                    }
-                }
-            }
-            if (invalidate) {
+            if (model.shouldInvalidate(cached)) {
                 registerUserInvalidation(realm, cached);
                 return getDelegate().getUserById(cached.getId(), realm);
             }
@@ -371,26 +337,11 @@ public class UserCacheSession implements UserCache {
             adapter = new UserAdapter(cached, this, session, realm);
             onCache(realm, adapter, delegate);
 
-            if (policy == null || policy == UserStorageProviderModel.CachePolicy.DEFAULT) {
-                cache.addRevisioned(cached, startupRevision);
+            long lifespan = model.getLifespan();
+            if (lifespan > 0) {
+                cache.addRevisioned(cached, startupRevision, lifespan);
             } else {
-                long lifespan = -1;
-                if (policy == UserStorageProviderModel.CachePolicy.EVICT_DAILY) {
-                    if (model.getEvictionHour() > -1 && model.getEvictionMinute() > -1) {
-                        lifespan = dailyTimeout(model.getEvictionHour(), model.getEvictionMinute()) - Time.currentTimeMillis();
-                    }
-                } else if (policy == UserStorageProviderModel.CachePolicy.EVICT_WEEKLY) {
-                    if (model.getEvictionDay() > 0 && model.getEvictionHour() > -1 && model.getEvictionMinute() > -1) {
-                        lifespan = weeklyTimeout(model.getEvictionDay(), model.getEvictionHour(), model.getEvictionMinute()) - Time.currentTimeMillis();
-                    }
-                } else if (policy == UserStorageProviderModel.CachePolicy.MAX_LIFESPAN) {
-                    lifespan = model.getMaxLifespan();
-                }
-                if (lifespan > 0) {
-                    cache.addRevisioned(cached, startupRevision, lifespan);
-                } else {
-                    cache.addRevisioned(cached, startupRevision);
-                }
+                cache.addRevisioned(cached, startupRevision);
             }
         } else {
             cached = new CachedUser(revision, realm, delegate, notBefore);
@@ -402,39 +353,6 @@ public class UserCacheSession implements UserCache {
         return adapter;
     }
 
-
-    public static long dailyTimeout(int hour, int minute) {
-        Calendar cal = Calendar.getInstance();
-        Calendar cal2 = Calendar.getInstance();
-        cal.setTimeInMillis(Time.currentTimeMillis());
-        cal2.setTimeInMillis(Time.currentTimeMillis());
-        cal2.set(Calendar.HOUR_OF_DAY, hour);
-        cal2.set(Calendar.MINUTE, minute);
-        if (cal2.getTimeInMillis() < cal.getTimeInMillis()) {
-            int add = (24 * 60 * 60 * 1000);
-            cal.add(Calendar.MILLISECOND, add);
-        } else {
-            cal.add(Calendar.MILLISECOND, (int)(cal2.getTimeInMillis() - cal.getTimeInMillis()));
-        }
-        return cal.getTimeInMillis();
-    }
-
-    public static long weeklyTimeout(int day, int hour, int minute) {
-        Calendar cal = Calendar.getInstance();
-        Calendar cal2 = Calendar.getInstance();
-        cal.setTimeInMillis(Time.currentTimeMillis());
-        cal2.setTimeInMillis(Time.currentTimeMillis());
-        cal2.set(Calendar.HOUR_OF_DAY, hour);
-        cal2.set(Calendar.MINUTE, minute);
-        cal2.set(Calendar.DAY_OF_WEEK, day);
-        if (cal2.getTimeInMillis() < cal.getTimeInMillis()) {
-            int add = (7 * 24 * 60 * 60 * 1000);
-            cal2.add(Calendar.MILLISECOND, add);
-        }
-
-        return cal2.getTimeInMillis();
-    }
-
     private void onCache(RealmModel realm, UserAdapter adapter, UserModel delegate) {
         ((OnUserCache)getDelegate()).onCache(realm, adapter, delegate);
         ((OnUserCache)session.userCredentialManager()).onCache(realm, adapter, delegate);
diff --git a/model/infinispan/src/test/java/org/keycloak/models/sessions/infinispan/initializer/InitializerStateTest.java b/model/infinispan/src/test/java/org/keycloak/models/sessions/infinispan/initializer/InitializerStateTest.java
index 32210a0..d26f2b9 100644
--- a/model/infinispan/src/test/java/org/keycloak/models/sessions/infinispan/initializer/InitializerStateTest.java
+++ b/model/infinispan/src/test/java/org/keycloak/models/sessions/infinispan/initializer/InitializerStateTest.java
@@ -20,6 +20,7 @@ package org.keycloak.models.sessions.infinispan.initializer;
 import org.junit.Assert;
 import org.junit.Test;
 import org.keycloak.models.cache.infinispan.UserCacheSession;
+import org.keycloak.storage.CacheableStorageProviderModel;
 
 import java.text.DateFormat;
 import java.util.Calendar;
@@ -65,13 +66,13 @@ public class InitializerStateTest {
 
     @Test
     public void testDailyTimeout() throws Exception {
-        Date date = new Date(UserCacheSession.dailyTimeout(10, 30));
+        Date date = new Date(CacheableStorageProviderModel.dailyTimeout(10, 30));
         System.out.println(DateFormat.getDateTimeInstance(DateFormat.FULL, DateFormat.FULL).format(date));
-        date = new Date(UserCacheSession.dailyTimeout(17, 45));
+        date = new Date(CacheableStorageProviderModel.dailyTimeout(17, 45));
         System.out.println(DateFormat.getDateTimeInstance(DateFormat.FULL, DateFormat.FULL).format(date));
-        date = new Date(UserCacheSession.weeklyTimeout(Calendar.MONDAY, 13, 45));
+        date = new Date(CacheableStorageProviderModel.weeklyTimeout(Calendar.MONDAY, 13, 45));
         System.out.println(DateFormat.getDateTimeInstance(DateFormat.FULL, DateFormat.FULL).format(date));
-        date = new Date(UserCacheSession.weeklyTimeout(Calendar.THURSDAY, 13, 45));
+        date = new Date(CacheableStorageProviderModel.weeklyTimeout(Calendar.THURSDAY, 13, 45));
         System.out.println(DateFormat.getDateTimeInstance(DateFormat.FULL, DateFormat.FULL).format(date));
         System.out.println("----");
         Calendar cal = Calendar.getInstance();
@@ -80,7 +81,7 @@ public class InitializerStateTest {
         int min = cal.get(Calendar.MINUTE);
         date = new Date(cal.getTimeInMillis());
         System.out.println(DateFormat.getDateTimeInstance(DateFormat.FULL, DateFormat.FULL).format(date));
-        date = new Date(UserCacheSession.dailyTimeout(hour, min));
+        date = new Date(CacheableStorageProviderModel.dailyTimeout(hour, min));
         System.out.println(DateFormat.getDateTimeInstance(DateFormat.FULL, DateFormat.FULL).format(date));
         cal = Calendar.getInstance();
         cal.set(Calendar.DAY_OF_WEEK, Calendar.MONDAY);
diff --git a/server-spi/src/main/java/org/keycloak/storage/CacheableStorageProviderModel.java b/server-spi/src/main/java/org/keycloak/storage/CacheableStorageProviderModel.java
index 8263bfe..dd740f2 100644
--- a/server-spi/src/main/java/org/keycloak/storage/CacheableStorageProviderModel.java
+++ b/server-spi/src/main/java/org/keycloak/storage/CacheableStorageProviderModel.java
@@ -16,8 +16,12 @@
  */
 package org.keycloak.storage;
 
+import org.keycloak.common.util.Time;
 import org.keycloak.component.ComponentModel;
 import org.keycloak.component.PrioritizedComponentModel;
+import org.keycloak.models.cache.CachedObject;
+
+import java.util.Calendar;
 
 /**
  * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
@@ -30,6 +34,7 @@ public class CacheableStorageProviderModel extends PrioritizedComponentModel {
     public static final String EVICTION_MINUTE = "evictionMinute";
     public static final String EVICTION_DAY = "evictionDay";
     public static final String CACHE_INVALID_BEFORE = "cacheInvalidBefore";
+    public static final String ENABLED = "enabled";
 
     private transient CachePolicy cachePolicy;
     private transient long maxLifespan = -1;
@@ -37,6 +42,7 @@ public class CacheableStorageProviderModel extends PrioritizedComponentModel {
     private transient int evictionMinute = -1;
     private transient int evictionDay = -1;
     private transient long cacheInvalidBefore = -1;
+    private transient Boolean enabled;
 
     public CacheableStorageProviderModel() {
     }
@@ -137,6 +143,117 @@ public class CacheableStorageProviderModel extends PrioritizedComponentModel {
         getConfig().putSingle(CACHE_INVALID_BEFORE, Long.toString(cacheInvalidBefore));
     }
 
+    public void setEnabled(boolean flag) {
+        enabled = flag;
+        getConfig().putSingle(ENABLED, Boolean.toString(flag));
+    }
+
+    public boolean isEnabled() {
+        if (enabled == null) {
+            String val = getConfig().getFirst(ENABLED);
+            if (val == null) {
+                enabled = true;
+            } else {
+                enabled = Boolean.valueOf(val);
+            }
+        }
+        return enabled;
+
+    }
+
+    public long getLifespan() {
+        UserStorageProviderModel.CachePolicy policy = getCachePolicy();
+        long lifespan = -1;
+        if (policy == null || policy == UserStorageProviderModel.CachePolicy.DEFAULT) {
+            lifespan = -1;
+        } else if (policy == CacheableStorageProviderModel.CachePolicy.EVICT_DAILY) {
+            if (getEvictionHour() > -1 && getEvictionMinute() > -1) {
+                lifespan = dailyTimeout(getEvictionHour(), getEvictionMinute()) - Time.currentTimeMillis();
+            }
+        } else if (policy == CacheableStorageProviderModel.CachePolicy.EVICT_WEEKLY) {
+            if (getEvictionDay() > 0 && getEvictionHour() > -1 && getEvictionMinute() > -1) {
+                lifespan = weeklyTimeout(getEvictionDay(), getEvictionHour(), getEvictionMinute()) - Time.currentTimeMillis();
+            }
+        } else if (policy == CacheableStorageProviderModel.CachePolicy.MAX_LIFESPAN) {
+            lifespan = getMaxLifespan();
+        }
+        return lifespan;
+    }
+
+    public boolean shouldInvalidate(CachedObject cached) {
+        boolean invalidate = false;
+        if (!isEnabled()) {
+            invalidate = true;
+        } else {
+            CacheableStorageProviderModel.CachePolicy policy = getCachePolicy();
+            if (policy != null) {
+                //String currentTime = DateFormat.getDateTimeInstance(DateFormat.FULL, DateFormat.FULL).format(new Date(Time.currentTimeMillis()));
+                if (policy == CacheableStorageProviderModel.CachePolicy.NO_CACHE) {
+                    invalidate = true;
+                } else if (cached.getCacheTimestamp() < getCacheInvalidBefore()) {
+                    invalidate = true;
+                } else if (policy == CacheableStorageProviderModel.CachePolicy.MAX_LIFESPAN) {
+                    if (cached.getCacheTimestamp() + getMaxLifespan() < Time.currentTimeMillis()) {
+                        invalidate = true;
+                    }
+                } else if (policy == CacheableStorageProviderModel.CachePolicy.EVICT_DAILY) {
+                    long dailyTimeout = dailyTimeout(getEvictionHour(), getEvictionMinute());
+                    dailyTimeout = dailyTimeout - (24 * 60 * 60 * 1000);
+                    //String timeout = DateFormat.getDateTimeInstance(DateFormat.FULL, DateFormat.FULL).format(new Date(dailyTimeout));
+                    //String stamp = DateFormat.getDateTimeInstance(DateFormat.FULL, DateFormat.FULL).format(new Date(cached.getCacheTimestamp()));
+                    if (cached.getCacheTimestamp() <= dailyTimeout) {
+                        invalidate = true;
+                    }
+                } else if (policy == CacheableStorageProviderModel.CachePolicy.EVICT_WEEKLY) {
+                    int oneWeek = 7 * 24 * 60 * 60 * 1000;
+                    long weeklyTimeout = weeklyTimeout(getEvictionDay(), getEvictionHour(), getEvictionMinute());
+                    long lastTimeout = weeklyTimeout - oneWeek;
+                    //String timeout = DateFormat.getDateTimeInstance(DateFormat.FULL, DateFormat.FULL).format(new Date(weeklyTimeout));
+                    //String stamp = DateFormat.getDateTimeInstance(DateFormat.FULL, DateFormat.FULL).format(new Date(cached.getCacheTimestamp()));
+                    if (cached.getCacheTimestamp() <= lastTimeout) {
+                        invalidate = true;
+                    }
+                }
+            }
+        }
+        return invalidate;
+    }
+
+
+    public static long dailyTimeout(int hour, int minute) {
+        Calendar cal = Calendar.getInstance();
+        Calendar cal2 = Calendar.getInstance();
+        cal.setTimeInMillis(Time.currentTimeMillis());
+        cal2.setTimeInMillis(Time.currentTimeMillis());
+        cal2.set(Calendar.HOUR_OF_DAY, hour);
+        cal2.set(Calendar.MINUTE, minute);
+        if (cal2.getTimeInMillis() < cal.getTimeInMillis()) {
+            int add = (24 * 60 * 60 * 1000);
+            cal.add(Calendar.MILLISECOND, add);
+        } else {
+            cal.add(Calendar.MILLISECOND, (int)(cal2.getTimeInMillis() - cal.getTimeInMillis()));
+        }
+        return cal.getTimeInMillis();
+    }
+
+    public static long weeklyTimeout(int day, int hour, int minute) {
+        Calendar cal = Calendar.getInstance();
+        Calendar cal2 = Calendar.getInstance();
+        cal.setTimeInMillis(Time.currentTimeMillis());
+        cal2.setTimeInMillis(Time.currentTimeMillis());
+        cal2.set(Calendar.HOUR_OF_DAY, hour);
+        cal2.set(Calendar.MINUTE, minute);
+        cal2.set(Calendar.DAY_OF_WEEK, day);
+        if (cal2.getTimeInMillis() < cal.getTimeInMillis()) {
+            int add = (7 * 24 * 60 * 60 * 1000);
+            cal2.add(Calendar.MILLISECOND, add);
+        }
+
+        return cal2.getTimeInMillis();
+    }
+
+
+
     public enum CachePolicy {
         NO_CACHE,
         DEFAULT,
diff --git a/server-spi/src/main/java/org/keycloak/storage/UserStorageProviderModel.java b/server-spi/src/main/java/org/keycloak/storage/UserStorageProviderModel.java
index 0b32b94..e145c40 100755
--- a/server-spi/src/main/java/org/keycloak/storage/UserStorageProviderModel.java
+++ b/server-spi/src/main/java/org/keycloak/storage/UserStorageProviderModel.java
@@ -18,7 +18,6 @@
 package org.keycloak.storage;
 
 import org.keycloak.component.ComponentModel;
-import org.keycloak.component.PrioritizedComponentModel;
 
 /**
  * Stored configuration of a User Storage provider instance.
@@ -32,7 +31,6 @@ public class UserStorageProviderModel extends CacheableStorageProviderModel {
     public static final String FULL_SYNC_PERIOD = "fullSyncPeriod";
     public static final String CHANGED_SYNC_PERIOD = "changedSyncPeriod";
     public static final String LAST_SYNC = "lastSync";
-    public static final String ENABLED = "enabled";
 
     public UserStorageProviderModel() {
         setProviderType(UserStorageProvider.class.getName());
@@ -46,7 +44,6 @@ public class UserStorageProviderModel extends CacheableStorageProviderModel {
     private transient Integer changedSyncPeriod;
     private transient Integer lastSync;
     private transient Boolean importEnabled;
-    private transient Boolean enabled;
 
     public boolean isImportEnabled() {
         if (importEnabled == null) {
@@ -66,24 +63,6 @@ public class UserStorageProviderModel extends CacheableStorageProviderModel {
         getConfig().putSingle(IMPORT_ENABLED, Boolean.toString(flag));
     }
 
-    public void setEnabled(boolean flag) {
-        enabled = flag;
-        getConfig().putSingle(ENABLED, Boolean.toString(flag));
-    }
-
-
-    public boolean isEnabled() {
-        if (enabled == null) {
-            String val = getConfig().getFirst(ENABLED);
-            if (val == null) {
-                enabled = true;
-            } else {
-                enabled = Boolean.valueOf(val);
-            }
-        }
-        return enabled;
-
-    }
 
     public int getFullSyncPeriod() {
         if (fullSyncPeriod == null) {
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/federation/storage/ClientStorageTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/federation/storage/ClientStorageTest.java
index a1af7fe..c8c2f52 100644
--- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/federation/storage/ClientStorageTest.java
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/federation/storage/ClientStorageTest.java
@@ -30,17 +30,21 @@ import org.keycloak.admin.client.resource.ClientsResource;
 import org.keycloak.admin.client.resource.UserResource;
 import org.keycloak.authentication.authenticators.browser.UsernamePasswordFormFactory;
 import org.keycloak.common.util.MultivaluedHashMap;
+import org.keycloak.component.ComponentModel;
 import org.keycloak.events.Details;
 import org.keycloak.models.AuthenticationExecutionModel;
 import org.keycloak.models.AuthenticationFlowBindings;
 import org.keycloak.models.AuthenticationFlowModel;
 import org.keycloak.models.ClientModel;
 import org.keycloak.models.RealmModel;
+import org.keycloak.models.cache.infinispan.ClientAdapter;
 import org.keycloak.representations.idm.ClientRepresentation;
 import org.keycloak.representations.idm.ComponentRepresentation;
 import org.keycloak.representations.idm.RealmRepresentation;
+import org.keycloak.storage.CacheableStorageProviderModel;
 import org.keycloak.storage.UserStorageProvider;
 import org.keycloak.storage.client.ClientStorageProvider;
+import org.keycloak.storage.client.ClientStorageProviderModel;
 import org.keycloak.testsuite.AbstractTestRealmKeycloakTest;
 import org.keycloak.testsuite.AssertEvents;
 import org.keycloak.testsuite.admin.ApiUtil;
@@ -66,9 +70,18 @@ import javax.ws.rs.core.Response;
 import java.io.File;
 import java.io.IOException;
 import java.net.URISyntaxException;
+import java.util.Calendar;
 import java.util.List;
 
+import static java.util.Calendar.DAY_OF_WEEK;
+import static java.util.Calendar.HOUR_OF_DAY;
+import static java.util.Calendar.MINUTE;
 import static org.junit.Assert.assertEquals;
+import static org.keycloak.storage.CacheableStorageProviderModel.CACHE_POLICY;
+import static org.keycloak.storage.CacheableStorageProviderModel.EVICTION_DAY;
+import static org.keycloak.storage.CacheableStorageProviderModel.EVICTION_HOUR;
+import static org.keycloak.storage.CacheableStorageProviderModel.EVICTION_MINUTE;
+import static org.keycloak.storage.CacheableStorageProviderModel.MAX_LIFESPAN;
 
 /**
  * Test that clients can override auth flows
@@ -92,6 +105,8 @@ public class ClientStorageTest extends AbstractTestRealmKeycloakTest {
     public void configureTestRealm(RealmRepresentation testRealm) {
     }
 
+    protected String providerId;
+
     @Deployment
     public static WebArchive deploy() {
         return RunOnServerDeployment.create(UserResource.class)
@@ -116,7 +131,7 @@ public class ClientStorageTest extends AbstractTestRealmKeycloakTest {
         provider.getConfig().putSingle(HardcodedClientStorageProviderFactory.CLIENT_ID, "hardcoded-client");
         provider.getConfig().putSingle(HardcodedClientStorageProviderFactory.REDIRECT_URI, oauth.getRedirectUri());
 
-        String providerId = addComponent(provider);
+        providerId = addComponent(provider);
     }
 
 
@@ -212,4 +227,176 @@ public class ClientStorageTest extends AbstractTestRealmKeycloakTest {
         httpClient.close();
         events.clear();
     }
+
+    /*
+
+    @Test
+    public void testDailyEviction() {
+
+        // set eviction to 1 hour from now
+        Calendar eviction = Calendar.getInstance();
+        eviction.add(Calendar.HOUR, 1);
+        ComponentRepresentation propProviderRW = testRealmResource().components().component(propProviderRWId).toRepresentation();
+        propProviderRW.getConfig().putSingle(CACHE_POLICY, CacheableStorageProviderModel.CachePolicy.EVICT_DAILY.name());
+        propProviderRW.getConfig().putSingle(EVICTION_HOUR, Integer.toString(eviction.get(HOUR_OF_DAY)));
+        propProviderRW.getConfig().putSingle(EVICTION_MINUTE, Integer.toString(eviction.get(MINUTE)));
+        testRealmResource().components().component(propProviderRWId).update(propProviderRW);
+
+        // now
+        testingClient.server().run(session -> {
+            RealmModel realm = session.realms().getRealmByName("test");
+            UserModel user = session.users().getUserByUsername("thor", realm);
+        });
+
+        // run twice to make sure its in cache.
+        testingClient.server().run(session -> {
+            RealmModel realm = session.realms().getRealmByName("test");
+            UserModel user = session.users().getUserByUsername("thor", realm);
+            System.out.println("User class: " + user.getClass());
+            Assert.assertTrue(user instanceof CachedUserModel); // should still be cached
+        });
+
+        setTimeOffset(2 * 60 * 60); // 2 hours in future
+
+        testingClient.server().run(session -> {
+            RealmModel realm = session.realms().getRealmByName("test");
+            UserModel user = session.users().getUserByUsername("thor", realm);
+            System.out.println("User class: " + user.getClass());
+            Assert.assertFalse(user instanceof CachedUserModel); // should be evicted
+        });
+
+    }
+
+
+        */
+
+
+    @Test
+    public void testDailyEviction() {
+        testIsCached();
+
+        testingClient.server().run(session -> {
+            RealmModel realm = session.realms().getRealmByName("test");
+            ClientStorageProviderModel model = realm.getClientStorageProviders().get(0);
+            Calendar eviction = Calendar.getInstance();
+            eviction.add(Calendar.HOUR, 1);
+            model.setCachePolicy(CacheableStorageProviderModel.CachePolicy.EVICT_DAILY);
+            model.setEvictionHour(eviction.get(HOUR_OF_DAY));
+            model.setEvictionMinute(eviction.get(MINUTE));
+            realm.updateComponent(model);
+        });
+        testIsCached();
+        setTimeOffset(2 * 60 * 60); // 2 hours in future
+        testNotCached();
+        testIsCached();
+
+        setDefaultCachePolicy();
+        testIsCached();
+
+    }
+    @Test
+    public void testWeeklyEviction() {
+        testIsCached();
+
+        testingClient.server().run(session -> {
+            RealmModel realm = session.realms().getRealmByName("test");
+            ClientStorageProviderModel model = realm.getClientStorageProviders().get(0);
+            Calendar eviction = Calendar.getInstance();
+            eviction.add(Calendar.HOUR, 4 * 24);
+            model.setCachePolicy(CacheableStorageProviderModel.CachePolicy.EVICT_WEEKLY);
+            model.setEvictionDay(eviction.get(DAY_OF_WEEK));
+            model.setEvictionHour(eviction.get(HOUR_OF_DAY));
+            model.setEvictionMinute(eviction.get(MINUTE));
+            realm.updateComponent(model);
+        });
+        testIsCached();
+        setTimeOffset(2 * 24 * 60 * 60); // 2 days in future
+        testIsCached();
+        setTimeOffset(5 * 24 * 60 * 60); // 5 days in future
+        testNotCached();
+        testIsCached();
+
+        setDefaultCachePolicy();
+        testIsCached();
+
+    }
+    @Test
+    public void testMaxLifespan() {
+        testIsCached();
+
+        testingClient.server().run(session -> {
+            RealmModel realm = session.realms().getRealmByName("test");
+            ClientStorageProviderModel model = realm.getClientStorageProviders().get(0);
+            model.setCachePolicy(CacheableStorageProviderModel.CachePolicy.MAX_LIFESPAN);
+            model.setMaxLifespan(1 * 60 * 60 * 1000);
+            realm.updateComponent(model);
+        });
+        testIsCached();
+
+        setTimeOffset(1/2 * 60 * 60); // 1/2 hour in future
+
+        testIsCached();
+
+        setTimeOffset(2 * 60 * 60); // 2 hours in future
+
+        testNotCached();
+        testIsCached();
+
+        setDefaultCachePolicy();
+        testIsCached();
+
+    }
+
+    private void testNotCached() {
+        testingClient.server().run(session -> {
+            RealmModel realm = session.realms().getRealmByName("test");
+            ClientModel hardcoded = realm.getClientByClientId("hardcoded-client");
+            Assert.assertNotNull(hardcoded);
+            Assert.assertFalse(hardcoded instanceof ClientAdapter);
+        });
+    }
+
+
+    @Test
+    public void testIsCached() {
+        testingClient.server().run(session -> {
+            RealmModel realm = session.realms().getRealmByName("test");
+            ClientModel hardcoded = realm.getClientByClientId("hardcoded-client");
+            Assert.assertNotNull(hardcoded);
+            Assert.assertTrue(hardcoded instanceof org.keycloak.models.cache.infinispan.ClientAdapter);
+        });
+    }
+
+
+    @Test
+    public void testNoCache() {
+        testIsCached();
+
+        testingClient.server().run(session -> {
+            RealmModel realm = session.realms().getRealmByName("test");
+            ClientStorageProviderModel model = realm.getClientStorageProviders().get(0);
+            model.setCachePolicy(CacheableStorageProviderModel.CachePolicy.NO_CACHE);
+            realm.updateComponent(model);
+        });
+
+        testNotCached();
+
+        // test twice because updating component should evict
+        testNotCached();
+
+        // set it back
+        setDefaultCachePolicy();
+        testIsCached();
+
+
+    }
+
+    private void setDefaultCachePolicy() {
+        testingClient.server().run(session -> {
+            RealmModel realm = session.realms().getRealmByName("test");
+            ClientStorageProviderModel model = realm.getClientStorageProviders().get(0);
+            model.setCachePolicy(CacheableStorageProviderModel.CachePolicy.DEFAULT);
+            realm.updateComponent(model);
+        });
+    }
 }