keycloak-aplcache
Changes
model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/counter/RevisionedCacheRealmProvider.java 401(+0 -401)
model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/counter/RevisionedCacheRealmProviderFactory.java 161(+0 -161)
model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/counter/RevisionedConnectionProviderFactory.java 47(+0 -47)
model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/counter/RevisionedRealmCache.java 255(+0 -255)
model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/DefaultCacheRealmProvider.java 51(+38 -13)
model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/InfinispanCacheRealmProviderFactory.java 6(+3 -3)
model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/InfinispanRealmCache.java 47(+17 -30)
model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/locking/entities/RevisionedCachedClient.java 5(+2 -3)
model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/locking/entities/RevisionedCachedClientRole.java 4(+2 -2)
model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/locking/entities/RevisionedCachedClientTemplate.java 4(+2 -2)
model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/locking/entities/RevisionedCachedGroup.java 4(+2 -2)
model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/locking/entities/RevisionedCachedRealm.java 8(+2 -6)
model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/locking/entities/RevisionedCachedRealmRole.java 4(+2 -2)
model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/locking/entities/RevisionedCachedUser.java 4(+2 -2)
model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/locking/LockingCacheRealmProvider.java 102(+81 -21)
model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/locking/LockingCacheRealmProviderFactory.java 18(+8 -10)
model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/locking/LockingRealmCache.java 144(+75 -69)
model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/locking/Revisioned.java 2(+1 -1)
model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/locking/UpdateCounter.java 2(+1 -1)
model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/skewed/RepeatableReadWriteSkewCacheRealmProviderFactory.java 160(+0 -160)
model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/skewed/RepeatableReadWriteSkewConnectionProviderFactory.java 164(+0 -164)
model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/skewed/RepeatableReadWriteSkewRealmCache.java 269(+0 -269)
model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/skewed/RepeatableReadWriteSkewRealmCacheProvider.java 472(+0 -472)
model/infinispan/src/main/resources/META-INF/services/org.keycloak.connections.infinispan.InfinispanConnectionProviderFactory 1(+0 -1)
model/infinispan/src/main/resources/META-INF/services/org.keycloak.models.cache.CacheRealmProviderFactory 1(+0 -1)
model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/MongoRealmProvider.java 22(+22 -0)
testsuite/pom.xml 1(+1 -0)
testsuite/stress/pom.xml 571(+571 -0)
testsuite/stress/src/test/resources/testrealm.json 185(+185 -0)
Details
diff --git a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/DefaultCacheRealmProvider.java b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/DefaultCacheRealmProvider.java
index d80b817..b998b8c 100755
--- a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/DefaultCacheRealmProvider.java
+++ b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/DefaultCacheRealmProvider.java
@@ -120,7 +120,7 @@ public class DefaultCacheRealmProvider implements CacheRealmProvider {
protected void runInvalidations() {
for (String id : realmInvalidations) {
- cache.invalidateCachedRealmById(id);
+ cache.invalidateRealmById(id);
}
for (String id : roleInvalidations) {
cache.invalidateRoleById(id);
@@ -129,10 +129,10 @@ public class DefaultCacheRealmProvider implements CacheRealmProvider {
cache.invalidateGroupById(id);
}
for (String id : appInvalidations) {
- cache.invalidateCachedApplicationById(id);
+ cache.invalidateClientById(id);
}
for (String id : clientTemplateInvalidations) {
- cache.invalidateCachedClientTemplateById(id);
+ cache.invalidateClientTemplateById(id);
}
}
@@ -193,13 +193,13 @@ public class DefaultCacheRealmProvider implements CacheRealmProvider {
@Override
public RealmModel getRealm(String id) {
- CachedRealm cached = cache.getCachedRealm(id);
+ CachedRealm cached = cache.getRealm(id);
if (cached == null) {
RealmModel model = getDelegate().getRealm(id);
if (model == null) return null;
if (realmInvalidations.contains(id)) return model;
cached = new CachedRealm(cache, this, model);
- cache.addCachedRealm(cached);
+ cache.addRealm(cached);
} else if (realmInvalidations.contains(id)) {
return getDelegate().getRealm(id);
} else if (managedRealms.containsKey(id)) {
@@ -212,13 +212,13 @@ public class DefaultCacheRealmProvider implements CacheRealmProvider {
@Override
public RealmModel getRealmByName(String name) {
- CachedRealm cached = cache.getCachedRealmByName(name);
+ CachedRealm cached = cache.getRealmByName(name);
if (cached == null) {
RealmModel model = getDelegate().getRealmByName(name);
if (model == null) return null;
if (realmInvalidations.contains(model.getId())) return model;
cached = new CachedRealm(cache, this, model);
- cache.addCachedRealm(cached);
+ cache.addRealm(cached);
} else if (realmInvalidations.contains(cached.getId())) {
return getDelegate().getRealmByName(name);
} else if (managedRealms.containsKey(cached.getId())) {
@@ -245,7 +245,7 @@ public class DefaultCacheRealmProvider implements CacheRealmProvider {
@Override
public boolean removeRealm(String id) {
- cache.invalidateCachedRealmById(id);
+ cache.invalidateRealmById(id);
RealmModel realm = getDelegate().getRealm(id);
Set<RoleModel> realmRoles = null;
@@ -287,7 +287,7 @@ public class DefaultCacheRealmProvider implements CacheRealmProvider {
} else {
cached = new CachedRealmRole(model, realm);
}
- cache.addCachedRole(cached);
+ cache.addRole(cached);
} else if (roleInvalidations.contains(id)) {
return getDelegate().getRoleById(id, realm);
@@ -311,7 +311,7 @@ public class DefaultCacheRealmProvider implements CacheRealmProvider {
if (model == null) return null;
if (groupInvalidations.contains(id)) return model;
cached = new CachedGroup(realm, model);
- cache.addCachedGroup(cached);
+ cache.addGroup(cached);
} else if (groupInvalidations.contains(id)) {
return getDelegate().getGroupById(id, realm);
@@ -325,7 +325,7 @@ public class DefaultCacheRealmProvider implements CacheRealmProvider {
@Override
public ClientModel getClientById(String id, RealmModel realm) {
- CachedClient cached = cache.getApplication(id);
+ CachedClient cached = cache.getClient(id);
if (cached != null && !cached.getRealm().equals(realm.getId())) {
cached = null;
}
@@ -335,7 +335,7 @@ public class DefaultCacheRealmProvider implements CacheRealmProvider {
if (model == null) return null;
if (appInvalidations.contains(id)) return model;
cached = new CachedClient(cache, getDelegate(), realm, model);
- cache.addCachedClient(cached);
+ cache.addClient(cached);
} else if (appInvalidations.contains(id)) {
return getDelegate().getClientById(id, realm);
} else if (managedApplications.containsKey(id)) {
@@ -345,6 +345,31 @@ public class DefaultCacheRealmProvider implements CacheRealmProvider {
managedApplications.put(id, adapter);
return adapter;
}
+
+ @Override
+ public ClientModel getClientByClientId(String clientId, RealmModel realm) {
+ return getDelegate().getClientByClientId(clientId, realm);
+ }
+
+ @Override
+ public boolean removeClient(String id, RealmModel realm) {
+ ClientModel client = getClientById(id, realm);
+ if (client == null) return false;
+ registerApplicationInvalidation(id);
+ registerRealmInvalidation(realm.getId());
+ cache.invalidateClientById(id);
+ cache.invalidateRealmById(realm.getId());
+
+
+
+ Set<RoleModel> roles = client.getRoles();
+ for (RoleModel role : roles) {
+ registerRoleInvalidation(role.getId());
+ }
+ return getDelegate().removeClient(id, realm);
+ }
+
+
@Override
public ClientTemplateModel getClientTemplateById(String id, RealmModel realm) {
CachedClientTemplate cached = cache.getClientTemplate(id);
@@ -357,7 +382,7 @@ public class DefaultCacheRealmProvider implements CacheRealmProvider {
if (model == null) return null;
if (clientTemplateInvalidations.contains(id)) return model;
cached = new CachedClientTemplate(cache, getDelegate(), realm, model);
- cache.addCachedClientTemplate(cached);
+ cache.addClientTemplate(cached);
} else if (clientTemplateInvalidations.contains(id)) {
return getDelegate().getClientTemplateById(id, realm);
} else if (managedClientTemplates.containsKey(id)) {
diff --git a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/InfinispanCacheRealmProviderFactory.java b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/InfinispanCacheRealmProviderFactory.java
index 3c65733..ac8f373 100755
--- a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/InfinispanCacheRealmProviderFactory.java
+++ b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/InfinispanCacheRealmProviderFactory.java
@@ -138,11 +138,11 @@ public class InfinispanCacheRealmProviderFactory implements CacheRealmProviderFa
realmLookup.remove(realm.getName());
for (String r : realm.getRealmRoles().values()) {
- realmCache.evictCachedRoleById(r);
+ realmCache.evictRoleById(r);
}
for (String c : realm.getClients().values()) {
- realmCache.evictCachedApplicationById(c);
+ realmCache.evictClientById(c);
}
log.tracev("Realm removed realm={0}", realm.getName());
@@ -150,7 +150,7 @@ public class InfinispanCacheRealmProviderFactory implements CacheRealmProviderFa
CachedClient client = (CachedClient) object;
for (String r : client.getRoles().values()) {
- realmCache.evictCachedRoleById(r);
+ realmCache.evictRoleById(r);
}
log.tracev("Client removed client={0}", client.getId());
diff --git a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/InfinispanRealmCache.java b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/InfinispanRealmCache.java
index 4f89237..8dfe923 100755
--- a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/InfinispanRealmCache.java
+++ b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/InfinispanRealmCache.java
@@ -53,61 +53,61 @@ public class InfinispanRealmCache implements RealmCache {
}
@Override
- public CachedRealm getCachedRealm(String id) {
+ public CachedRealm getRealm(String id) {
return get(id, CachedRealm.class);
}
@Override
- public void invalidateCachedRealm(CachedRealm realm) {
+ public void invalidateRealm(CachedRealm realm) {
logger.tracev("Invalidating realm {0}", realm.getId());
cache.remove(realm.getId());
realmLookup.remove(realm.getName());
}
@Override
- public void invalidateCachedRealmById(String id) {
+ public void invalidateRealmById(String id) {
CachedRealm cached = (CachedRealm) cache.remove(id);
if (cached != null) realmLookup.remove(cached.getName());
}
@Override
- public void addCachedRealm(CachedRealm realm) {
+ public void addRealm(CachedRealm realm) {
logger.tracev("Adding realm {0}", realm.getId());
cache.putForExternalRead(realm.getId(), realm);
realmLookup.put(realm.getName(), realm.getId());
}
@Override
- public CachedRealm getCachedRealmByName(String name) {
+ public CachedRealm getRealmByName(String name) {
String id = realmLookup.get(name);
- return id != null ? getCachedRealm(id) : null;
+ return id != null ? getRealm(id) : null;
}
@Override
- public CachedClient getApplication(String id) {
+ public CachedClient getClient(String id) {
return get(id, CachedClient.class);
}
@Override
- public void invalidateApplication(CachedClient app) {
+ public void invalidateClient(CachedClient app) {
logger.tracev("Removing application {0}", app.getId());
cache.remove(app.getId());
}
@Override
- public void addCachedClient(CachedClient app) {
+ public void addClient(CachedClient app) {
logger.tracev("Adding application {0}", app.getId());
cache.putForExternalRead(app.getId(), app);
}
@Override
- public void invalidateCachedApplicationById(String id) {
+ public void invalidateClientById(String id) {
logger.tracev("Removing application {0}", id);
cache.remove(id);
}
@Override
- public void evictCachedApplicationById(String id) {
+ public void evictClientById(String id) {
logger.tracev("Evicting application {0}", id);
cache.evict(id);
}
@@ -124,19 +124,12 @@ public class InfinispanRealmCache implements RealmCache {
}
@Override
- public void addCachedGroup(CachedGroup role) {
+ public void addGroup(CachedGroup role) {
logger.tracev("Adding group {0}", role.getId());
cache.putForExternalRead(role.getId(), role);
}
@Override
- public void invalidateCachedGroupById(String id) {
- logger.tracev("Removing group {0}", id);
- cache.remove(id);
-
- }
-
- @Override
public void invalidateGroupById(String id) {
logger.tracev("Removing group {0}", id);
cache.remove(id);
@@ -160,23 +153,17 @@ public class InfinispanRealmCache implements RealmCache {
}
@Override
- public void evictCachedRoleById(String id) {
+ public void evictRoleById(String id) {
logger.tracev("Evicting role {0}", id);
cache.evict(id);
}
@Override
- public void addCachedRole(CachedRole role) {
+ public void addRole(CachedRole role) {
logger.tracev("Adding role {0}", role.getId());
cache.putForExternalRead(role.getId(), role);
}
- @Override
- public void invalidateCachedRoleById(String id) {
- logger.tracev("Removing role {0}", id);
- cache.remove(id);
- }
-
private <T> T get(String id, Class<T> type) {
Object o = cache.get(id);
return o != null && type.isInstance(o) ? type.cast(o) : null;
@@ -194,19 +181,19 @@ public class InfinispanRealmCache implements RealmCache {
}
@Override
- public void addCachedClientTemplate(CachedClientTemplate app) {
+ public void addClientTemplate(CachedClientTemplate app) {
logger.tracev("Adding client template {0}", app.getId());
cache.putForExternalRead(app.getId(), app);
}
@Override
- public void invalidateCachedClientTemplateById(String id) {
+ public void invalidateClientTemplateById(String id) {
logger.tracev("Removing client template {0}", id);
cache.remove(id);
}
@Override
- public void evictCachedClientTemplateById(String id) {
+ public void evictClientTemplateById(String id) {
logger.tracev("Evicting client template {0}", id);
cache.evict(id);
}
diff --git a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/locking/LockingCacheRealmProvider.java b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/locking/LockingCacheRealmProvider.java
index ea451b0..4edb046 100755
--- a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/locking/LockingCacheRealmProvider.java
+++ b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/locking/LockingCacheRealmProvider.java
@@ -38,12 +38,12 @@ import org.keycloak.models.cache.infinispan.ClientTemplateAdapter;
import org.keycloak.models.cache.infinispan.GroupAdapter;
import org.keycloak.models.cache.infinispan.RealmAdapter;
import org.keycloak.models.cache.infinispan.RoleAdapter;
-import org.keycloak.models.cache.infinispan.counter.entities.RevisionedCachedClient;
-import org.keycloak.models.cache.infinispan.counter.entities.RevisionedCachedClientRole;
-import org.keycloak.models.cache.infinispan.counter.entities.RevisionedCachedClientTemplate;
-import org.keycloak.models.cache.infinispan.counter.entities.RevisionedCachedGroup;
-import org.keycloak.models.cache.infinispan.counter.entities.RevisionedCachedRealm;
-import org.keycloak.models.cache.infinispan.counter.entities.RevisionedCachedRealmRole;
+import org.keycloak.models.cache.infinispan.locking.entities.RevisionedCachedClient;
+import org.keycloak.models.cache.infinispan.locking.entities.RevisionedCachedClientRole;
+import org.keycloak.models.cache.infinispan.locking.entities.RevisionedCachedClientTemplate;
+import org.keycloak.models.cache.infinispan.locking.entities.RevisionedCachedGroup;
+import org.keycloak.models.cache.infinispan.locking.entities.RevisionedCachedRealm;
+import org.keycloak.models.cache.infinispan.locking.entities.RevisionedCachedRealmRole;
import java.util.Collections;
import java.util.HashMap;
@@ -105,6 +105,10 @@ public class LockingCacheRealmProvider implements CacheRealmProvider {
return delegate;
}
+ public LockingRealmCache getCache() {
+ return cache;
+ }
+
@Override
public void registerRealmInvalidation(String id) {
realmInvalidations.add(id);
@@ -132,7 +136,7 @@ public class LockingCacheRealmProvider implements CacheRealmProvider {
protected void runInvalidations() {
for (String id : realmInvalidations) {
- cache.invalidateCachedRealmById(id);
+ cache.invalidateRealmById(id);
}
for (String id : roleInvalidations) {
cache.invalidateRoleById(id);
@@ -141,10 +145,10 @@ public class LockingCacheRealmProvider implements CacheRealmProvider {
cache.invalidateGroupById(id);
}
for (String id : appInvalidations) {
- cache.invalidateCachedApplicationById(id);
+ cache.invalidateClientById(id);
}
for (String id : clientTemplateInvalidations) {
- cache.invalidateCachedClientTemplateById(id);
+ cache.invalidateClientTemplateById(id);
}
}
@@ -271,17 +275,18 @@ public class LockingCacheRealmProvider implements CacheRealmProvider {
@Override
public RealmModel getRealm(String id) {
- CachedRealm cached = cache.getCachedRealm(id);
+ CachedRealm cached = cache.getRealm(id);
if (cached != null) {
logger.tracev("by id cache hit: {0}", cached.getName());
}
if (cached == null) {
Long loaded = cache.getCurrentRevision(id);
+ if (loaded == null) loaded = UpdateCounter.current();
RealmModel model = getDelegate().getRealm(id);
if (model == null) return null;
if (realmInvalidations.contains(id)) return model;
cached = new RevisionedCachedRealm(loaded, cache, this, model);
- cache.addCachedRealm(cached);
+ cache.addRealm(cached);
} else if (realmInvalidations.contains(id)) {
return getDelegate().getRealm(id);
} else if (managedRealms.containsKey(id)) {
@@ -294,16 +299,17 @@ public class LockingCacheRealmProvider implements CacheRealmProvider {
@Override
public RealmModel getRealmByName(String name) {
- CachedRealm cached = cache.getCachedRealmByName(name);
+ CachedRealm cached = cache.getRealmByName(name);
if (cached != null) {
logger.tracev("by name cache hit: {0}", cached.getName());
}
if (cached == null) {
+ Long loaded = UpdateCounter.current();
RealmModel model = getDelegate().getRealmByName(name);
if (model == null) return null;
if (realmInvalidations.contains(model.getId())) return model;
- cached = new RevisionedCachedRealm(null, cache, this, model);
- cache.addCachedRealm(cached);
+ cached = new RevisionedCachedRealm(loaded, cache, this, model);
+ cache.addRealm(cached);
} else if (realmInvalidations.contains(cached.getId())) {
return getDelegate().getRealmByName(name);
} else if (managedRealms.containsKey(cached.getId())) {
@@ -330,7 +336,7 @@ public class LockingCacheRealmProvider implements CacheRealmProvider {
@Override
public boolean removeRealm(String id) {
- cache.invalidateCachedRealmById(id);
+ cache.invalidateRealmById(id);
RealmModel realm = getDelegate().getRealm(id);
Set<RoleModel> realmRoles = null;
@@ -352,6 +358,25 @@ public class LockingCacheRealmProvider implements CacheRealmProvider {
}
@Override
+ public boolean removeClient(String id, RealmModel realm) {
+ ClientModel client = getClientById(id, realm);
+ if (client == null) return false;
+
+ registerApplicationInvalidation(id);
+ registerRealmInvalidation(realm.getId());
+ cache.invalidateClientById(id);
+ cache.invalidateRealmById(realm.getId());
+
+
+
+ Set<RoleModel> roles = client.getRoles();
+ for (RoleModel role : roles) {
+ registerRoleInvalidation(role.getId());
+ }
+ return getDelegate().removeClient(id, realm);
+ }
+
+ @Override
public void close() {
if (delegate != null) delegate.close();
}
@@ -365,6 +390,7 @@ public class LockingCacheRealmProvider implements CacheRealmProvider {
if (cached == null) {
Long loaded = cache.getCurrentRevision(id);
+ if (loaded == null) loaded = UpdateCounter.current();
RoleModel model = getDelegate().getRoleById(id, realm);
if (model == null) return null;
if (roleInvalidations.contains(id)) return model;
@@ -373,7 +399,7 @@ public class LockingCacheRealmProvider implements CacheRealmProvider {
} else {
cached = new RevisionedCachedRealmRole(loaded, model, realm);
}
- cache.addCachedRole(cached);
+ cache.addRole(cached);
} else if (roleInvalidations.contains(id)) {
return getDelegate().getRoleById(id, realm);
@@ -394,11 +420,12 @@ public class LockingCacheRealmProvider implements CacheRealmProvider {
if (cached == null) {
Long loaded = cache.getCurrentRevision(id);
+ if (loaded == null) loaded = UpdateCounter.current();
GroupModel model = getDelegate().getGroupById(id, realm);
if (model == null) return null;
if (groupInvalidations.contains(id)) return model;
cached = new RevisionedCachedGroup(loaded, realm, model);
- cache.addCachedGroup(cached);
+ cache.addGroup(cached);
} else if (groupInvalidations.contains(id)) {
return getDelegate().getGroupById(id, realm);
@@ -412,21 +439,23 @@ public class LockingCacheRealmProvider implements CacheRealmProvider {
@Override
public ClientModel getClientById(String id, RealmModel realm) {
- CachedClient cached = cache.getApplication(id);
+ CachedClient cached = cache.getClient(id);
if (cached != null && !cached.getRealm().equals(realm.getId())) {
cached = null;
}
- if (cached != null && cached.getClientId().equals("client")) {
+ if (cached != null) {
logger.tracev("client by id cache hit: {0}", cached.getClientId());
}
if (cached == null) {
Long loaded = cache.getCurrentRevision(id);
+ if (loaded == null) loaded = UpdateCounter.current();
ClientModel model = getDelegate().getClientById(id, realm);
if (model == null) return null;
if (appInvalidations.contains(id)) return model;
cached = new RevisionedCachedClient(loaded, cache, getDelegate(), realm, model);
- cache.addCachedClient(cached);
+ logger.tracev("adding client by id cache miss: {0}", cached.getClientId());
+ cache.addClient(cached);
} else if (appInvalidations.contains(id)) {
return getDelegate().getClientById(id, realm);
} else if (managedApplications.containsKey(id)) {
@@ -436,6 +465,36 @@ public class LockingCacheRealmProvider implements CacheRealmProvider {
managedApplications.put(id, adapter);
return adapter;
}
+
+ @Override
+ public ClientModel getClientByClientId(String clientId, RealmModel realm) {
+ CachedClient cached = cache.getClientByClientId(realm, clientId);
+ if (cached != null && !cached.getRealm().equals(realm.getId())) {
+ cached = null;
+ }
+ if (cached != null) {
+ logger.tracev("client by name cache hit: {0}", cached.getClientId());
+ }
+
+ if (cached == null) {
+ Long loaded = UpdateCounter.current();
+ if (loaded == null) loaded = UpdateCounter.current();
+ ClientModel model = getDelegate().getClientByClientId(clientId, realm);
+ if (model == null) return null;
+ if (appInvalidations.contains(model.getId())) return model;
+ cached = new RevisionedCachedClient(loaded, cache, getDelegate(), realm, model);
+ logger.tracev("adding client by name cache miss: {0}", cached.getClientId());
+ cache.addClient(cached);
+ } else if (appInvalidations.contains(cached.getId())) {
+ return getDelegate().getClientById(cached.getId(), realm);
+ } else if (managedApplications.containsKey(cached.getId())) {
+ return managedApplications.get(cached.getId());
+ }
+ ClientAdapter adapter = new ClientAdapter(realm, cached, this, cache);
+ managedApplications.put(cached.getId(), adapter);
+ return adapter;
+ }
+
@Override
public ClientTemplateModel getClientTemplateById(String id, RealmModel realm) {
CachedClientTemplate cached = cache.getClientTemplate(id);
@@ -445,11 +504,12 @@ public class LockingCacheRealmProvider implements CacheRealmProvider {
if (cached == null) {
Long loaded = cache.getCurrentRevision(id);
+ if (loaded == null) loaded = UpdateCounter.current();
ClientTemplateModel model = getDelegate().getClientTemplateById(id, realm);
if (model == null) return null;
if (clientTemplateInvalidations.contains(id)) return model;
cached = new RevisionedCachedClientTemplate(loaded, cache, getDelegate(), realm, model);
- cache.addCachedClientTemplate(cached);
+ cache.addClientTemplate(cached);
} else if (clientTemplateInvalidations.contains(id)) {
return getDelegate().getClientTemplateById(id, realm);
} else if (managedClientTemplates.containsKey(id)) {
diff --git a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/locking/LockingCacheRealmProviderFactory.java b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/locking/LockingCacheRealmProviderFactory.java
index f143ea3..23f8e53 100755
--- a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/locking/LockingCacheRealmProviderFactory.java
+++ b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/locking/LockingCacheRealmProviderFactory.java
@@ -37,8 +37,6 @@ import org.keycloak.models.cache.CacheRealmProviderFactory;
import org.keycloak.models.cache.entities.CachedClient;
import org.keycloak.models.cache.entities.CachedRealm;
-import java.util.concurrent.ConcurrentHashMap;
-
/**
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
@@ -49,8 +47,6 @@ public class LockingCacheRealmProviderFactory implements CacheRealmProviderFacto
protected volatile LockingRealmCache realmCache;
- protected final ConcurrentHashMap<String, String> realmLookup = new ConcurrentHashMap<>();
-
@Override
public CacheRealmProvider create(KeycloakSession session) {
lazyInit(session);
@@ -64,7 +60,7 @@ public class LockingCacheRealmProviderFactory implements CacheRealmProviderFacto
Cache<String, Object> cache = session.getProvider(InfinispanConnectionProvider.class).getCache(InfinispanConnectionProvider.REALM_CACHE_NAME);
Cache<String, Long> counterCache = session.getProvider(InfinispanConnectionProvider.class).getCache(LockingConnectionProviderFactory.VERSION_CACHE_NAME);
cache.addListener(new CacheListener());
- realmCache = new LockingRealmCache(cache, counterCache, realmLookup);
+ realmCache = new LockingRealmCache(cache, counterCache);
}
}
}
@@ -98,7 +94,7 @@ public class LockingCacheRealmProviderFactory implements CacheRealmProviderFacto
if (object != null) {
if (object instanceof CachedRealm) {
CachedRealm realm = (CachedRealm) object;
- realmLookup.put(realm.getName(), realm.getId());
+ realmCache.getRealmLookup().put(realm.getName(), realm.getId());
log.tracev("Realm added realm={0}", realm.getName());
}
}
@@ -136,22 +132,24 @@ public class LockingCacheRealmProviderFactory implements CacheRealmProviderFacto
if (object instanceof CachedRealm) {
CachedRealm realm = (CachedRealm) object;
- realmLookup.remove(realm.getName());
+ realmCache.getRealmLookup().remove(realm.getName());
for (String r : realm.getRealmRoles().values()) {
- realmCache.evictCachedRoleById(r);
+ realmCache.evictRoleById(r);
}
for (String c : realm.getClients().values()) {
- realmCache.evictCachedApplicationById(c);
+ realmCache.evictClientById(c);
}
log.tracev("Realm removed realm={0}", realm.getName());
} else if (object instanceof CachedClient) {
CachedClient client = (CachedClient) object;
+ realmCache.getClientLookup().remove(client.getRealm() + "." + client.getClientId());
+
for (String r : client.getRoles().values()) {
- realmCache.evictCachedRoleById(r);
+ realmCache.evictRoleById(r);
}
log.tracev("Client removed client={0}", client.getId());
diff --git a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/locking/LockingRealmCache.java b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/locking/LockingRealmCache.java
index 9c8e784..a77c59b 100755
--- a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/locking/LockingRealmCache.java
+++ b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/locking/LockingRealmCache.java
@@ -19,16 +19,15 @@ package org.keycloak.models.cache.infinispan.locking;
import org.infinispan.Cache;
import org.jboss.logging.Logger;
+import org.keycloak.models.RealmModel;
import org.keycloak.models.cache.RealmCache;
import org.keycloak.models.cache.entities.CachedClient;
import org.keycloak.models.cache.entities.CachedClientTemplate;
import org.keycloak.models.cache.entities.CachedGroup;
import org.keycloak.models.cache.entities.CachedRealm;
import org.keycloak.models.cache.entities.CachedRole;
-import org.keycloak.models.cache.infinispan.counter.Revisioned;
import java.util.concurrent.ConcurrentHashMap;
-import java.util.concurrent.atomic.AtomicLong;
/**
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
@@ -39,17 +38,12 @@ public class LockingRealmCache implements RealmCache {
protected final Cache<String, Long> revisions;
protected final Cache<String, Object> cache;
- final AtomicLong realmCounter = new AtomicLong();
- final AtomicLong clientCounter = new AtomicLong();
- final AtomicLong clientTemplateCounter = new AtomicLong();
- final AtomicLong roleCounter = new AtomicLong();
- final AtomicLong groupCounter = new AtomicLong();
- protected final ConcurrentHashMap<String, String> realmLookup;
+ protected final ConcurrentHashMap<String, String> realmLookup = new ConcurrentHashMap<>();
+ protected final ConcurrentHashMap<String, String> clientLookup = new ConcurrentHashMap<>();
- public LockingRealmCache(Cache<String, Object> cache, Cache<String, Long> revisions, ConcurrentHashMap<String, String> realmLookup) {
+ public LockingRealmCache(Cache<String, Object> cache, Cache<String, Long> revisions) {
this.cache = cache;
- this.realmLookup = realmLookup;
this.revisions = revisions;
}
@@ -61,8 +55,16 @@ public class LockingRealmCache implements RealmCache {
return revisions;
}
- public void startRevisionBatch() {
- revisions.startBatch();
+ public ConcurrentHashMap<String, String> getRealmLookup() {
+ return realmLookup;
+ }
+
+ public ConcurrentHashMap<String, String> getClientLookup() {
+ return clientLookup;
+ }
+
+ public Long getCurrentRevision(String id) {
+ return revisions.get(id);
}
public void endRevisionBatch() {
@@ -91,33 +93,39 @@ public class LockingRealmCache implements RealmCache {
return o != null && type.isInstance(o) ? type.cast(o) : null;
}
- protected Object invalidateObject(String id, AtomicLong counter) {
+ protected Object invalidateObject(String id) {
Object removed = cache.remove(id);
- revisions.put(id, counter.incrementAndGet());
+ revisions.put(id, UpdateCounter.next());
return removed;
}
- protected void addRevisioned(String id, Revisioned object, AtomicLong counter) {
+ protected void addRevisioned(String id, Revisioned object) {
//startRevisionBatch();
try {
//revisions.getAdvancedCache().lock(id);
Long rev = revisions.get(id);
if (rev == null) {
- rev = counter.incrementAndGet();
+ rev = UpdateCounter.current();
revisions.put(id, rev);
- return;
}
revisions.startBatch();
- revisions.getAdvancedCache().lock(id);
+ if (!revisions.getAdvancedCache().lock(id)) {
+ logger.trace("Could not obtain version lock");
+ }
rev = revisions.get(id);
if (rev == null) {
- rev = counter.incrementAndGet();
- revisions.put(id, rev);
return;
}
if (rev.equals(object.getRevision())) {
cache.putForExternalRead(id, object);
+ return;
+ }
+ if (rev > object.getRevision()) { // revision is ahead, don't cache
+ return;
}
+ // revisions cache has a lower value than the object.revision, so update revision and add it to cache
+ revisions.put(id, object.getRevision());
+ cache.putForExternalRead(id, object);
} finally {
endRevisionBatch();
}
@@ -127,71 +135,82 @@ public class LockingRealmCache implements RealmCache {
- public Long getCurrentRevision(String id) {
- return revisions.get(id);
- }
@Override
public void clear() {
cache.clear();
}
@Override
- public CachedRealm getCachedRealm(String id) {
+ public CachedRealm getRealm(String id) {
return get(id, CachedRealm.class);
}
@Override
- public void invalidateCachedRealm(CachedRealm realm) {
+ public void invalidateRealm(CachedRealm realm) {
logger.tracev("Invalidating realm {0}", realm.getId());
- invalidateObject(realm.getId(), realmCounter);
+ invalidateObject(realm.getId());
realmLookup.remove(realm.getName());
}
@Override
- public void invalidateCachedRealmById(String id) {
- CachedRealm cached = (CachedRealm) invalidateObject(id, realmCounter);
+ public void invalidateRealmById(String id) {
+ CachedRealm cached = (CachedRealm) invalidateObject(id);
if (cached != null) realmLookup.remove(cached.getName());
}
@Override
- public void addCachedRealm(CachedRealm realm) {
+ public void addRealm(CachedRealm realm) {
logger.tracev("Adding realm {0}", realm.getId());
- addRevisioned(realm.getId(), (Revisioned) realm, realmCounter);
+ addRevisioned(realm.getId(), (Revisioned) realm);
realmLookup.put(realm.getName(), realm.getId());
}
@Override
- public CachedRealm getCachedRealmByName(String name) {
+ public CachedRealm getRealmByName(String name) {
String id = realmLookup.get(name);
- return id != null ? getCachedRealm(id) : null;
+ return id != null ? getRealm(id) : null;
}
@Override
- public CachedClient getApplication(String id) {
+ public CachedClient getClient(String id) {
return get(id, CachedClient.class);
}
+ public CachedClient getClientByClientId(RealmModel realm, String clientId) {
+ String id = clientLookup.get(realm.getId() + "." + clientId);
+ return id != null ? getClient(id) : null;
+ }
+
@Override
- public void invalidateApplication(CachedClient app) {
+ public void invalidateClient(CachedClient app) {
logger.tracev("Removing application {0}", app.getId());
- invalidateObject(app.getId(), clientCounter);
+ invalidateObject(app.getId());
+ clientLookup.remove(getClientIdKey(app));
}
@Override
- public void addCachedClient(CachedClient app) {
+ public void addClient(CachedClient app) {
logger.tracev("Adding application {0}", app.getId());
- addRevisioned(app.getId(), (Revisioned) app, clientCounter);
+ addRevisioned(app.getId(), (Revisioned) app);
+ clientLookup.put(getClientIdKey(app), app.getId());
}
@Override
- public void invalidateCachedApplicationById(String id) {
- CachedClient client = (CachedClient)invalidateObject(id, clientCounter);
- if (client != null) logger.tracev("Removing application {0}", client.getClientId());
+ public void invalidateClientById(String id) {
+ CachedClient client = (CachedClient)invalidateObject(id);
+ if (client != null) {
+ logger.tracev("Removing application {0}", client.getClientId());
+ clientLookup.remove(getClientIdKey(client));
+ }
+ }
+
+ protected String getClientIdKey(CachedClient client) {
+ return client.getRealm() + "." + client.getClientId();
}
@Override
- public void evictCachedApplicationById(String id) {
+ public void evictClientById(String id) {
logger.tracev("Evicting application {0}", id);
cache.evict(id);
}
@@ -204,26 +223,19 @@ public class LockingRealmCache implements RealmCache {
@Override
public void invalidateGroup(CachedGroup role) {
logger.tracev("Removing group {0}", role.getId());
- invalidateObject(role.getId(), groupCounter);
+ invalidateObject(role.getId());
}
@Override
- public void addCachedGroup(CachedGroup role) {
+ public void addGroup(CachedGroup role) {
logger.tracev("Adding group {0}", role.getId());
- addRevisioned(role.getId(), (Revisioned) role, groupCounter);
- }
-
- @Override
- public void invalidateCachedGroupById(String id) {
- logger.tracev("Removing group {0}", id);
- invalidateObject(id, groupCounter);
-
+ addRevisioned(role.getId(), (Revisioned) role);
}
@Override
public void invalidateGroupById(String id) {
logger.tracev("Removing group {0}", id);
- invalidateObject(id, groupCounter);
+ invalidateObject(id);
}
@Override
@@ -234,31 +246,25 @@ public class LockingRealmCache implements RealmCache {
@Override
public void invalidateRole(CachedRole role) {
logger.tracev("Removing role {0}", role.getId());
- invalidateObject(role.getId(), roleCounter);
+ invalidateObject(role.getId());
}
@Override
public void invalidateRoleById(String id) {
logger.tracev("Removing role {0}", id);
- invalidateObject(id, roleCounter);
+ invalidateObject(id);
}
@Override
- public void evictCachedRoleById(String id) {
+ public void evictRoleById(String id) {
logger.tracev("Evicting role {0}", id);
cache.evict(id);
}
@Override
- public void addCachedRole(CachedRole role) {
+ public void addRole(CachedRole role) {
logger.tracev("Adding role {0}", role.getId());
- addRevisioned(role.getId(), (Revisioned) role, roleCounter);
- }
-
- @Override
- public void invalidateCachedRoleById(String id) {
- logger.tracev("Removing role {0}", id);
- invalidateObject(id, roleCounter);
+ addRevisioned(role.getId(), (Revisioned) role);
}
@Override
@@ -269,23 +275,23 @@ public class LockingRealmCache implements RealmCache {
@Override
public void invalidateClientTemplate(CachedClientTemplate app) {
logger.tracev("Removing client template {0}", app.getId());
- invalidateObject(app.getId(), clientTemplateCounter);
+ invalidateObject(app.getId());
}
@Override
- public void addCachedClientTemplate(CachedClientTemplate app) {
+ public void addClientTemplate(CachedClientTemplate app) {
logger.tracev("Adding client template {0}", app.getId());
- addRevisioned(app.getId(), (Revisioned) app, clientTemplateCounter);
+ addRevisioned(app.getId(), (Revisioned) app);
}
@Override
- public void invalidateCachedClientTemplateById(String id) {
+ public void invalidateClientTemplateById(String id) {
logger.tracev("Removing client template {0}", id);
- invalidateObject(id, clientTemplateCounter);
+ invalidateObject(id);
}
@Override
- public void evictCachedClientTemplateById(String id) {
+ public void evictClientTemplateById(String id) {
logger.tracev("Evicting client template {0}", id);
cache.evict(id);
}
diff --git a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/RealmAdapter.java b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/RealmAdapter.java
index 41dc8d8..14e2850 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
@@ -415,6 +415,9 @@ public class RealmAdapter implements RealmModel {
@Override
public PublicKey getPublicKey() {
+ if (updated != null) return updated.getPublicKey();
+ if (publicKey != null) return publicKey;
+ publicKey = cached.getPublicKey();
if (publicKey != null) return publicKey;
publicKey = KeycloakModelUtils.getPublicKey(getPublicKeyPem());
return publicKey;
@@ -429,6 +432,9 @@ public class RealmAdapter implements RealmModel {
@Override
public X509Certificate getCertificate() {
+ if (updated != null) return updated.getCertificate();
+ if (certificate != null) return certificate;
+ certificate = cached.getCertificate();
if (certificate != null) return certificate;
certificate = KeycloakModelUtils.getCertificate(getCertificatePem());
return certificate;
@@ -456,7 +462,14 @@ public class RealmAdapter implements RealmModel {
@Override
public PrivateKey getPrivateKey() {
- if (privateKey != null) return privateKey;
+ if (updated != null) return updated.getPrivateKey();
+ if (privateKey != null) {
+ return privateKey;
+ }
+ privateKey = cached.getPrivateKey();
+ if (privateKey != null) {
+ return privateKey;
+ }
privateKey = KeycloakModelUtils.getPrivateKey(getPrivateKeyPem());
return privateKey;
}
@@ -635,10 +648,7 @@ public class RealmAdapter implements RealmModel {
@Override
public ClientModel getClientByClientId(String clientId) {
- if (updated != null) return updated.getClientByClientId(clientId);
- String id = cached.getClients().get(clientId);
- if (id == null) return null;
- return getClientById(id);
+ return cacheSession.getClientByClientId(clientId, this);
}
@Override
diff --git a/model/infinispan/src/main/resources/META-INF/services/org.keycloak.connections.infinispan.InfinispanConnectionProviderFactory b/model/infinispan/src/main/resources/META-INF/services/org.keycloak.connections.infinispan.InfinispanConnectionProviderFactory
index ef880ec..299da00 100755
--- a/model/infinispan/src/main/resources/META-INF/services/org.keycloak.connections.infinispan.InfinispanConnectionProviderFactory
+++ b/model/infinispan/src/main/resources/META-INF/services/org.keycloak.connections.infinispan.InfinispanConnectionProviderFactory
@@ -16,5 +16,4 @@
#
org.keycloak.connections.infinispan.DefaultInfinispanConnectionProviderFactory
-org.keycloak.models.cache.infinispan.counter.RevisionedConnectionProviderFactory
org.keycloak.models.cache.infinispan.locking.LockingConnectionProviderFactory
\ No newline at end of file
diff --git a/model/infinispan/src/main/resources/META-INF/services/org.keycloak.models.cache.CacheRealmProviderFactory b/model/infinispan/src/main/resources/META-INF/services/org.keycloak.models.cache.CacheRealmProviderFactory
index 6554765..40ced18 100755
--- a/model/infinispan/src/main/resources/META-INF/services/org.keycloak.models.cache.CacheRealmProviderFactory
+++ b/model/infinispan/src/main/resources/META-INF/services/org.keycloak.models.cache.CacheRealmProviderFactory
@@ -16,5 +16,4 @@
#
org.keycloak.models.cache.infinispan.InfinispanCacheRealmProviderFactory
-org.keycloak.models.cache.infinispan.counter.RevisionedCacheRealmProviderFactory
org.keycloak.models.cache.infinispan.locking.LockingCacheRealmProviderFactory
\ No newline at end of file
diff --git a/model/jpa/src/main/java/org/keycloak/models/jpa/entities/ClientEntity.java b/model/jpa/src/main/java/org/keycloak/models/jpa/entities/ClientEntity.java
index 15544c6..5466a77 100755
--- a/model/jpa/src/main/java/org/keycloak/models/jpa/entities/ClientEntity.java
+++ b/model/jpa/src/main/java/org/keycloak/models/jpa/entities/ClientEntity.java
@@ -50,6 +50,8 @@ import java.util.Set;
@Table(name="CLIENT", uniqueConstraints = {@UniqueConstraint(columnNames = {"REALM_ID", "CLIENT_ID"})})
@NamedQueries({
@NamedQuery(name="getClientsByRealm", query="select client from ClientEntity client where client.realm = :realm"),
+ @NamedQuery(name="findClientIdByClientId", query="select client.id from ClientEntity client where client.clientId = :clientId and client.realm.id = :realm"),
+ @NamedQuery(name="findClientByClientId", query="select client from ClientEntity client where client.clientId = :clientId and client.realm.id = :realm"),
})
public class ClientEntity {
diff --git a/model/jpa/src/main/java/org/keycloak/models/jpa/entities/RealmEntity.java b/model/jpa/src/main/java/org/keycloak/models/jpa/entities/RealmEntity.java
index 76aa238..684d102 100755
--- a/model/jpa/src/main/java/org/keycloak/models/jpa/entities/RealmEntity.java
+++ b/model/jpa/src/main/java/org/keycloak/models/jpa/entities/RealmEntity.java
@@ -191,7 +191,7 @@ public class RealmEntity {
@Column(name="ADMIN_EVENTS_DETAILS_ENABLED")
protected boolean adminEventsDetailsEnabled;
- @OneToOne
+ @OneToOne(fetch = FetchType.LAZY)
@JoinColumn(name="MASTER_ADMIN_CLIENT")
protected ClientEntity masterAdminClient;
diff --git a/model/jpa/src/main/java/org/keycloak/models/jpa/JpaRealmProvider.java b/model/jpa/src/main/java/org/keycloak/models/jpa/JpaRealmProvider.java
index 86bc5a2..ce245de 100755
--- a/model/jpa/src/main/java/org/keycloak/models/jpa/JpaRealmProvider.java
+++ b/model/jpa/src/main/java/org/keycloak/models/jpa/JpaRealmProvider.java
@@ -17,6 +17,7 @@
package org.keycloak.models.jpa;
+import org.jboss.logging.Logger;
import org.keycloak.migration.MigrationModel;
import org.keycloak.models.ClientModel;
import org.keycloak.models.ClientTemplateModel;
@@ -43,6 +44,7 @@ import java.util.List;
* @version $Revision: 1 $
*/
public class JpaRealmProvider implements RealmProvider {
+ protected static final Logger logger = Logger.getLogger(JpaRealmProvider.class);
private final KeycloakSession session;
protected EntityManager em;
@@ -115,7 +117,6 @@ public class JpaRealmProvider implements RealmProvider {
if (realm == null) {
return false;
}
-
RealmAdapter adapter = new RealmAdapter(session, em, realm);
session.users().preRemove(adapter);
int num = em.createNamedQuery("deleteGroupRoleMappingsByRealm")
@@ -144,6 +145,11 @@ public class JpaRealmProvider implements RealmProvider {
em.flush();
em.clear();
+ realm = em.find(RealmEntity.class, id);
+ if (realm != null) {
+ logger.error("WTF is the realm still there after a removal????????");
+ }
+
return true;
}
@@ -177,6 +183,42 @@ public class JpaRealmProvider implements RealmProvider {
}
@Override
+ public ClientModel getClientByClientId(String clientId, RealmModel realm) {
+ TypedQuery<ClientEntity> query = em.createNamedQuery("findClientByClientId", ClientEntity.class);
+ query.setParameter("clientId", clientId);
+ query.setParameter("realm", realm.getId());
+ List<ClientEntity> results = query.getResultList();
+ if (results.isEmpty()) return null;
+ ClientEntity entity = results.get(0);
+ return new ClientAdapter(realm, em, session, entity);
+ }
+
+ @Override
+ public boolean removeClient(String id, RealmModel realm) {
+ ClientModel client = getClientById(id, realm);
+ if (client == null) return false;
+
+ session.users().preRemove(realm, client);
+
+ for (RoleModel role : client.getRoles()) {
+ client.removeRole(role);
+ }
+
+
+ ClientEntity clientEntity = ((ClientAdapter)client).getEntity();
+ em.createNamedQuery("deleteScopeMappingByClient").setParameter("client", clientEntity).executeUpdate();
+ em.flush();
+ em.remove(clientEntity); // i have no idea why, but this needs to come before deleteScopeMapping
+ try {
+ em.flush();
+ } catch (RuntimeException e) {
+ logger.errorv("Unable to delete client entity: {0} from realm {1}", client.getClientId(), realm.getName());
+ throw e;
+ }
+ return true;
+ }
+
+ @Override
public ClientTemplateModel getClientTemplateById(String id, RealmModel realm) {
ClientTemplateEntity app = em.find(ClientTemplateEntity.class, id);
diff --git a/model/jpa/src/main/java/org/keycloak/models/jpa/RealmAdapter.java b/model/jpa/src/main/java/org/keycloak/models/jpa/RealmAdapter.java
index 4d632ec..da66a3b 100755
--- a/model/jpa/src/main/java/org/keycloak/models/jpa/RealmAdapter.java
+++ b/model/jpa/src/main/java/org/keycloak/models/jpa/RealmAdapter.java
@@ -17,6 +17,7 @@
package org.keycloak.models.jpa;
+import org.jboss.logging.Logger;
import org.keycloak.connections.jpa.util.JpaUtils;
import org.keycloak.common.enums.SslRequired;
import org.keycloak.models.AuthenticationExecutionModel;
@@ -65,6 +66,7 @@ import java.util.Set;
* @version $Revision: 1 $
*/
public class RealmAdapter implements RealmModel {
+ protected static final Logger logger = Logger.getLogger(RealmAdapter.class);
protected RealmEntity realm;
protected EntityManager em;
protected volatile transient PublicKey publicKey;
@@ -774,34 +776,7 @@ public class RealmAdapter implements RealmModel {
if (id == null) return false;
ClientModel client = getClientById(id);
if (client == null) return false;
-
- session.users().preRemove(this, client);
-
- for (RoleModel role : client.getRoles()) {
- client.removeRole(role);
- }
-
- ClientEntity clientEntity = null;
- Iterator<ClientEntity> it = realm.getClients().iterator();
- while (it.hasNext()) {
- ClientEntity ae = it.next();
- if (ae.getId().equals(id)) {
- clientEntity = ae;
- it.remove();
- break;
- }
- }
- for (ClientEntity a : realm.getClients()) {
- if (a.getId().equals(id)) {
- clientEntity = a;
- }
- }
- if (clientEntity == null) return false;
- em.createNamedQuery("deleteScopeMappingByClient").setParameter("client", clientEntity).executeUpdate();
- em.remove(clientEntity);
- em.flush();
-
- return true;
+ return session.realms().removeClient(id, this);
}
@Override
@@ -811,7 +786,7 @@ public class RealmAdapter implements RealmModel {
@Override
public ClientModel getClientByClientId(String clientId) {
- return getClientNameMap().get(clientId);
+ return session.realms().getClientByClientId(clientId, this);
}
private static final String BROWSER_HEADER_PREFIX = "_browser_header.";
diff --git a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/MongoRealmProvider.java b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/MongoRealmProvider.java
index 323bbbe..c5d70fb 100755
--- a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/MongoRealmProvider.java
+++ b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/MongoRealmProvider.java
@@ -163,6 +163,28 @@ public class MongoRealmProvider implements RealmProvider {
}
@Override
+ public boolean removeClient(String id, RealmModel realm) {
+ if (id == null) return false;
+ ClientModel client = getClientById(id, realm);
+ if (client == null) return false;
+
+ session.users().preRemove(realm, client);
+
+ return getMongoStore().removeEntity(MongoClientEntity.class, id, invocationContext);
+ }
+
+ @Override
+ public ClientModel getClientByClientId(String clientId, RealmModel realm) {
+ DBObject query = new QueryBuilder()
+ .and("realmId").is(realm.getId())
+ .and("clientId").is(clientId)
+ .get();
+ MongoClientEntity appEntity = getMongoStore().loadSingleEntity(MongoClientEntity.class, query, invocationContext);
+ return appEntity == null ? null : new ClientAdapter(session, realm, appEntity, invocationContext);
+
+ }
+
+ @Override
public ClientTemplateModel getClientTemplateById(String id, RealmModel realm) {
MongoClientTemplateEntity appData = getMongoStore().loadEntity(MongoClientTemplateEntity.class, id, invocationContext);
diff --git a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/RealmAdapter.java b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/RealmAdapter.java
index bae4589..701cced 100755
--- a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/RealmAdapter.java
+++ b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/RealmAdapter.java
@@ -807,12 +807,7 @@ public class RealmAdapter extends AbstractMongoAdapter<MongoRealmEntity> impleme
@Override
public ClientModel getClientByClientId(String clientId) {
- DBObject query = new QueryBuilder()
- .and("realmId").is(getId())
- .and("clientId").is(clientId)
- .get();
- MongoClientEntity appEntity = getMongoStore().loadSingleEntity(MongoClientEntity.class, query, invocationContext);
- return appEntity == null ? null : new ClientAdapter(session, this, appEntity, invocationContext);
+ return session.realms().getClientByClientId(clientId, this);
}
@Override
@@ -873,10 +868,7 @@ public class RealmAdapter extends AbstractMongoAdapter<MongoRealmEntity> impleme
if (id == null) return false;
ClientModel client = getClientById(id);
if (client == null) return false;
-
- session.users().preRemove(this, client);
-
- return getMongoStore().removeEntity(MongoClientEntity.class, id, invocationContext);
+ return session.realms().removeClient(id, this);
}
@Override
diff --git a/server-spi/src/main/java/org/keycloak/models/cache/entities/CachedClient.java b/server-spi/src/main/java/org/keycloak/models/cache/entities/CachedClient.java
index ca3e35b..727cabc 100755
--- a/server-spi/src/main/java/org/keycloak/models/cache/entities/CachedClient.java
+++ b/server-spi/src/main/java/org/keycloak/models/cache/entities/CachedClient.java
@@ -127,7 +127,7 @@ public class CachedClient implements Serializable {
protected void cacheRoles(RealmCache cache, RealmModel realm, ClientModel model) {
for (RoleModel role : model.getRoles()) {
roles.put(role.getName(), role.getId());
- cache.addCachedRole(new CachedClientRole(id, role, realm));
+ cache.addRole(new CachedClientRole(id, role, realm));
}
}
diff --git a/server-spi/src/main/java/org/keycloak/models/cache/entities/CachedRealm.java b/server-spi/src/main/java/org/keycloak/models/cache/entities/CachedRealm.java
index 42ed3bc..29b9331 100755
--- a/server-spi/src/main/java/org/keycloak/models/cache/entities/CachedRealm.java
+++ b/server-spi/src/main/java/org/keycloak/models/cache/entities/CachedRealm.java
@@ -39,6 +39,10 @@ import org.keycloak.models.cache.RealmCache;
import org.keycloak.common.util.MultivaluedHashMap;
import java.io.Serializable;
+import java.security.PrivateKey;
+import java.security.PublicKey;
+import java.security.cert.Certificate;
+import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
@@ -89,8 +93,11 @@ public class CachedRealm implements Serializable {
protected PasswordPolicy passwordPolicy;
protected OTPPolicy otpPolicy;
+ protected transient PublicKey publicKey;
protected String publicKeyPem;
+ protected transient PrivateKey privateKey;
protected String privateKeyPem;
+ protected transient X509Certificate certificate;
protected String certificatePem;
protected String codeSecret;
@@ -179,8 +186,11 @@ public class CachedRealm implements Serializable {
otpPolicy = model.getOTPPolicy();
publicKeyPem = model.getPublicKeyPem();
+ publicKey = model.getPublicKey();
privateKeyPem = model.getPrivateKeyPem();
+ privateKey = model.getPrivateKey();
certificatePem = model.getCertificatePem();
+ certificate = model.getCertificate();
codeSecret = model.getCodeSecret();
loginTheme = model.getLoginTheme();
@@ -265,7 +275,7 @@ public class CachedRealm implements Serializable {
for (ClientTemplateModel template : model.getClientTemplates()) {
clientTemplates.add(template.getId());
CachedClientTemplate cachedClient = new CachedClientTemplate(cache, delegate, model, template);
- cache.addCachedClientTemplate(cachedClient);
+ cache.addClientTemplate(cachedClient);
}
}
@@ -273,7 +283,7 @@ public class CachedRealm implements Serializable {
for (ClientModel client : model.getClients()) {
clients.put(client.getClientId(), client.getId());
CachedClient cachedClient = new CachedClient(cache, delegate, model, client);
- cache.addCachedClient(cachedClient);
+ cache.addClient(cachedClient);
}
}
@@ -281,7 +291,7 @@ public class CachedRealm implements Serializable {
for (RoleModel role : model.getRoles()) {
realmRoles.put(role.getName(), role.getId());
CachedRole cachedRole = new CachedRealmRole(role, model);
- cache.addCachedRole(cachedRole);
+ cache.addRole(cachedRole);
}
}
@@ -584,4 +594,16 @@ public class CachedRealm implements Serializable {
public List<String> getClientTemplates() {
return clientTemplates;
}
+
+ public PublicKey getPublicKey() {
+ return publicKey;
+ }
+
+ public PrivateKey getPrivateKey() {
+ return privateKey;
+ }
+
+ public X509Certificate getCertificate() {
+ return certificate;
+ }
}
diff --git a/server-spi/src/main/java/org/keycloak/models/cache/RealmCache.java b/server-spi/src/main/java/org/keycloak/models/cache/RealmCache.java
index 909223b..2b7b339 100755
--- a/server-spi/src/main/java/org/keycloak/models/cache/RealmCache.java
+++ b/server-spi/src/main/java/org/keycloak/models/cache/RealmCache.java
@@ -30,35 +30,33 @@ import org.keycloak.models.cache.entities.CachedRole;
public interface RealmCache {
void clear();
- CachedRealm getCachedRealm(String id);
+ CachedRealm getRealm(String id);
- void invalidateCachedRealm(CachedRealm realm);
+ void invalidateRealm(CachedRealm realm);
- void addCachedRealm(CachedRealm realm);
+ void addRealm(CachedRealm realm);
- CachedRealm getCachedRealmByName(String name);
+ CachedRealm getRealmByName(String name);
- void invalidateCachedRealmById(String id);
+ void invalidateRealmById(String id);
- CachedClient getApplication(String id);
+ CachedClient getClient(String id);
- void invalidateApplication(CachedClient app);
+ void invalidateClient(CachedClient app);
- void evictCachedApplicationById(String id);
+ void evictClientById(String id);
- void addCachedClient(CachedClient app);
+ void addClient(CachedClient app);
- void invalidateCachedApplicationById(String id);
+ void invalidateClientById(String id);
CachedRole getRole(String id);
void invalidateRole(CachedRole role);
- void evictCachedRoleById(String id);
+ void evictRoleById(String id);
- void addCachedRole(CachedRole role);
-
- void invalidateCachedRoleById(String id);
+ void addRole(CachedRole role);
void invalidateRoleById(String id);
@@ -66,9 +64,7 @@ public interface RealmCache {
void invalidateGroup(CachedGroup role);
- void addCachedGroup(CachedGroup role);
-
- void invalidateCachedGroupById(String id);
+ void addGroup(CachedGroup role);
void invalidateGroupById(String id);
@@ -76,10 +72,10 @@ public interface RealmCache {
void invalidateClientTemplate(CachedClientTemplate app);
- void evictCachedClientTemplateById(String id);
+ void evictClientTemplateById(String id);
- void addCachedClientTemplate(CachedClientTemplate app);
+ void addClientTemplate(CachedClientTemplate app);
- void invalidateCachedClientTemplateById(String id);
+ void invalidateClientTemplateById(String id);
}
diff --git a/server-spi/src/main/java/org/keycloak/models/RealmProvider.java b/server-spi/src/main/java/org/keycloak/models/RealmProvider.java
index ad9e2d1..2b3f5fa 100755
--- a/server-spi/src/main/java/org/keycloak/models/RealmProvider.java
+++ b/server-spi/src/main/java/org/keycloak/models/RealmProvider.java
@@ -35,8 +35,14 @@ public interface RealmProvider extends Provider {
RealmModel getRealm(String id);
RealmModel getRealmByName(String name);
- RoleModel getRoleById(String id, RealmModel realm);
ClientModel getClientById(String id, RealmModel realm);
+ ClientModel getClientByClientId(String clientId, RealmModel realm);
+
+
+ RoleModel getRoleById(String id, RealmModel realm);
+
+ boolean removeClient(String id, RealmModel realm);
+
ClientTemplateModel getClientTemplateById(String id, RealmModel realm);
GroupModel getGroupById(String id, RealmModel realm);
diff --git a/services/src/main/java/org/keycloak/services/managers/RealmManager.java b/services/src/main/java/org/keycloak/services/managers/RealmManager.java
index dbaaad2..55695a0 100755
--- a/services/src/main/java/org/keycloak/services/managers/RealmManager.java
+++ b/services/src/main/java/org/keycloak/services/managers/RealmManager.java
@@ -211,10 +211,11 @@ public class RealmManager implements RealmImporter {
public boolean removeRealm(RealmModel realm) {
List<UserFederationProviderModel> federationProviders = realm.getUserFederationProviders();
+ ClientModel masterAdminClient = realm.getMasterAdminClient();
boolean removed = model.removeRealm(realm.getId());
if (removed) {
- if (realm.getMasterAdminClient() != null) {
- new ClientManager(this).removeClient(getKeycloakAdminstrationRealm(), realm.getMasterAdminClient());
+ if (masterAdminClient != null) {
+ new ClientManager(this).removeClient(getKeycloakAdminstrationRealm(), masterAdminClient);
}
UserSessionProvider sessions = session.sessions();
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/admin/ConcurrencyTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/admin/ConcurrencyTest.java
index f510f04..7984fae 100755
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/admin/ConcurrencyTest.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/admin/ConcurrencyTest.java
@@ -46,8 +46,8 @@ public class ConcurrencyTest extends AbstractClientTest {
private static final Logger log = Logger.getLogger(ConcurrencyTest.class);
- private static final int DEFAULT_THREADS = 1;
- private static final int DEFAULT_ITERATIONS = 5;
+ private static final int DEFAULT_THREADS = 5;
+ private static final int DEFAULT_ITERATIONS = 20;
// If enabled only one request is allowed at the time. Useful for checking that test is working.
private static final boolean SYNCHRONIZED = false;
testsuite/pom.xml 1(+1 -0)
diff --git a/testsuite/pom.xml b/testsuite/pom.xml
index 6f8079e..bc054ce 100755
--- a/testsuite/pom.xml
+++ b/testsuite/pom.xml
@@ -57,6 +57,7 @@
<module>tomcat8</module>
<module>jetty</module>
<module>performance</module>
+ <module>stress</module>
<module>integration-arquillian</module>
</modules>
testsuite/stress/pom.xml 571(+571 -0)
diff --git a/testsuite/stress/pom.xml b/testsuite/stress/pom.xml
new file mode 100755
index 0000000..f7ea824
--- /dev/null
+++ b/testsuite/stress/pom.xml
@@ -0,0 +1,571 @@
+<?xml version="1.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.
+ -->
+
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+ <parent>
+ <artifactId>keycloak-testsuite-pom</artifactId>
+ <groupId>org.keycloak</groupId>
+ <version>1.9.0.Final-SNAPSHOT</version>
+ <relativePath>../pom.xml</relativePath>
+ </parent>
+ <modelVersion>4.0.0</modelVersion>
+
+ <artifactId>keycloak-stress-tester</artifactId>
+ <name>Keycloak Stress TestSuite</name>
+ <description />
+
+ <dependencies>
+ <dependency>
+ <groupId>org.bouncycastle</groupId>
+ <artifactId>bcprov-jdk15on</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.bouncycastle</groupId>
+ <artifactId>bcpkix-jdk15on</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.keycloak</groupId>
+ <artifactId>keycloak-dependencies-server-all</artifactId>
+ <type>pom</type>
+ </dependency>
+ <dependency>
+ <groupId>org.keycloak</groupId>
+ <artifactId>keycloak-admin-client</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.keycloak</groupId>
+ <artifactId>keycloak-wildfly-adduser</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>log4j</groupId>
+ <artifactId>log4j</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.jboss.spec.javax.servlet</groupId>
+ <artifactId>jboss-servlet-api_3.0_spec</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.jboss.spec.javax.ws.rs</groupId>
+ <artifactId>jboss-jaxrs-api_2.0_spec</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.jboss.resteasy</groupId>
+ <artifactId>async-http-servlet-3.0</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.jboss.resteasy</groupId>
+ <artifactId>resteasy-jaxrs</artifactId>
+ <exclusions>
+ <exclusion>
+ <groupId>log4j</groupId>
+ <artifactId>log4j</artifactId>
+ </exclusion>
+ <exclusion>
+ <groupId>org.slf4j</groupId>
+ <artifactId>slf4j-api</artifactId>
+ </exclusion>
+ <exclusion>
+ <groupId>org.slf4j</groupId>
+ <artifactId>slf4j-simple</artifactId>
+ </exclusion>
+ </exclusions>
+ </dependency>
+ <dependency>
+ <groupId>org.jboss.resteasy</groupId>
+ <artifactId>resteasy-client</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.jboss.resteasy</groupId>
+ <artifactId>resteasy-undertow</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.jboss.resteasy</groupId>
+ <artifactId>resteasy-multipart-provider</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.jboss.resteasy</groupId>
+ <artifactId>resteasy-jackson2-provider</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>com.google.zxing</groupId>
+ <artifactId>javase</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.httpcomponents</groupId>
+ <artifactId>httpclient</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.keycloak</groupId>
+ <artifactId>keycloak-server-spi</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.keycloak</groupId>
+ <artifactId>keycloak-ldap-federation</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.keycloak</groupId>
+ <artifactId>keycloak-kerberos-federation</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.keycloak</groupId>
+ <artifactId>keycloak-undertow-adapter</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.keycloak</groupId>
+ <artifactId>keycloak-saml-adapter-core</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.keycloak</groupId>
+ <artifactId>keycloak-saml-servlet-filter-adapter</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.keycloak</groupId>
+ <artifactId>keycloak-servlet-filter-adapter</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.keycloak</groupId>
+ <artifactId>keycloak-saml-undertow-adapter</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.keycloak</groupId>
+ <artifactId>keycloak-jaxrs-oauth-client</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.keycloak</groupId>
+ <artifactId>federation-properties-example</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.jboss.logging</groupId>
+ <artifactId>jboss-logging</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>io.undertow</groupId>
+ <artifactId>undertow-servlet</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>io.undertow</groupId>
+ <artifactId>undertow-core</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>com.fasterxml.jackson.core</groupId>
+ <artifactId>jackson-core</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>com.fasterxml.jackson.core</groupId>
+ <artifactId>jackson-databind</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>com.fasterxml.jackson.core</groupId>
+ <artifactId>jackson-annotations</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>junit</groupId>
+ <artifactId>junit</artifactId>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.hamcrest</groupId>
+ <artifactId>hamcrest-all</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.hibernate.javax.persistence</groupId>
+ <artifactId>hibernate-jpa-2.1-api</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>com.h2database</groupId>
+ <artifactId>h2</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.hibernate</groupId>
+ <artifactId>hibernate-entitymanager</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>com.icegreen</groupId>
+ <artifactId>greenmail</artifactId>
+ <exclusions>
+ <exclusion>
+ <groupId>org.slf4j</groupId>
+ <artifactId>slf4j-api</artifactId>
+ </exclusion>
+ </exclusions>
+ </dependency>
+ <dependency>
+ <groupId>org.infinispan</groupId>
+ <artifactId>infinispan-core</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.seleniumhq.selenium</groupId>
+ <artifactId>selenium-java</artifactId>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>xml-apis</groupId>
+ <artifactId>xml-apis</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.seleniumhq.selenium</groupId>
+ <artifactId>selenium-chrome-driver</artifactId>
+ <scope>provided</scope>
+ </dependency>
+
+ <!-- Apache DS -->
+ <dependency>
+ <groupId>org.keycloak</groupId>
+ <artifactId>keycloak-util-embedded-ldap</artifactId>
+ </dependency>
+
+ <dependency>
+ <groupId>org.wildfly</groupId>
+ <artifactId>wildfly-undertow</artifactId>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.keycloak</groupId>
+ <artifactId>keycloak-testsuite-integration</artifactId>
+ <type>test-jar</type>
+ </dependency>
+
+
+ </dependencies>
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-jar-plugin</artifactId>
+ <version>2.2</version>
+ <executions>
+ <execution>
+ <goals>
+ <goal>test-jar</goal>
+ </goals>
+ </execution>
+ </executions>
+ </plugin>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-deploy-plugin</artifactId>
+ <configuration>
+ <skip>true</skip>
+ </configuration>
+ </plugin>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-compiler-plugin</artifactId>
+ <configuration>
+ <source>${maven.compiler.source}</source>
+ <target>${maven.compiler.target}</target>
+ </configuration>
+ </plugin>
+ <plugin>
+ <groupId>org.codehaus.mojo</groupId>
+ <artifactId>exec-maven-plugin</artifactId>
+ <configuration>
+ <workingDirectory>${project.basedir}</workingDirectory>
+ </configuration>
+ </plugin>
+ <plugin>
+ <groupId>org.apache.felix</groupId>
+ <artifactId>maven-bundle-plugin</artifactId>
+ <inherited>true</inherited>
+ <extensions>true</extensions>
+ </plugin>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-javadoc-plugin</artifactId>
+ <configuration>
+ <skip>true</skip>
+ </configuration>
+ </plugin>
+ </plugins>
+ </build>
+
+ <profiles>
+ <profile>
+ <id>keycloak-server</id>
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.codehaus.mojo</groupId>
+ <artifactId>exec-maven-plugin</artifactId>
+ <configuration>
+ <mainClass>org.keycloak.testsuite.KeycloakServer</mainClass>
+ <classpathScope>test</classpathScope>
+ </configuration>
+ </plugin>
+ </plugins>
+ </build>
+ </profile>
+ <profile>
+ <id>mail-server</id>
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.codehaus.mojo</groupId>
+ <artifactId>exec-maven-plugin</artifactId>
+ <configuration>
+ <mainClass>org.keycloak.testsuite.MailServer</mainClass>
+ <classpathScope>test</classpathScope>
+ </configuration>
+ </plugin>
+ </plugins>
+ </build>
+ </profile>
+ <profile>
+ <id>totp</id>
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.codehaus.mojo</groupId>
+ <artifactId>exec-maven-plugin</artifactId>
+ <configuration>
+ <mainClass>org.keycloak.testsuite.TotpGenerator</mainClass>
+ <classpathScope>test</classpathScope>
+ </configuration>
+ </plugin>
+ </plugins>
+ </build>
+ </profile>
+ <profile>
+ <id>ldap</id>
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.codehaus.mojo</groupId>
+ <artifactId>exec-maven-plugin</artifactId>
+ <configuration>
+ <mainClass>org.keycloak.util.ldap.LDAPEmbeddedServer</mainClass>
+ <classpathScope>test</classpathScope>
+ </configuration>
+ </plugin>
+ </plugins>
+ </build>
+ </profile>
+ <profile>
+ <id>kerberos</id>
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.codehaus.mojo</groupId>
+ <artifactId>exec-maven-plugin</artifactId>
+ <configuration>
+ <mainClass>org.keycloak.util.ldap.KerberosEmbeddedServer</mainClass>
+ <classpathScope>test</classpathScope>
+ </configuration>
+ </plugin>
+ </plugins>
+ </build>
+ </profile>
+
+ <profile>
+ <id>jpa</id>
+
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-surefire-plugin</artifactId>
+ <configuration>
+ <systemPropertyVariables>
+ <keycloak.realm.provider>jpa</keycloak.realm.provider>
+ <keycloak.user.provider>jpa</keycloak.user.provider>
+ <keycloak.userSessionPersister.provider>jpa</keycloak.userSessionPersister.provider>
+ <keycloak.eventsStore.provider>jpa</keycloak.eventsStore.provider>
+
+ <keycloak.liquibase.logging.level>debug</keycloak.liquibase.logging.level>
+ </systemPropertyVariables>
+ </configuration>
+ </plugin>
+ </plugins>
+ </build>
+ </profile>
+
+ <profile>
+ <id>mongo</id>
+
+ <properties>
+ <keycloak.connectionsMongo.host>localhost</keycloak.connectionsMongo.host>
+ <keycloak.connectionsMongo.port>27018</keycloak.connectionsMongo.port>
+ <keycloak.connectionsMongo.db>keycloak</keycloak.connectionsMongo.db>
+ <keycloak.connectionsMongo.bindIp>127.0.0.1</keycloak.connectionsMongo.bindIp>
+ </properties>
+
+ <build>
+ <plugins>
+
+ <!-- Postpone tests to "integration-test" phase, so that we can bootstrap embedded mongo on 27018 before running tests -->
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-surefire-plugin</artifactId>
+ <executions>
+ <execution>
+ <id>test</id>
+ <phase>integration-test</phase>
+ <goals>
+ <goal>test</goal>
+ </goals>
+ <configuration>
+ <systemPropertyVariables>
+ <keycloak.realm.provider>mongo</keycloak.realm.provider>
+ <keycloak.user.provider>mongo</keycloak.user.provider>
+ <keycloak.userSessionPersister.provider>mongo</keycloak.userSessionPersister.provider>
+ <keycloak.eventsStore.provider>mongo</keycloak.eventsStore.provider>
+ <keycloak.connectionsMongo.host>${keycloak.connectionsMongo.host}</keycloak.connectionsMongo.host>
+ <keycloak.connectionsMongo.port>${keycloak.connectionsMongo.port}</keycloak.connectionsMongo.port>
+ <keycloak.connectionsMongo.db>${keycloak.connectionsMongo.db}</keycloak.connectionsMongo.db>
+ <keycloak.connectionsMongo.bindIp>${keycloak.connectionsMongo.bindIp}</keycloak.connectionsMongo.bindIp>
+ </systemPropertyVariables>
+ </configuration>
+ </execution>
+ <execution>
+ <id>default-test</id>
+ <configuration>
+ <skip>true</skip>
+ </configuration>
+ </execution>
+ </executions>
+ </plugin>
+
+ <!-- Embedded mongo -->
+ <plugin>
+ <groupId>com.github.joelittlejohn.embedmongo</groupId>
+ <artifactId>embedmongo-maven-plugin</artifactId>
+ <executions>
+ <execution>
+ <id>start-mongodb</id>
+ <phase>pre-integration-test</phase>
+ <goals>
+ <goal>start</goal>
+ </goals>
+ <configuration>
+ <port>${keycloak.connectionsMongo.port}</port>
+ <logging>file</logging>
+ <logFile>${project.build.directory}/mongodb.log</logFile>
+ <bindIp>${keycloak.connectionsMongo.bindIp}</bindIp>
+ </configuration>
+ </execution>
+ <execution>
+ <id>stop-mongodb</id>
+ <phase>post-integration-test</phase>
+ <goals>
+ <goal>stop</goal>
+ </goals>
+ </execution>
+ </executions>
+ </plugin>
+ </plugins>
+ </build>
+
+ </profile>
+
+ <!-- MySQL -->
+ <profile>
+ <activation>
+ <property>
+ <name>keycloak.connectionsJpa.driver</name>
+ <value>com.mysql.jdbc.Driver</value>
+ </property>
+ </activation>
+ <id>mysql</id>
+ <dependencies>
+ <dependency>
+ <groupId>mysql</groupId>
+ <artifactId>mysql-connector-java</artifactId>
+ </dependency>
+ </dependencies>
+ </profile>
+
+ <!-- PostgreSQL -->
+ <profile>
+ <activation>
+ <property>
+ <name>keycloak.connectionsJpa.driver</name>
+ <value>org.postgresql.Driver</value>
+ </property>
+ </activation>
+ <id>postgresql</id>
+ <dependencies>
+ <dependency>
+ <groupId>org.postgresql</groupId>
+ <artifactId>postgresql</artifactId>
+ <version>${postgresql.version}</version>
+ </dependency>
+ </dependencies>
+ </profile>
+
+ <profile>
+ <id>clean-jpa</id>
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.liquibase</groupId>
+ <artifactId>liquibase-maven-plugin</artifactId>
+ <configuration>
+ <changeLogFile>META-INF/jpa-changelog-master.xml</changeLogFile>
+
+ <url>${keycloak.connectionsJpa.url}</url>
+ <driver>${keycloak.connectionsJpa.driver}</driver>
+ <username>${keycloak.connectionsJpa.user}</username>
+ <password>${keycloak.connectionsJpa.password}</password>
+
+ <promptOnNonLocalDatabase>false</promptOnNonLocalDatabase>
+ <databaseClass>${keycloak.connectionsJpa.liquibaseDatabaseClass}</databaseClass>
+ </configuration>
+ <executions>
+ <execution>
+ <id>clean-jpa</id>
+ <phase>clean</phase>
+ <goals>
+ <goal>dropAll</goal>
+ </goals>
+ </execution>
+ </executions>
+ </plugin>
+ </plugins>
+ </build>
+ </profile>
+
+ <!-- Ldap profiles -->
+ <profile>
+ <activation>
+ <property>
+ <name>ldap.vendor</name>
+ <value>msad</value>
+ </property>
+ </activation>
+ <id>msad</id>
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-surefire-plugin</artifactId>
+ <configuration>
+ <includes>
+ <include>org/keycloak/testsuite/federation/ldap/base/**</include>
+ </includes>
+ <excludes>
+ <exclude>**/LDAPMultipleAttributesTest.java</exclude>
+ </excludes>
+ </configuration>
+ </plugin>
+ </plugins>
+ </build>
+ </profile>
+
+ </profiles>
+</project>
diff --git a/testsuite/stress/src/main/java/org/keycloak/test/stress/MaxRateExecutor.java b/testsuite/stress/src/main/java/org/keycloak/test/stress/MaxRateExecutor.java
new file mode 100755
index 0000000..e56aa83
--- /dev/null
+++ b/testsuite/stress/src/main/java/org/keycloak/test/stress/MaxRateExecutor.java
@@ -0,0 +1,138 @@
+package org.keycloak.test.stress;
+
+import java.util.Collections;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.ExecutorCompletionService;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.Future;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Executes a test N number of times. This is done multiple times over an ever expanding amount of threads to determine
+ * when the computer is saturated and you can't eek out any more concurrent requests.
+ *
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public class MaxRateExecutor {
+
+ public static class RateResult {
+ StressResult result;
+ int threads;
+ long time;
+
+ public RateResult(StressResult result, int threads, long time) {
+ this.result = result;
+ this.threads = threads;
+ this.time = time;
+ }
+
+ public StressResult getResult() {
+ return result;
+ }
+
+ public int getThreads() {
+ return threads;
+ }
+
+ public long getTime() {
+ return time;
+ }
+ }
+
+ List<RateResult> allResults = new LinkedList<>();
+ RateResult fastest = null;
+ RateResult last = null;
+
+
+
+ public void best(TestFactory factory, int jobs) {
+ fastest = last = null;
+ int threads = 2;
+ do {
+ fastest = last;
+ try {
+ last = execute(factory, threads, jobs);
+ allResults.add(last);
+ } catch (InterruptedException e) {
+ throw new RuntimeException(e);
+ } catch (ExecutionException e) {
+ throw new RuntimeException(e);
+ }
+ threads++;
+ } while (fastest == null || fastest.time > last.time);
+ }
+
+ public RateResult getFastest() {
+ return fastest;
+ }
+
+ public RateResult getLast() {
+ return last;
+ }
+
+ public RateResult execute(TestFactory factory, int threads, int jobs) throws InterruptedException, ExecutionException {
+ List<StressTest> tests = new LinkedList<>();
+ ExecutorService executor = Executors.newFixedThreadPool(threads);
+ ExecutorCompletionService<StressTest> completionService = new ExecutorCompletionService<>(executor);
+ StressResult result = new StressResult("num threads:" + threads);
+ addTest(factory, result, tests, threads + 5);
+ long start = System.currentTimeMillis();
+ for (StressTest stressTest : tests) {
+ completionService.submit(stressTest);
+ }
+ for (int i = 0; i < jobs; i++) {
+ Future<StressTest> future = completionService.take();
+ StressTest stressTest = future.get();
+ if (i < jobs - threads - 5) completionService.submit(stressTest);
+ }
+ long end = System.currentTimeMillis() - start;
+ executor.shutdown();
+ executor.awaitTermination(10, TimeUnit.SECONDS);
+ RateResult rate = new RateResult(result, threads, end);
+ return rate;
+ }
+
+ private void addTest(TestFactory factory, StressResult result, List<StressTest> tests, int num) {
+ int add = num - tests.size();
+ for (int i = 0; i < add; i++) {
+ Test test = factory.create();
+ test.init();
+ StressTest stress = new StressTest(result, test, 1);
+ tests.add(stress);
+ }
+ }
+
+ public void printResults() {
+ System.out.println("*******************");
+ System.out.println("* Best Result *");
+ System.out.println("*******************");
+ printResult(fastest);
+ }
+
+
+ public void printResult(RateResult result) {
+ System.out.println("Threads: " + result.getThreads());
+ System.out.println("Total Time: " + result.getTime());
+ System.out.println("Rate: " + ((double)result.getResult().getIterations()) / ((double)result.getTime()));
+ System.out.println("Successes: " + result.getResult().getSuccess());
+ System.out.println("Iterations: " + result.getResult().getIterations());
+ System.out.println("Average time per iteration: " + result.getResult().getAverageTime());
+
+ }
+
+ public void printSummary() {
+
+ for (RateResult result : allResults) {
+ System.out.println("*******************");
+ printSummary(result);
+ }
+ }
+ public void printSummary(RateResult result) {
+ System.out.println("Threads: " + result.getThreads());
+ System.out.println("Total Time: " + result.getTime());
+ }
+}
diff --git a/testsuite/stress/src/main/java/org/keycloak/test/stress/StressExecutor.java b/testsuite/stress/src/main/java/org/keycloak/test/stress/StressExecutor.java
new file mode 100755
index 0000000..ebf6ef1
--- /dev/null
+++ b/testsuite/stress/src/main/java/org/keycloak/test/stress/StressExecutor.java
@@ -0,0 +1,65 @@
+package org.keycloak.test.stress;
+
+import java.util.Collections;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Random;
+import java.util.concurrent.Callable;
+import java.util.concurrent.CompletionService;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.ExecutorCompletionService;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.Future;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Executes all test threads until completion.
+ *
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public class StressExecutor {
+ protected List<StressTest> tests = new LinkedList<>();
+ protected List<StressResult> results = new LinkedList<>();
+
+ public void addTest(Class<? extends Test> test, int threads, int iterations) {
+ StressResult result = new StressResult(test.getName());
+ results.add(result);
+ for (int i = 0; i < threads; i++) {
+ try {
+ Test t = test.newInstance();
+ t.init();
+ StressTest stress = new StressTest(result, t, iterations);
+ tests.add(stress);
+ } catch (InstantiationException e) {
+ throw new RuntimeException(e);
+ } catch (IllegalAccessException e) {
+ throw new RuntimeException(e);
+ }
+ }
+ }
+
+ public void addTest(Test test, StressResult result, int iterations) {
+ tests.add(new StressTest(result, test, iterations));
+ }
+
+ public void addTest(Test test, int iterations) {
+ StressResult result = new StressResult(test.getClass().getName());
+ tests.add(new StressTest(result, test, iterations));
+ }
+
+ public long execute() throws InterruptedException, ExecutionException {
+ ExecutorService executor = Executors.newFixedThreadPool(tests.size());
+ Collections.shuffle(tests);
+ long start = System.currentTimeMillis();
+ for (StressTest test : tests) {
+ executor.submit(test);
+ }
+ executor.shutdown();
+ boolean done = executor.awaitTermination(100, TimeUnit.HOURS);
+ long end = System.currentTimeMillis() - start;
+ return end;
+
+ }
+}
diff --git a/testsuite/stress/src/main/java/org/keycloak/test/stress/StressResult.java b/testsuite/stress/src/main/java/org/keycloak/test/stress/StressResult.java
new file mode 100755
index 0000000..080c553
--- /dev/null
+++ b/testsuite/stress/src/main/java/org/keycloak/test/stress/StressResult.java
@@ -0,0 +1,62 @@
+package org.keycloak.test.stress;
+
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.concurrent.atomic.AtomicLong;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public class StressResult {
+ ThreadLocal<Long> start = new ThreadLocal<>();
+ AtomicLong iterations = new AtomicLong();
+ AtomicLong totalTime = new AtomicLong();
+ String name;
+ AtomicInteger success = new AtomicInteger();
+
+ public StressResult(String name) {
+ this.name = name;
+ }
+
+ public void start() {
+ start.set(System.currentTimeMillis());
+ }
+
+ public void success() {
+ success.incrementAndGet();
+ }
+
+ public void end() {
+ long end = System.currentTimeMillis() - start.get();
+ totalTime.addAndGet(end);
+ iterations.incrementAndGet();
+ }
+
+ public int getSuccess() {
+ return success.get();
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public long getTotalTime() {
+ return totalTime.longValue();
+ }
+ public long getIterations() {
+ return iterations.get();
+ }
+
+ public double getAverageTime() {
+ return (double)(double)totalTime.get() / (double)iterations.get();
+ }
+ public double getRate() {
+ return (double)(double)iterations.get() / (double)totalTime.get();
+ }
+
+ public void clear() {
+ iterations.set(0);
+ totalTime.set(0);
+ success.set(0);
+ }
+}
diff --git a/testsuite/stress/src/main/java/org/keycloak/test/stress/StressTest.java b/testsuite/stress/src/main/java/org/keycloak/test/stress/StressTest.java
new file mode 100755
index 0000000..363c5e2
--- /dev/null
+++ b/testsuite/stress/src/main/java/org/keycloak/test/stress/StressTest.java
@@ -0,0 +1,37 @@
+package org.keycloak.test.stress;
+
+import jdk.nashorn.internal.codegen.CompilerConstants;
+
+import java.util.concurrent.Callable;
+import java.util.concurrent.CountDownLatch;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public class StressTest implements Callable<StressTest> {
+ protected StressResult result;
+ protected Callable<Boolean> test;
+ protected int iterations;
+
+ public StressTest(StressResult result, Callable<Boolean> test, int iterations) {
+ this.result = result;
+ this.test = test;
+ this.iterations = iterations;
+ }
+
+ @Override
+ public StressTest call() throws Exception {
+ for (int i = 0; i < iterations; i++) {
+ result.start();
+ try {
+ if (test.call()) {
+ result.success();
+ }
+ } catch (Throwable throwable) {
+ }
+ result.end();
+ }
+ return this;
+ }
+}
diff --git a/testsuite/stress/src/main/java/org/keycloak/test/stress/Test.java b/testsuite/stress/src/main/java/org/keycloak/test/stress/Test.java
new file mode 100755
index 0000000..04b7233
--- /dev/null
+++ b/testsuite/stress/src/main/java/org/keycloak/test/stress/Test.java
@@ -0,0 +1,11 @@
+package org.keycloak.test.stress;
+
+import java.util.concurrent.Callable;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public interface Test extends Callable<Boolean> {
+ void init();
+}
diff --git a/testsuite/stress/src/main/java/org/keycloak/test/stress/TestFactory.java b/testsuite/stress/src/main/java/org/keycloak/test/stress/TestFactory.java
new file mode 100755
index 0000000..aee1f7b
--- /dev/null
+++ b/testsuite/stress/src/main/java/org/keycloak/test/stress/TestFactory.java
@@ -0,0 +1,9 @@
+package org.keycloak.test.stress;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public interface TestFactory {
+ Test create();
+}
diff --git a/testsuite/stress/src/main/java/org/keycloak/test/stress/tests/LoginLogout.java b/testsuite/stress/src/main/java/org/keycloak/test/stress/tests/LoginLogout.java
new file mode 100755
index 0000000..859bc45
--- /dev/null
+++ b/testsuite/stress/src/main/java/org/keycloak/test/stress/tests/LoginLogout.java
@@ -0,0 +1,104 @@
+package org.keycloak.test.stress.tests;
+
+import org.junit.Assert;
+import org.keycloak.OAuth2Constants;
+import org.keycloak.admin.client.Keycloak;
+import org.keycloak.models.Constants;
+import org.keycloak.protocol.oidc.OIDCLoginProtocolService;
+import org.keycloak.test.stress.Test;
+import org.keycloak.testsuite.OAuthClient;
+import org.keycloak.testsuite.pages.LoginPage;
+import org.keycloak.testsuite.rule.WebResource;
+import org.keycloak.testsuite.rule.WebRule;
+import org.openqa.selenium.WebDriver;
+
+import javax.ws.rs.core.UriBuilder;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public class LoginLogout implements Test {
+ public WebRule webRule = new WebRule(this);
+
+ protected String securedResourceUrl;
+ protected List<String> containsInPage = new LinkedList<>();
+ protected String realm;
+ protected String authServerUrl;
+ protected String username;
+ protected String password;
+
+ protected String loginUrl;
+ protected String logoutUrl;
+
+ @WebResource
+ protected WebDriver driver;
+
+ @WebResource
+ protected LoginPage loginPage;
+
+ public LoginLogout securedResourceUrl(String securedResourceUrl) {
+ this.securedResourceUrl = securedResourceUrl;
+ return this;
+ }
+
+ public LoginLogout addPageContains(String contains) {
+ containsInPage.add(contains);
+ return this;
+ }
+
+ public LoginLogout realm(String realm) {
+ this.realm = realm;
+ return this;
+ }
+
+ public LoginLogout authServerUrl(String authServerUrl) {
+ this.authServerUrl = authServerUrl;
+ return this;
+ }
+
+ public LoginLogout username(String username) {
+ this.username = username;
+ return this;
+ }
+
+ public LoginLogout password(String password) {
+ this.password = password;
+ return this;
+ }
+
+ @Override
+ public void init() {
+
+ loginUrl = OIDCLoginProtocolService.authUrl(UriBuilder.fromUri(authServerUrl)).build(realm).toString();
+ logoutUrl = OIDCLoginProtocolService.logoutUrl(UriBuilder.fromUri(authServerUrl))
+ .queryParam(OAuth2Constants.REDIRECT_URI, securedResourceUrl).build(realm).toString();
+ try {
+ webRule.before();
+ } catch (Throwable e) {
+ throw new RuntimeException(e);
+ }
+
+ }
+
+ @Override
+ public Boolean call() throws Exception {
+ driver.navigate().to(securedResourceUrl);
+ Assert.assertTrue(driver.getCurrentUrl().startsWith(loginUrl));
+ loginPage.login(username, password);
+ Assert.assertTrue(driver.getCurrentUrl().startsWith(securedResourceUrl));
+ String pageSource = driver.getPageSource();
+ for (String contains : containsInPage) {
+ Assert.assertTrue(pageSource.contains(contains));
+
+ }
+
+ // test logout
+ driver.navigate().to(logoutUrl);
+ Assert.assertTrue(driver.getCurrentUrl().startsWith(loginUrl));
+ return true;
+ }
+}
diff --git a/testsuite/stress/src/test/java/org/keycloak/test/CustomerDatabaseServlet.java b/testsuite/stress/src/test/java/org/keycloak/test/CustomerDatabaseServlet.java
new file mode 100755
index 0000000..0115ef1
--- /dev/null
+++ b/testsuite/stress/src/test/java/org/keycloak/test/CustomerDatabaseServlet.java
@@ -0,0 +1,51 @@
+/*
+ * 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.test;
+
+import org.junit.Assert;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.security.Principal;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public class CustomerDatabaseServlet extends HttpServlet {
+ private static final String LINK = "<a href=\"%s\" id=\"%s\">%s</a>";
+
+ @Override
+ protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
+ resp.setContentType("text/html");
+ PrintWriter pw = resp.getWriter();
+ Principal principal = req.getUserPrincipal();
+ Assert.assertNotNull(principal);
+ pw.printf("<html><head><title>%s</title></head><body>", "Customer Portal");
+ pw.println("Stian Thorgersen");
+ pw.println("Bill Burke");
+ pw.print("</body></html>");
+ pw.flush();
+
+
+ }
+}
diff --git a/testsuite/stress/src/test/java/org/keycloak/test/LoginLogoutTest.java b/testsuite/stress/src/test/java/org/keycloak/test/LoginLogoutTest.java
new file mode 100755
index 0000000..351f8f4
--- /dev/null
+++ b/testsuite/stress/src/test/java/org/keycloak/test/LoginLogoutTest.java
@@ -0,0 +1,96 @@
+package org.keycloak.test;
+
+import org.junit.ClassRule;
+import org.junit.Test;
+import org.keycloak.models.KeycloakSession;
+import org.keycloak.models.RealmModel;
+import org.keycloak.services.managers.RealmManager;
+import org.keycloak.test.stress.MaxRateExecutor;
+import org.keycloak.test.stress.StressExecutor;
+import org.keycloak.test.stress.TestFactory;
+import org.keycloak.test.stress.tests.LoginLogout;
+import org.keycloak.testsuite.adapter.AdapterTestStrategy;
+import org.keycloak.testsuite.adapter.CallAuthenticatedServlet;
+import org.keycloak.testsuite.adapter.CustomerDatabaseServlet;
+import org.keycloak.testsuite.adapter.CustomerServlet;
+import org.keycloak.testsuite.adapter.InputServlet;
+import org.keycloak.testsuite.adapter.ProductServlet;
+import org.keycloak.testsuite.adapter.SessionServlet;
+import org.keycloak.testsuite.rule.AbstractKeycloakRule;
+
+import java.net.URL;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public class LoginLogoutTest {
+ @ClassRule
+ public static AbstractKeycloakRule keycloakRule = new AbstractKeycloakRule() {
+ @Override
+ protected void configure(KeycloakSession session, RealmManager manager, RealmModel adminRealm) {
+ RealmModel realm = AdapterTestStrategy.baseAdapterTestInitialization(session, manager, adminRealm, getClass());
+
+ URL url = getClass().getResource("/adapter-test/cust-app-keycloak.json");
+ createApplicationDeployment()
+ .name("customer-portal").contextPath("/customer-portal")
+ .servletClass(org.keycloak.test.CustomerDatabaseServlet.class).adapterConfigPath(url.getPath())
+ .role("user").deployApplication();
+
+ }
+ };
+
+ @Test
+ public void testStressExecutor() throws Exception {
+ System.out.println("*************************");
+ System.out.println();
+ System.out.println();
+ StressExecutor executor = new StressExecutor();
+ LoginLogout test = new LoginLogout();
+ test.authServerUrl("http://localhost:8081/auth")
+ .realm("demo")
+ .username("bburke@redhat.com")
+ .password("password")
+ .securedResourceUrl("http://localhost:8081/customer-portal");
+ test.init();
+ executor.addTest(test, 5);
+ long time = executor.execute();
+ System.out.println("Took: " + time );
+ }
+
+ /*
+**************************
+* Bill's Best Result *
+**************************
+Threads: 13
+Total Time: 1018
+Successes: 400
+Iterations: 400
+Average time: 32.8075
+Rate: 0.030480835174883793
+ */
+
+ @Test
+ public void testRate() throws Exception {
+ System.out.println("*************************");
+ System.out.println();
+ System.out.println();
+ TestFactory factory = new TestFactory() {
+ @Override
+ public org.keycloak.test.stress.Test create() {
+ LoginLogout test = new LoginLogout();
+ test.authServerUrl("http://localhost:8081/auth")
+ .realm("demo")
+ .username("bburke@redhat.com")
+ .password("password")
+ .securedResourceUrl("http://localhost:8081/customer-portal");
+ return test;
+ }
+ };
+ MaxRateExecutor executor = new MaxRateExecutor();
+ executor.best(factory, 10);
+ executor.printResults();
+ executor.printSummary();
+ }
+
+}
diff --git a/testsuite/stress/src/test/resources/adapter-test/cust-app-keycloak.json b/testsuite/stress/src/test/resources/adapter-test/cust-app-keycloak.json
new file mode 100755
index 0000000..e9ad987
--- /dev/null
+++ b/testsuite/stress/src/test/resources/adapter-test/cust-app-keycloak.json
@@ -0,0 +1,11 @@
+{
+ "realm": "demo",
+ "resource": "customer-portal",
+ "realm-public-key": "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCrVrCuTtArbgaZzL1hvh0xtL5mc7o0NqPVnYXkLvgcwiC3BjLGw1tGEGoJaXDuSaRllobm53JBhjx33UNv+5z/UMG4kytBWxheNVKnL6GgqlNabMaFfPLPCF8kAgKnsi79NMo+n6KnSY8YeUmec/p2vjO2NjsSAVcWEQMVhJ31LwIDAQAB",
+ "auth-server-url": "http://localhost:8081/auth",
+ "ssl-required" : "external",
+ "expose-token": true,
+ "credentials": {
+ "secret": "password"
+ }
+}
testsuite/stress/src/test/resources/testrealm.json 185(+185 -0)
diff --git a/testsuite/stress/src/test/resources/testrealm.json b/testsuite/stress/src/test/resources/testrealm.json
new file mode 100755
index 0000000..b4718dd
--- /dev/null
+++ b/testsuite/stress/src/test/resources/testrealm.json
@@ -0,0 +1,185 @@
+{
+ "id": "test",
+ "realm": "test",
+ "enabled": true,
+ "sslRequired": "external",
+ "registrationAllowed": true,
+ "resetPasswordAllowed": true,
+ "editUsernameAllowed" : true,
+ "privateKey": "MIICXAIBAAKBgQCrVrCuTtArbgaZzL1hvh0xtL5mc7o0NqPVnYXkLvgcwiC3BjLGw1tGEGoJaXDuSaRllobm53JBhjx33UNv+5z/UMG4kytBWxheNVKnL6GgqlNabMaFfPLPCF8kAgKnsi79NMo+n6KnSY8YeUmec/p2vjO2NjsSAVcWEQMVhJ31LwIDAQABAoGAfmO8gVhyBxdqlxmIuglbz8bcjQbhXJLR2EoS8ngTXmN1bo2L90M0mUKSdc7qF10LgETBzqL8jYlQIbt+e6TH8fcEpKCjUlyq0Mf/vVbfZSNaVycY13nTzo27iPyWQHK5NLuJzn1xvxxrUeXI6A2WFpGEBLbHjwpx5WQG9A+2scECQQDvdn9NE75HPTVPxBqsEd2z10TKkl9CZxu10Qby3iQQmWLEJ9LNmy3acvKrE3gMiYNWb6xHPKiIqOR1as7L24aTAkEAtyvQOlCvr5kAjVqrEKXalj0Tzewjweuxc0pskvArTI2Oo070h65GpoIKLc9jf+UA69cRtquwP93aZKtW06U8dQJAF2Y44ks/mK5+eyDqik3koCI08qaC8HYq2wVl7G2QkJ6sbAaILtcvD92ToOvyGyeE0flvmDZxMYlvaZnaQ0lcSQJBAKZU6umJi3/xeEbkJqMfeLclD27XGEFoPeNrmdx0q10Azp4NfJAY+Z8KRyQCR2BEG+oNitBOZ+YXF9KCpH3cdmECQHEigJhYg+ykOvr1aiZUMFT72HU0jnmQe2FVekuG+LJUt2Tm7GtMjTFoGpf0JwrVuZN39fOYAlo+nTixgeW7X8Y=",
+ "publicKey": "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCrVrCuTtArbgaZzL1hvh0xtL5mc7o0NqPVnYXkLvgcwiC3BjLGw1tGEGoJaXDuSaRllobm53JBhjx33UNv+5z/UMG4kytBWxheNVKnL6GgqlNabMaFfPLPCF8kAgKnsi79NMo+n6KnSY8YeUmec/p2vjO2NjsSAVcWEQMVhJ31LwIDAQAB",
+ "requiredCredentials": [ "password" ],
+ "defaultRoles": [ "user" ],
+ "smtpServer": {
+ "from": "auto@keycloak.org",
+ "host": "localhost",
+ "port":"3025"
+ },
+ "users" : [
+ {
+ "username" : "test-user@localhost",
+ "enabled": true,
+ "email" : "test-user@localhost",
+ "firstName": "Tom",
+ "lastName": "Brady",
+ "credentials" : [
+ { "type" : "password",
+ "value" : "password" }
+ ],
+ "realmRoles": ["user", "offline_access"],
+ "clientRoles": {
+ "test-app": [ "customer-user" ],
+ "account": [ "view-profile", "manage-account" ]
+ }
+ },
+ {
+ "username" : "john-doh@localhost",
+ "enabled": true,
+ "email" : "john-doh@localhost",
+ "firstName": "John",
+ "lastName": "Doh",
+ "credentials" : [
+ { "type" : "password",
+ "value" : "password" }
+ ],
+ "realmRoles": ["user"],
+ "clientRoles": {
+ "test-app": [ "customer-user" ],
+ "account": [ "view-profile", "manage-account" ]
+ }
+ },
+ {
+ "username" : "keycloak-user@localhost",
+ "enabled": true,
+ "email" : "keycloak-user@localhost",
+ "credentials" : [
+ { "type" : "password",
+ "value" : "password" }
+ ],
+ "realmRoles": ["user"],
+ "clientRoles": {
+ "test-app": [ "customer-user" ],
+ "account": [ "view-profile", "manage-account" ]
+ }
+ },
+ {
+ "username" : "topGroupUser",
+ "enabled": true,
+ "email" : "top@redhat.com",
+ "credentials" : [
+ { "type" : "password",
+ "value" : "password" }
+ ],
+ "groups": [
+ "/topGroup"
+ ]
+ },
+ {
+ "username" : "level2GroupUser",
+ "enabled": true,
+ "email" : "level2@redhat.com",
+ "credentials" : [
+ { "type" : "password",
+ "value" : "password" }
+ ],
+ "groups": [
+ "/topGroup/level2group"
+ ]
+ }
+ ],
+ "scopeMappings": [
+ {
+ "client": "third-party",
+ "roles": ["user"]
+ },
+ {
+ "client": "test-app",
+ "roles": ["user"]
+ }
+ ],
+ "clients": [
+ {
+ "clientId": "test-app",
+ "enabled": true,
+ "baseUrl": "http://localhost:8081/app",
+ "redirectUris": [
+ "http://localhost:8081/app/*"
+ ],
+ "adminUrl": "http://localhost:8081/app/logout",
+ "secret": "password"
+ },
+ {
+ "clientId" : "third-party",
+ "enabled": true,
+ "consentRequired": true,
+
+ "redirectUris": [
+ "http://localhost:8081/app/*"
+ ],
+ "secret": "password"
+ }
+ ],
+ "roles" : {
+ "realm" : [
+ {
+ "name": "user",
+ "description": "Have User privileges"
+ },
+ {
+ "name": "admin",
+ "description": "Have Administrator privileges"
+ }
+ ],
+ "client" : {
+ "test-app" : [
+ {
+ "name": "customer-user",
+ "description": "Have Customer User privileges"
+ },
+ {
+ "name": "customer-admin",
+ "description": "Have Customer Admin privileges"
+ }
+ ]
+ }
+
+ },
+ "groups" : [
+ {
+ "name": "topGroup",
+ "attributes": {
+ "topAttribute": ["true"]
+
+ },
+ "realmRoles": ["user"],
+
+ "subGroups": [
+ {
+ "name": "level2group",
+ "realmRoles": ["admin"],
+ "clientRoles": {
+ "test-app": ["customer-user"]
+ },
+ "attributes": {
+ "level2Attribute": ["true"]
+
+ }
+ }
+ ]
+ }
+ ],
+
+
+ "clientScopeMappings": {
+ "test-app": [
+ {
+ "client": "third-party",
+ "roles": ["customer-user"]
+ }
+ ]
+ },
+
+ "internationalizationEnabled": true,
+ "supportedLocales": ["en", "de"],
+ "defaultLocale": "en"
+}