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 01cfd8c015..3176506743 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/RealmAdapter.java b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/RealmAdapter.java
index b80bbe385c..17cacc8b4a 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/RealmCache.java b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/RealmCache.java
deleted file mode 100755
index d683bb015b..0000000000
--- a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/RealmCache.java
+++ /dev/null
@@ -1,81 +0,0 @@
-/*
- * Copyright 2016 Red Hat, Inc. and/or its affiliates
- * and other contributors as indicated by the @author tags.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.keycloak.models.cache.infinispan;
-
-import org.keycloak.models.cache.infinispan.entities.CachedClient;
-import org.keycloak.models.cache.infinispan.entities.CachedClientTemplate;
-import org.keycloak.models.cache.infinispan.entities.CachedGroup;
-import org.keycloak.models.cache.infinispan.entities.CachedRealm;
-import org.keycloak.models.cache.infinispan.entities.CachedRole;
-
-/**
- * @author Bill Burke
- * @version $Revision: 1 $
- */
-public interface RealmCache {
- void clear();
-
- CachedRealm getRealm(String id);
-
- void invalidateRealm(CachedRealm realm);
-
- void addRealm(CachedRealm realm);
-
- CachedRealm getRealmByName(String name);
-
- void invalidateRealmById(String id);
-
- CachedClient getClient(String id);
-
- void invalidateClient(CachedClient app);
-
- void evictClientById(String id);
-
- void addClient(CachedClient app);
-
- void invalidateClientById(String id);
-
- CachedRole getRole(String id);
-
- void invalidateRole(CachedRole role);
-
- void evictRoleById(String id);
-
- void addRole(CachedRole role);
-
- void invalidateRoleById(String id);
-
- CachedGroup getGroup(String id);
-
- void invalidateGroup(CachedGroup role);
-
- void addGroup(CachedGroup role);
-
- void invalidateGroupById(String id);
-
- CachedClientTemplate getClientTemplate(String id);
-
- void invalidateClientTemplate(CachedClientTemplate app);
-
- void evictClientTemplateById(String id);
-
- void addClientTemplate(CachedClientTemplate app);
-
- void invalidateClientTemplateById(String id);
-
-}
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 289e3a14e1..018213f6a8 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 managedRealms = new HashMap<>();
- protected Map managedApplications = new HashMap<>();
+ protected Map managedApplications = new HashMap<>();
protected Map managedClientTemplates = new HashMap<>();
protected Map managedRoles = new HashMap<>();
protected Map 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 4b5309a613..e2fcc83f24 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/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 22fef962c4..8c88df774f 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 Bill Burke
* @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/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 32210a0f02..d26f2b99fe 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/models/cache/CachedObject.java b/server-spi/src/main/java/org/keycloak/models/cache/CachedObject.java
new file mode 100644
index 0000000000..f252db55fa
--- /dev/null
+++ b/server-spi/src/main/java/org/keycloak/models/cache/CachedObject.java
@@ -0,0 +1,25 @@
+/*
+ * 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;
+
+/**
+ * @author Bill Burke
+ * @version $Revision: 1 $
+ */
+public interface CachedObject {
+ long getCacheTimestamp();
+}
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 8263bfe153..dd740f234c 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 Bill Burke
@@ -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 0b32b94c25..e145c40225 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 a1af7fe449..c8c2f529f1 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);
+ });
+ }
}