concurrency 2
This commit is contained in:
parent
1c97b9c41d
commit
45e5cfac99
22 changed files with 1283 additions and 107 deletions
|
@ -546,7 +546,9 @@ public class ClientAdapter implements ClientModel {
|
||||||
public RoleModel getRole(String name) {
|
public RoleModel getRole(String name) {
|
||||||
if (updated != null) return updated.getRole(name);
|
if (updated != null) return updated.getRole(name);
|
||||||
String id = cached.getRoles().get(name);
|
String id = cached.getRoles().get(name);
|
||||||
if (id == null) return null;
|
if (id == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
return cacheSession.getRoleById(id, cachedRealm);
|
return cacheSession.getRoleById(id, cachedRealm);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -351,8 +351,8 @@ public class RevisionedCacheRealmProvider implements CacheRealmProvider {
|
||||||
if (cached != null && !cached.getRealm().equals(realm.getId())) {
|
if (cached != null && !cached.getRealm().equals(realm.getId())) {
|
||||||
cached = null;
|
cached = null;
|
||||||
}
|
}
|
||||||
if (cached != null && cached.getClientId().equals("client")) {
|
if (cached != null) {
|
||||||
logger.infov("client by id cache hit: {0}", cached.getClientId());
|
logger.tracev("client by id cache hit: {0}", cached.getClientId());
|
||||||
}
|
}
|
||||||
|
|
||||||
if (cached == null) {
|
if (cached == null) {
|
||||||
|
|
|
@ -38,11 +38,6 @@ public class RevisionedRealmCache implements RealmCache {
|
||||||
|
|
||||||
protected final Cache<String, Long> revisions;
|
protected final Cache<String, Long> revisions;
|
||||||
protected final Cache<String, Object> cache;
|
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;
|
||||||
|
|
||||||
|
@ -57,33 +52,56 @@ public class RevisionedRealmCache implements RealmCache {
|
||||||
}
|
}
|
||||||
|
|
||||||
public Long getCurrentRevision(String id) {
|
public Long getCurrentRevision(String id) {
|
||||||
return revisions.get(id);
|
//return revisions.get(id);
|
||||||
|
return UpdateCounter.current();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private <T> T get(String id, Class<T> type) {
|
||||||
|
Revisioned o = (Revisioned)cache.get(id);
|
||||||
|
if (o == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
Long rev = revisions.get(id);
|
||||||
|
if (rev == null) {
|
||||||
|
logger.tracev("get() missing rev");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
long oRev = o.getRevision() == null ? -1L : o.getRevision().longValue();
|
||||||
|
if (rev > oRev) {
|
||||||
|
logger.tracev("stale rev: {0} o.rev: {1}", rev.longValue(), oRev);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return o != null && type.isInstance(o) ? type.cast(o) : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected Object invalidateObject(String id) {
|
||||||
|
Object removed = cache.remove(id);
|
||||||
|
revisions.put(id, UpdateCounter.next());
|
||||||
|
return removed;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void addRevisioned(String id, Revisioned object) {
|
||||||
|
Long rev = revisions.get(id);
|
||||||
|
if (rev == null) {
|
||||||
|
rev = UpdateCounter.next();
|
||||||
|
revisions.put(id, rev);
|
||||||
|
}
|
||||||
|
if (rev.equals(object.getRevision())) {
|
||||||
|
cache.putForExternalRead(id, object);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void clear() {
|
public void clear() {
|
||||||
cache.clear();
|
cache.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
public AtomicLong getRealmCounter() {
|
|
||||||
return realmCounter;
|
|
||||||
}
|
|
||||||
|
|
||||||
public AtomicLong getClientCounter() {
|
|
||||||
return clientCounter;
|
|
||||||
}
|
|
||||||
|
|
||||||
public AtomicLong getClientTemplateCounter() {
|
|
||||||
return clientTemplateCounter;
|
|
||||||
}
|
|
||||||
|
|
||||||
public AtomicLong getRoleCounter() {
|
|
||||||
return roleCounter;
|
|
||||||
}
|
|
||||||
|
|
||||||
public AtomicLong getGroupCounter() {
|
|
||||||
return groupCounter;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public CachedRealm getCachedRealm(String id) {
|
public CachedRealm getCachedRealm(String id) {
|
||||||
return get(id, CachedRealm.class);
|
return get(id, CachedRealm.class);
|
||||||
|
@ -92,41 +110,23 @@ public class RevisionedRealmCache implements RealmCache {
|
||||||
@Override
|
@Override
|
||||||
public void invalidateCachedRealm(CachedRealm realm) {
|
public void invalidateCachedRealm(CachedRealm realm) {
|
||||||
logger.tracev("Invalidating realm {0}", realm.getId());
|
logger.tracev("Invalidating realm {0}", realm.getId());
|
||||||
invalidateObject(realm.getId(), realmCounter);
|
invalidateObject(realm.getId());
|
||||||
realmLookup.remove(realm.getName());
|
realmLookup.remove(realm.getName());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void invalidateCachedRealmById(String id) {
|
public void invalidateCachedRealmById(String id) {
|
||||||
CachedRealm cached = (CachedRealm) invalidateObject(id, realmCounter);
|
CachedRealm cached = (CachedRealm) invalidateObject(id);
|
||||||
if (cached != null) realmLookup.remove(cached.getName());
|
if (cached != null) realmLookup.remove(cached.getName());
|
||||||
}
|
}
|
||||||
|
|
||||||
protected Object invalidateObject(String id, AtomicLong counter) {
|
|
||||||
revisions.put(id, counter.incrementAndGet());
|
|
||||||
Object removed = cache.remove(id);
|
|
||||||
revisions.put(id, counter.incrementAndGet());
|
|
||||||
return removed;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void addCachedRealm(CachedRealm realm) {
|
public void addCachedRealm(CachedRealm realm) {
|
||||||
logger.tracev("Adding realm {0}", realm.getId());
|
logger.tracev("Adding realm {0}", realm.getId());
|
||||||
addRevisioned(realm.getId(), (Revisioned) realm, realmCounter);
|
addRevisioned(realm.getId(), (Revisioned) realm);
|
||||||
realmLookup.put(realm.getName(), realm.getId());
|
realmLookup.put(realm.getName(), realm.getId());
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void addRevisioned(String id, Revisioned object, AtomicLong counter) {
|
|
||||||
Long rev = revisions.get(id);
|
|
||||||
if (rev == null) {
|
|
||||||
rev = counter.incrementAndGet();
|
|
||||||
revisions.put(id, rev);
|
|
||||||
}
|
|
||||||
if (rev.equals(object.getRevision())) {
|
|
||||||
cache.putForExternalRead(id, object);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public CachedRealm getCachedRealmByName(String name) {
|
public CachedRealm getCachedRealmByName(String name) {
|
||||||
String id = realmLookup.get(name);
|
String id = realmLookup.get(name);
|
||||||
|
@ -140,20 +140,20 @@ public class RevisionedRealmCache implements RealmCache {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void invalidateApplication(CachedClient app) {
|
public void invalidateApplication(CachedClient app) {
|
||||||
logger.infov("Removing application {0}", app.getClientAuthenticatorType());
|
logger.tracev("Removing application {0}", app.getId());
|
||||||
invalidateObject(app.getId(), clientCounter);
|
invalidateObject(app.getId());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void addCachedClient(CachedClient app) {
|
public void addCachedClient(CachedClient app) {
|
||||||
logger.tracev("Adding application {0}", app.getId());
|
logger.tracev("Adding application {0}", app.getId());
|
||||||
addRevisioned(app.getId(), (Revisioned) app, clientCounter);
|
addRevisioned(app.getId(), (Revisioned) app);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void invalidateCachedApplicationById(String id) {
|
public void invalidateCachedApplicationById(String id) {
|
||||||
CachedClient client = (CachedClient)invalidateObject(id, clientCounter);
|
CachedClient client = (CachedClient)invalidateObject(id);
|
||||||
if (client != null) logger.infov("Removing application {0}", client.getClientId());
|
if (client != null) logger.tracev("Removing application {0}", client.getClientId());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -170,26 +170,26 @@ public class RevisionedRealmCache implements RealmCache {
|
||||||
@Override
|
@Override
|
||||||
public void invalidateGroup(CachedGroup role) {
|
public void invalidateGroup(CachedGroup role) {
|
||||||
logger.tracev("Removing group {0}", role.getId());
|
logger.tracev("Removing group {0}", role.getId());
|
||||||
invalidateObject(role.getId(), groupCounter);
|
invalidateObject(role.getId());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void addCachedGroup(CachedGroup role) {
|
public void addCachedGroup(CachedGroup role) {
|
||||||
logger.tracev("Adding group {0}", role.getId());
|
logger.tracev("Adding group {0}", role.getId());
|
||||||
addRevisioned(role.getId(), (Revisioned) role, groupCounter);
|
addRevisioned(role.getId(), (Revisioned) role);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void invalidateCachedGroupById(String id) {
|
public void invalidateCachedGroupById(String id) {
|
||||||
logger.tracev("Removing group {0}", id);
|
logger.tracev("Removing group {0}", id);
|
||||||
invalidateObject(id, groupCounter);
|
invalidateObject(id);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void invalidateGroupById(String id) {
|
public void invalidateGroupById(String id) {
|
||||||
logger.tracev("Removing group {0}", id);
|
logger.tracev("Removing group {0}", id);
|
||||||
invalidateObject(id, groupCounter);
|
invalidateObject(id);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -200,13 +200,13 @@ public class RevisionedRealmCache implements RealmCache {
|
||||||
@Override
|
@Override
|
||||||
public void invalidateRole(CachedRole role) {
|
public void invalidateRole(CachedRole role) {
|
||||||
logger.tracev("Removing role {0}", role.getId());
|
logger.tracev("Removing role {0}", role.getId());
|
||||||
invalidateObject(role.getId(), roleCounter);
|
invalidateObject(role.getId());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void invalidateRoleById(String id) {
|
public void invalidateRoleById(String id) {
|
||||||
logger.tracev("Removing role {0}", id);
|
logger.tracev("Removing role {0}", id);
|
||||||
invalidateObject(id, roleCounter);
|
invalidateObject(id);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -218,31 +218,13 @@ public class RevisionedRealmCache implements RealmCache {
|
||||||
@Override
|
@Override
|
||||||
public void addCachedRole(CachedRole role) {
|
public void addCachedRole(CachedRole role) {
|
||||||
logger.tracev("Adding role {0}", role.getId());
|
logger.tracev("Adding role {0}", role.getId());
|
||||||
addRevisioned(role.getId(), (Revisioned) role, roleCounter);
|
addRevisioned(role.getId(), (Revisioned) role);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void invalidateCachedRoleById(String id) {
|
public void invalidateCachedRoleById(String id) {
|
||||||
logger.tracev("Removing role {0}", id);
|
logger.tracev("Removing role {0}", id);
|
||||||
invalidateObject(id, roleCounter);
|
invalidateObject(id);
|
||||||
}
|
|
||||||
|
|
||||||
private <T> T get(String id, Class<T> type) {
|
|
||||||
Revisioned o = (Revisioned)cache.get(id);
|
|
||||||
if (o == null) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
Long rev = revisions.get(id);
|
|
||||||
if (rev == null) {
|
|
||||||
logger.tracev("get() missing rev");
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
long oRev = o.getRevision() == null ? -1L : o.getRevision().longValue();
|
|
||||||
if (rev > oRev) {
|
|
||||||
logger.tracev("get() rev: {0} o.rev: {1}", rev.longValue(), oRev);
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
return o != null && type.isInstance(o) ? type.cast(o) : null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -253,19 +235,19 @@ public class RevisionedRealmCache implements RealmCache {
|
||||||
@Override
|
@Override
|
||||||
public void invalidateClientTemplate(CachedClientTemplate app) {
|
public void invalidateClientTemplate(CachedClientTemplate app) {
|
||||||
logger.tracev("Removing client template {0}", app.getId());
|
logger.tracev("Removing client template {0}", app.getId());
|
||||||
invalidateObject(app.getId(), clientTemplateCounter);
|
invalidateObject(app.getId());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void addCachedClientTemplate(CachedClientTemplate app) {
|
public void addCachedClientTemplate(CachedClientTemplate app) {
|
||||||
logger.tracev("Adding client template {0}", app.getId());
|
logger.tracev("Adding client template {0}", app.getId());
|
||||||
addRevisioned(app.getId(), (Revisioned) app, clientTemplateCounter);
|
addRevisioned(app.getId(), (Revisioned) app);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void invalidateCachedClientTemplateById(String id) {
|
public void invalidateCachedClientTemplateById(String id) {
|
||||||
logger.tracev("Removing client template {0}", id);
|
logger.tracev("Removing client template {0}", id);
|
||||||
invalidateObject(id, clientTemplateCounter);
|
invalidateObject(id);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
20
model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/counter/UpdateCounter.java
vendored
Executable file
20
model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/counter/UpdateCounter.java
vendored
Executable file
|
@ -0,0 +1,20 @@
|
||||||
|
package org.keycloak.models.cache.infinispan.counter;
|
||||||
|
|
||||||
|
import java.util.concurrent.atomic.AtomicLong;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
||||||
|
*/
|
||||||
|
public class UpdateCounter {
|
||||||
|
|
||||||
|
private static final AtomicLong counter = new AtomicLong();
|
||||||
|
|
||||||
|
public static long current() {
|
||||||
|
return counter.get();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static long next() {
|
||||||
|
return counter.incrementAndGet();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
463
model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/locking/LockingCacheRealmProvider.java
vendored
Executable file
463
model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/locking/LockingCacheRealmProvider.java
vendored
Executable file
|
@ -0,0 +1,463 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2016 Red Hat, Inc. and/or its affiliates
|
||||||
|
* and other contributors as indicated by the @author tags.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.keycloak.models.cache.infinispan.locking;
|
||||||
|
|
||||||
|
import org.jboss.logging.Logger;
|
||||||
|
import org.keycloak.migration.MigrationModel;
|
||||||
|
import org.keycloak.models.ClientModel;
|
||||||
|
import org.keycloak.models.ClientTemplateModel;
|
||||||
|
import org.keycloak.models.GroupModel;
|
||||||
|
import org.keycloak.models.KeycloakSession;
|
||||||
|
import org.keycloak.models.KeycloakTransaction;
|
||||||
|
import org.keycloak.models.RealmModel;
|
||||||
|
import org.keycloak.models.RealmProvider;
|
||||||
|
import org.keycloak.models.RoleModel;
|
||||||
|
import org.keycloak.models.cache.CacheRealmProvider;
|
||||||
|
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.ClientAdapter;
|
||||||
|
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 java.util.Collections;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.LinkedList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||||
|
* @version $Revision: 1 $
|
||||||
|
*/
|
||||||
|
public class LockingCacheRealmProvider implements CacheRealmProvider {
|
||||||
|
protected static final Logger logger = Logger.getLogger(LockingCacheRealmProvider.class);
|
||||||
|
protected LockingRealmCache cache;
|
||||||
|
protected KeycloakSession session;
|
||||||
|
protected RealmProvider delegate;
|
||||||
|
protected boolean transactionActive;
|
||||||
|
protected boolean setRollbackOnly;
|
||||||
|
|
||||||
|
protected Set<String> realmInvalidations = new HashSet<>();
|
||||||
|
protected Set<String> appInvalidations = new HashSet<>();
|
||||||
|
protected Set<String> clientTemplateInvalidations = new HashSet<>();
|
||||||
|
protected Set<String> roleInvalidations = new HashSet<>();
|
||||||
|
protected Set<String> groupInvalidations = new HashSet<>();
|
||||||
|
protected Map<String, RealmModel> managedRealms = new HashMap<>();
|
||||||
|
protected Map<String, ClientModel> managedApplications = new HashMap<>();
|
||||||
|
protected Map<String, ClientTemplateModel> managedClientTemplates = new HashMap<>();
|
||||||
|
protected Map<String, RoleModel> managedRoles = new HashMap<>();
|
||||||
|
protected Map<String, GroupModel> managedGroups = new HashMap<>();
|
||||||
|
|
||||||
|
protected boolean clearAll;
|
||||||
|
|
||||||
|
public LockingCacheRealmProvider(LockingRealmCache cache, KeycloakSession session) {
|
||||||
|
this.cache = cache;
|
||||||
|
this.session = session;
|
||||||
|
|
||||||
|
session.getTransaction().enlistPrepare(getPrepareTransaction());
|
||||||
|
session.getTransaction().enlistAfterCompletion(getAfterTransaction());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void clear() {
|
||||||
|
cache.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public MigrationModel getMigrationModel() {
|
||||||
|
return getDelegate().getMigrationModel();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public RealmProvider getDelegate() {
|
||||||
|
if (!transactionActive) throw new IllegalStateException("Cannot access delegate without a transaction");
|
||||||
|
if (delegate != null) return delegate;
|
||||||
|
delegate = session.getProvider(RealmProvider.class);
|
||||||
|
return delegate;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void registerRealmInvalidation(String id) {
|
||||||
|
realmInvalidations.add(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void registerApplicationInvalidation(String id) {
|
||||||
|
appInvalidations.add(id);
|
||||||
|
}
|
||||||
|
@Override
|
||||||
|
public void registerClientTemplateInvalidation(String id) {
|
||||||
|
clientTemplateInvalidations.add(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void registerRoleInvalidation(String id) {
|
||||||
|
roleInvalidations.add(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void registerGroupInvalidation(String id) {
|
||||||
|
groupInvalidations.add(id);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void runInvalidations() {
|
||||||
|
for (String id : realmInvalidations) {
|
||||||
|
cache.invalidateCachedRealmById(id);
|
||||||
|
}
|
||||||
|
for (String id : roleInvalidations) {
|
||||||
|
cache.invalidateRoleById(id);
|
||||||
|
}
|
||||||
|
for (String id : groupInvalidations) {
|
||||||
|
cache.invalidateGroupById(id);
|
||||||
|
}
|
||||||
|
for (String id : appInvalidations) {
|
||||||
|
cache.invalidateCachedApplicationById(id);
|
||||||
|
}
|
||||||
|
for (String id : clientTemplateInvalidations) {
|
||||||
|
cache.invalidateCachedClientTemplateById(id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private KeycloakTransaction getPrepareTransaction() {
|
||||||
|
return new KeycloakTransaction() {
|
||||||
|
@Override
|
||||||
|
public void begin() {
|
||||||
|
transactionActive = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void commit() {
|
||||||
|
if (delegate == null) return;
|
||||||
|
List<String> invalidates = new LinkedList<>();
|
||||||
|
for (String id : realmInvalidations) {
|
||||||
|
invalidates.add(id);
|
||||||
|
}
|
||||||
|
for (String id : roleInvalidations) {
|
||||||
|
invalidates.add(id);
|
||||||
|
}
|
||||||
|
for (String id : groupInvalidations) {
|
||||||
|
invalidates.add(id);
|
||||||
|
}
|
||||||
|
for (String id : appInvalidations) {
|
||||||
|
invalidates.add(id);
|
||||||
|
}
|
||||||
|
for (String id : clientTemplateInvalidations) {
|
||||||
|
invalidates.add(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
Collections.sort(invalidates); // lock ordering
|
||||||
|
cache.getRevisions().startBatch();
|
||||||
|
for (String id : invalidates) {
|
||||||
|
cache.getRevisions().getAdvancedCache().lock(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void rollback() {
|
||||||
|
setRollbackOnly = true;
|
||||||
|
transactionActive = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setRollbackOnly() {
|
||||||
|
setRollbackOnly = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean getRollbackOnly() {
|
||||||
|
return setRollbackOnly;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isActive() {
|
||||||
|
return transactionActive;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private KeycloakTransaction getAfterTransaction() {
|
||||||
|
return new KeycloakTransaction() {
|
||||||
|
@Override
|
||||||
|
public void begin() {
|
||||||
|
transactionActive = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void commit() {
|
||||||
|
try {
|
||||||
|
if (delegate == null) return;
|
||||||
|
if (clearAll) {
|
||||||
|
cache.clear();
|
||||||
|
}
|
||||||
|
runInvalidations();
|
||||||
|
transactionActive = false;
|
||||||
|
} finally {
|
||||||
|
cache.endRevisionBatch();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void rollback() {
|
||||||
|
try {
|
||||||
|
setRollbackOnly = true;
|
||||||
|
runInvalidations();
|
||||||
|
transactionActive = false;
|
||||||
|
} finally {
|
||||||
|
cache.endRevisionBatch();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setRollbackOnly() {
|
||||||
|
setRollbackOnly = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean getRollbackOnly() {
|
||||||
|
return setRollbackOnly;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isActive() {
|
||||||
|
return transactionActive;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public RealmModel createRealm(String name) {
|
||||||
|
RealmModel realm = getDelegate().createRealm(name);
|
||||||
|
registerRealmInvalidation(realm.getId());
|
||||||
|
return realm;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public RealmModel createRealm(String id, String name) {
|
||||||
|
RealmModel realm = getDelegate().createRealm(id, name);
|
||||||
|
registerRealmInvalidation(realm.getId());
|
||||||
|
return realm;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public RealmModel getRealm(String id) {
|
||||||
|
CachedRealm cached = cache.getCachedRealm(id);
|
||||||
|
if (cached != null) {
|
||||||
|
logger.tracev("by id cache hit: {0}", cached.getName());
|
||||||
|
}
|
||||||
|
if (cached == null) {
|
||||||
|
Long loaded = cache.getCurrentRevision(id);
|
||||||
|
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);
|
||||||
|
} else if (realmInvalidations.contains(id)) {
|
||||||
|
return getDelegate().getRealm(id);
|
||||||
|
} else if (managedRealms.containsKey(id)) {
|
||||||
|
return managedRealms.get(id);
|
||||||
|
}
|
||||||
|
RealmAdapter adapter = new RealmAdapter(cached, this);
|
||||||
|
managedRealms.put(id, adapter);
|
||||||
|
return adapter;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public RealmModel getRealmByName(String name) {
|
||||||
|
CachedRealm cached = cache.getCachedRealmByName(name);
|
||||||
|
if (cached != null) {
|
||||||
|
logger.tracev("by name cache hit: {0}", cached.getName());
|
||||||
|
}
|
||||||
|
if (cached == null) {
|
||||||
|
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);
|
||||||
|
} else if (realmInvalidations.contains(cached.getId())) {
|
||||||
|
return getDelegate().getRealmByName(name);
|
||||||
|
} else if (managedRealms.containsKey(cached.getId())) {
|
||||||
|
return managedRealms.get(cached.getId());
|
||||||
|
}
|
||||||
|
RealmAdapter adapter = new RealmAdapter(cached, this);
|
||||||
|
managedRealms.put(cached.getId(), adapter);
|
||||||
|
return adapter;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<RealmModel> getRealms() {
|
||||||
|
// Retrieve realms from backend
|
||||||
|
List<RealmModel> backendRealms = getDelegate().getRealms();
|
||||||
|
|
||||||
|
// Return cache delegates to ensure cache invalidated during write operations
|
||||||
|
List<RealmModel> cachedRealms = new LinkedList<RealmModel>();
|
||||||
|
for (RealmModel realm : backendRealms) {
|
||||||
|
RealmModel cached = getRealm(realm.getId());
|
||||||
|
cachedRealms.add(cached);
|
||||||
|
}
|
||||||
|
return cachedRealms;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean removeRealm(String id) {
|
||||||
|
cache.invalidateCachedRealmById(id);
|
||||||
|
|
||||||
|
RealmModel realm = getDelegate().getRealm(id);
|
||||||
|
Set<RoleModel> realmRoles = null;
|
||||||
|
if (realm != null) {
|
||||||
|
realmRoles = realm.getRoles();
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean didIt = getDelegate().removeRealm(id);
|
||||||
|
realmInvalidations.add(id);
|
||||||
|
|
||||||
|
// TODO: Temporary workaround to invalidate cached realm roles
|
||||||
|
if (didIt && realmRoles != null) {
|
||||||
|
for (RoleModel role : realmRoles) {
|
||||||
|
roleInvalidations.add(role.getId());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return didIt;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void close() {
|
||||||
|
if (delegate != null) delegate.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public RoleModel getRoleById(String id, RealmModel realm) {
|
||||||
|
CachedRole cached = cache.getRole(id);
|
||||||
|
if (cached != null && !cached.getRealm().equals(realm.getId())) {
|
||||||
|
cached = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (cached == null) {
|
||||||
|
Long loaded = cache.getCurrentRevision(id);
|
||||||
|
RoleModel model = getDelegate().getRoleById(id, realm);
|
||||||
|
if (model == null) return null;
|
||||||
|
if (roleInvalidations.contains(id)) return model;
|
||||||
|
if (model.getContainer() instanceof ClientModel) {
|
||||||
|
cached = new RevisionedCachedClientRole(loaded, ((ClientModel) model.getContainer()).getId(), model, realm);
|
||||||
|
} else {
|
||||||
|
cached = new RevisionedCachedRealmRole(loaded, model, realm);
|
||||||
|
}
|
||||||
|
cache.addCachedRole(cached);
|
||||||
|
|
||||||
|
} else if (roleInvalidations.contains(id)) {
|
||||||
|
return getDelegate().getRoleById(id, realm);
|
||||||
|
} else if (managedRoles.containsKey(id)) {
|
||||||
|
return managedRoles.get(id);
|
||||||
|
}
|
||||||
|
RoleAdapter adapter = new RoleAdapter(cached, cache, this, realm);
|
||||||
|
managedRoles.put(id, adapter);
|
||||||
|
return adapter;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public GroupModel getGroupById(String id, RealmModel realm) {
|
||||||
|
CachedGroup cached = cache.getGroup(id);
|
||||||
|
if (cached != null && !cached.getRealm().equals(realm.getId())) {
|
||||||
|
cached = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (cached == null) {
|
||||||
|
Long loaded = cache.getCurrentRevision(id);
|
||||||
|
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);
|
||||||
|
|
||||||
|
} else if (groupInvalidations.contains(id)) {
|
||||||
|
return getDelegate().getGroupById(id, realm);
|
||||||
|
} else if (managedGroups.containsKey(id)) {
|
||||||
|
return managedGroups.get(id);
|
||||||
|
}
|
||||||
|
GroupAdapter adapter = new GroupAdapter(cached, this, session, realm);
|
||||||
|
managedGroups.put(id, adapter);
|
||||||
|
return adapter;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ClientModel getClientById(String id, RealmModel realm) {
|
||||||
|
CachedClient cached = cache.getApplication(id);
|
||||||
|
if (cached != null && !cached.getRealm().equals(realm.getId())) {
|
||||||
|
cached = null;
|
||||||
|
}
|
||||||
|
if (cached != null && cached.getClientId().equals("client")) {
|
||||||
|
logger.tracev("client by id cache hit: {0}", cached.getClientId());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (cached == null) {
|
||||||
|
Long loaded = cache.getCurrentRevision(id);
|
||||||
|
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);
|
||||||
|
} else if (appInvalidations.contains(id)) {
|
||||||
|
return getDelegate().getClientById(id, realm);
|
||||||
|
} else if (managedApplications.containsKey(id)) {
|
||||||
|
return managedApplications.get(id);
|
||||||
|
}
|
||||||
|
ClientAdapter adapter = new ClientAdapter(realm, cached, this, cache);
|
||||||
|
managedApplications.put(id, adapter);
|
||||||
|
return adapter;
|
||||||
|
}
|
||||||
|
@Override
|
||||||
|
public ClientTemplateModel getClientTemplateById(String id, RealmModel realm) {
|
||||||
|
CachedClientTemplate cached = cache.getClientTemplate(id);
|
||||||
|
if (cached != null && !cached.getRealm().equals(realm.getId())) {
|
||||||
|
cached = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (cached == null) {
|
||||||
|
Long loaded = cache.getCurrentRevision(id);
|
||||||
|
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);
|
||||||
|
} else if (clientTemplateInvalidations.contains(id)) {
|
||||||
|
return getDelegate().getClientTemplateById(id, realm);
|
||||||
|
} else if (managedClientTemplates.containsKey(id)) {
|
||||||
|
return managedClientTemplates.get(id);
|
||||||
|
}
|
||||||
|
ClientTemplateModel adapter = new ClientTemplateAdapter(realm, cached, this, cache);
|
||||||
|
managedClientTemplates.put(id, adapter);
|
||||||
|
return adapter;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
161
model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/locking/LockingCacheRealmProviderFactory.java
vendored
Executable file
161
model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/locking/LockingCacheRealmProviderFactory.java
vendored
Executable file
|
@ -0,0 +1,161 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2016 Red Hat, Inc. and/or its affiliates
|
||||||
|
* and other contributors as indicated by the @author tags.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.keycloak.models.cache.infinispan.locking;
|
||||||
|
|
||||||
|
import org.infinispan.Cache;
|
||||||
|
import org.infinispan.notifications.Listener;
|
||||||
|
import org.infinispan.notifications.cachelistener.annotation.CacheEntriesEvicted;
|
||||||
|
import org.infinispan.notifications.cachelistener.annotation.CacheEntryCreated;
|
||||||
|
import org.infinispan.notifications.cachelistener.annotation.CacheEntryInvalidated;
|
||||||
|
import org.infinispan.notifications.cachelistener.annotation.CacheEntryRemoved;
|
||||||
|
import org.infinispan.notifications.cachelistener.event.CacheEntriesEvictedEvent;
|
||||||
|
import org.infinispan.notifications.cachelistener.event.CacheEntryCreatedEvent;
|
||||||
|
import org.infinispan.notifications.cachelistener.event.CacheEntryInvalidatedEvent;
|
||||||
|
import org.infinispan.notifications.cachelistener.event.CacheEntryRemovedEvent;
|
||||||
|
import org.jboss.logging.Logger;
|
||||||
|
import org.keycloak.Config;
|
||||||
|
import org.keycloak.connections.infinispan.InfinispanConnectionProvider;
|
||||||
|
import org.keycloak.models.KeycloakSession;
|
||||||
|
import org.keycloak.models.KeycloakSessionFactory;
|
||||||
|
import org.keycloak.models.cache.CacheRealmProvider;
|
||||||
|
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>
|
||||||
|
*/
|
||||||
|
public class LockingCacheRealmProviderFactory implements CacheRealmProviderFactory {
|
||||||
|
|
||||||
|
private static final Logger log = Logger.getLogger(LockingCacheRealmProviderFactory.class);
|
||||||
|
|
||||||
|
protected volatile LockingRealmCache realmCache;
|
||||||
|
|
||||||
|
protected final ConcurrentHashMap<String, String> realmLookup = new ConcurrentHashMap<>();
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public CacheRealmProvider create(KeycloakSession session) {
|
||||||
|
lazyInit(session);
|
||||||
|
return new LockingCacheRealmProvider(realmCache, session);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void lazyInit(KeycloakSession session) {
|
||||||
|
if (realmCache == null) {
|
||||||
|
synchronized (this) {
|
||||||
|
if (realmCache == null) {
|
||||||
|
Cache<String, Object> cache = session.getProvider(InfinispanConnectionProvider.class).getCache(InfinispanConnectionProvider.REALM_CACHE_NAME);
|
||||||
|
Cache<String, Long> counterCache = session.getProvider(InfinispanConnectionProvider.class).getCache(LockingConnectionProviderFactory.COUNTER_CACHE_NAME);
|
||||||
|
cache.addListener(new CacheListener());
|
||||||
|
realmCache = new LockingRealmCache(cache, counterCache, realmLookup);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void init(Config.Scope config) {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void postInit(KeycloakSessionFactory factory) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void close() {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getId() {
|
||||||
|
return "infinispan-locking";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Listener
|
||||||
|
public class CacheListener {
|
||||||
|
|
||||||
|
@CacheEntryCreated
|
||||||
|
public void created(CacheEntryCreatedEvent<String, Object> event) {
|
||||||
|
if (!event.isPre()) {
|
||||||
|
Object object = event.getValue();
|
||||||
|
if (object != null) {
|
||||||
|
if (object instanceof CachedRealm) {
|
||||||
|
CachedRealm realm = (CachedRealm) object;
|
||||||
|
realmLookup.put(realm.getName(), realm.getId());
|
||||||
|
log.tracev("Realm added realm={0}", realm.getName());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@CacheEntryRemoved
|
||||||
|
public void removed(CacheEntryRemovedEvent<String, Object> event) {
|
||||||
|
if (event.isPre()) {
|
||||||
|
Object object = event.getValue();
|
||||||
|
if (object != null) {
|
||||||
|
remove(object);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@CacheEntryInvalidated
|
||||||
|
public void removed(CacheEntryInvalidatedEvent<String, Object> event) {
|
||||||
|
if (event.isPre()) {
|
||||||
|
Object object = event.getValue();
|
||||||
|
if (object != null) {
|
||||||
|
remove(object);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@CacheEntriesEvicted
|
||||||
|
public void userEvicted(CacheEntriesEvictedEvent<String, Object> event) {
|
||||||
|
for (Object object : event.getEntries().values()) {
|
||||||
|
remove(object);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void remove(Object object) {
|
||||||
|
if (object instanceof CachedRealm) {
|
||||||
|
CachedRealm realm = (CachedRealm) object;
|
||||||
|
|
||||||
|
realmLookup.remove(realm.getName());
|
||||||
|
|
||||||
|
for (String r : realm.getRealmRoles().values()) {
|
||||||
|
realmCache.evictCachedRoleById(r);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (String c : realm.getClients().values()) {
|
||||||
|
realmCache.evictCachedApplicationById(c);
|
||||||
|
}
|
||||||
|
|
||||||
|
log.tracev("Realm removed realm={0}", realm.getName());
|
||||||
|
} else if (object instanceof CachedClient) {
|
||||||
|
CachedClient client = (CachedClient) object;
|
||||||
|
|
||||||
|
for (String r : client.getRoles().values()) {
|
||||||
|
realmCache.evictCachedRoleById(r);
|
||||||
|
}
|
||||||
|
|
||||||
|
log.tracev("Client removed client={0}", client.getId());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,52 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2016 Red Hat, Inc. and/or its affiliates
|
||||||
|
* and other contributors as indicated by the @author tags.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.keycloak.models.cache.infinispan.locking;
|
||||||
|
|
||||||
|
import org.infinispan.configuration.cache.Configuration;
|
||||||
|
import org.infinispan.configuration.cache.ConfigurationBuilder;
|
||||||
|
import org.infinispan.transaction.LockingMode;
|
||||||
|
import org.infinispan.transaction.lookup.DummyTransactionManagerLookup;
|
||||||
|
import org.jboss.logging.Logger;
|
||||||
|
import org.keycloak.connections.infinispan.DefaultInfinispanConnectionProviderFactory;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
||||||
|
*/
|
||||||
|
public class LockingConnectionProviderFactory extends DefaultInfinispanConnectionProviderFactory {
|
||||||
|
public static final String COUNTER_CACHE_NAME = "COUNTER_CACHE";
|
||||||
|
|
||||||
|
protected static final Logger logger = Logger.getLogger(LockingConnectionProviderFactory.class);
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getId() {
|
||||||
|
return "locking";
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
protected void initEmbedded() {
|
||||||
|
super.initEmbedded();
|
||||||
|
ConfigurationBuilder counterConfigBuilder = new ConfigurationBuilder();
|
||||||
|
counterConfigBuilder.invocationBatching().enable();
|
||||||
|
counterConfigBuilder.transaction().transactionManagerLookup(new DummyTransactionManagerLookup());
|
||||||
|
counterConfigBuilder.transaction().lockingMode(LockingMode.PESSIMISTIC);
|
||||||
|
Configuration counterCacheConfiguration = counterConfigBuilder.build();
|
||||||
|
|
||||||
|
cacheManager.defineConfiguration(COUNTER_CACHE_NAME, counterCacheConfiguration);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
294
model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/locking/LockingRealmCache.java
vendored
Executable file
294
model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/locking/LockingRealmCache.java
vendored
Executable file
|
@ -0,0 +1,294 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2016 Red Hat, Inc. and/or its affiliates
|
||||||
|
* and other contributors as indicated by the @author tags.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.keycloak.models.cache.infinispan.locking;
|
||||||
|
|
||||||
|
import org.infinispan.Cache;
|
||||||
|
import org.jboss.logging.Logger;
|
||||||
|
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>
|
||||||
|
*/
|
||||||
|
public class LockingRealmCache implements RealmCache {
|
||||||
|
|
||||||
|
protected static final Logger logger = Logger.getLogger(LockingRealmCache.class);
|
||||||
|
|
||||||
|
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;
|
||||||
|
|
||||||
|
public LockingRealmCache(Cache<String, Object> cache, Cache<String, Long> revisions, ConcurrentHashMap<String, String> realmLookup) {
|
||||||
|
this.cache = cache;
|
||||||
|
this.realmLookup = realmLookup;
|
||||||
|
this.revisions = revisions;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Cache<String, Object> getCache() {
|
||||||
|
return cache;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Cache<String, Long> getRevisions() {
|
||||||
|
return revisions;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void startRevisionBatch() {
|
||||||
|
revisions.startBatch();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void endRevisionBatch() {
|
||||||
|
try {
|
||||||
|
revisions.endBatch(true);
|
||||||
|
} catch (Exception e) {
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private <T> T get(String id, Class<T> type) {
|
||||||
|
Revisioned o = (Revisioned)cache.get(id);
|
||||||
|
if (o == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
Long rev = revisions.get(id);
|
||||||
|
if (rev == null) {
|
||||||
|
logger.tracev("get() missing rev");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
long oRev = o.getRevision() == null ? -1L : o.getRevision().longValue();
|
||||||
|
if (rev > oRev) {
|
||||||
|
logger.tracev("get() rev: {0} o.rev: {1}", rev.longValue(), oRev);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return o != null && type.isInstance(o) ? type.cast(o) : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected Object invalidateObject(String id, AtomicLong counter) {
|
||||||
|
Object removed = cache.remove(id);
|
||||||
|
revisions.put(id, counter.incrementAndGet());
|
||||||
|
return removed;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void addRevisioned(String id, Revisioned object, AtomicLong counter) {
|
||||||
|
//startRevisionBatch();
|
||||||
|
try {
|
||||||
|
//revisions.getAdvancedCache().lock(id);
|
||||||
|
Long rev = revisions.get(id);
|
||||||
|
if (rev == null) {
|
||||||
|
rev = counter.incrementAndGet();
|
||||||
|
revisions.put(id, rev);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
revisions.startBatch();
|
||||||
|
revisions.getAdvancedCache().lock(id);
|
||||||
|
rev = revisions.get(id);
|
||||||
|
if (rev == null) {
|
||||||
|
rev = counter.incrementAndGet();
|
||||||
|
revisions.put(id, rev);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (rev.equals(object.getRevision())) {
|
||||||
|
cache.putForExternalRead(id, object);
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
endRevisionBatch();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
public Long getCurrentRevision(String id) {
|
||||||
|
return revisions.get(id);
|
||||||
|
}
|
||||||
|
@Override
|
||||||
|
public void clear() {
|
||||||
|
cache.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public CachedRealm getCachedRealm(String id) {
|
||||||
|
return get(id, CachedRealm.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void invalidateCachedRealm(CachedRealm realm) {
|
||||||
|
logger.tracev("Invalidating realm {0}", realm.getId());
|
||||||
|
invalidateObject(realm.getId(), realmCounter);
|
||||||
|
realmLookup.remove(realm.getName());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void invalidateCachedRealmById(String id) {
|
||||||
|
CachedRealm cached = (CachedRealm) invalidateObject(id, realmCounter);
|
||||||
|
if (cached != null) realmLookup.remove(cached.getName());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void addCachedRealm(CachedRealm realm) {
|
||||||
|
logger.tracev("Adding realm {0}", realm.getId());
|
||||||
|
addRevisioned(realm.getId(), (Revisioned) realm, realmCounter);
|
||||||
|
realmLookup.put(realm.getName(), realm.getId());
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public CachedRealm getCachedRealmByName(String name) {
|
||||||
|
String id = realmLookup.get(name);
|
||||||
|
return id != null ? getCachedRealm(id) : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public CachedClient getApplication(String id) {
|
||||||
|
return get(id, CachedClient.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void invalidateApplication(CachedClient app) {
|
||||||
|
logger.tracev("Removing application {0}", app.getId());
|
||||||
|
invalidateObject(app.getId(), clientCounter);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void addCachedClient(CachedClient app) {
|
||||||
|
logger.tracev("Adding application {0}", app.getId());
|
||||||
|
addRevisioned(app.getId(), (Revisioned) app, clientCounter);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void invalidateCachedApplicationById(String id) {
|
||||||
|
CachedClient client = (CachedClient)invalidateObject(id, clientCounter);
|
||||||
|
if (client != null) logger.tracev("Removing application {0}", client.getClientId());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void evictCachedApplicationById(String id) {
|
||||||
|
logger.tracev("Evicting application {0}", id);
|
||||||
|
cache.evict(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public CachedGroup getGroup(String id) {
|
||||||
|
return get(id, CachedGroup.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void invalidateGroup(CachedGroup role) {
|
||||||
|
logger.tracev("Removing group {0}", role.getId());
|
||||||
|
invalidateObject(role.getId(), groupCounter);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void addCachedGroup(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);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void invalidateGroupById(String id) {
|
||||||
|
logger.tracev("Removing group {0}", id);
|
||||||
|
invalidateObject(id, groupCounter);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public CachedRole getRole(String id) {
|
||||||
|
return get(id, CachedRole.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void invalidateRole(CachedRole role) {
|
||||||
|
logger.tracev("Removing role {0}", role.getId());
|
||||||
|
invalidateObject(role.getId(), roleCounter);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void invalidateRoleById(String id) {
|
||||||
|
logger.tracev("Removing role {0}", id);
|
||||||
|
invalidateObject(id, roleCounter);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void evictCachedRoleById(String id) {
|
||||||
|
logger.tracev("Evicting role {0}", id);
|
||||||
|
cache.evict(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void addCachedRole(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);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public CachedClientTemplate getClientTemplate(String id) {
|
||||||
|
return get(id, CachedClientTemplate.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void invalidateClientTemplate(CachedClientTemplate app) {
|
||||||
|
logger.tracev("Removing client template {0}", app.getId());
|
||||||
|
invalidateObject(app.getId(), clientTemplateCounter);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void addCachedClientTemplate(CachedClientTemplate app) {
|
||||||
|
logger.tracev("Adding client template {0}", app.getId());
|
||||||
|
addRevisioned(app.getId(), (Revisioned) app, clientTemplateCounter);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void invalidateCachedClientTemplateById(String id) {
|
||||||
|
logger.tracev("Removing client template {0}", id);
|
||||||
|
invalidateObject(id, clientTemplateCounter);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void evictCachedClientTemplateById(String id) {
|
||||||
|
logger.tracev("Evicting client template {0}", id);
|
||||||
|
cache.evict(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
|
@ -16,4 +16,5 @@
|
||||||
#
|
#
|
||||||
|
|
||||||
org.keycloak.connections.infinispan.DefaultInfinispanConnectionProviderFactory
|
org.keycloak.connections.infinispan.DefaultInfinispanConnectionProviderFactory
|
||||||
org.keycloak.models.cache.infinispan.counter.RevisionedConnectionProviderFactory
|
org.keycloak.models.cache.infinispan.counter.RevisionedConnectionProviderFactory
|
||||||
|
org.keycloak.models.cache.infinispan.locking.LockingConnectionProviderFactory
|
|
@ -16,4 +16,5 @@
|
||||||
#
|
#
|
||||||
|
|
||||||
org.keycloak.models.cache.infinispan.InfinispanCacheRealmProviderFactory
|
org.keycloak.models.cache.infinispan.InfinispanCacheRealmProviderFactory
|
||||||
org.keycloak.models.cache.infinispan.counter.RevisionedCacheRealmProviderFactory
|
org.keycloak.models.cache.infinispan.counter.RevisionedCacheRealmProviderFactory
|
||||||
|
org.keycloak.models.cache.infinispan.locking.LockingCacheRealmProviderFactory
|
|
@ -0,0 +1,88 @@
|
||||||
|
package org.keycloak.models.sessions.infinispan.initializer;
|
||||||
|
|
||||||
|
import org.infinispan.Cache;
|
||||||
|
import org.infinispan.configuration.cache.CacheMode;
|
||||||
|
import org.infinispan.configuration.cache.Configuration;
|
||||||
|
import org.infinispan.configuration.cache.ConfigurationBuilder;
|
||||||
|
import org.infinispan.configuration.cache.VersioningScheme;
|
||||||
|
import org.infinispan.configuration.global.GlobalConfigurationBuilder;
|
||||||
|
import org.infinispan.manager.DefaultCacheManager;
|
||||||
|
import org.infinispan.manager.EmbeddedCacheManager;
|
||||||
|
import org.infinispan.transaction.LockingMode;
|
||||||
|
import org.infinispan.transaction.TransactionMode;
|
||||||
|
import org.infinispan.transaction.lookup.DummyTransactionManagerLookup;
|
||||||
|
import org.infinispan.util.concurrent.IsolationLevel;
|
||||||
|
import org.junit.Ignore;
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.keycloak.connections.infinispan.InfinispanConnectionProvider;
|
||||||
|
|
||||||
|
import java.util.concurrent.CountDownLatch;
|
||||||
|
import java.util.concurrent.ExecutorService;
|
||||||
|
import java.util.concurrent.Executors;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||||
|
* @version $Revision: 1 $
|
||||||
|
*/
|
||||||
|
@Ignore
|
||||||
|
public class ConcurrencyLockingTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testLocking() throws Exception {
|
||||||
|
final DefaultCacheManager cacheManager = getVersionedCacheManager();
|
||||||
|
Cache<String, String> cache = cacheManager.getCache("COUNTER_CACHE");
|
||||||
|
cache.put("key", "init");
|
||||||
|
ExecutorService executor = Executors.newSingleThreadExecutor();
|
||||||
|
executor.execute(new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
Cache<String, String> cache = cacheManager.getCache("COUNTER_CACHE");
|
||||||
|
cache.startBatch();
|
||||||
|
System.out.println("thread lock");
|
||||||
|
cache.getAdvancedCache().lock("key");
|
||||||
|
try {
|
||||||
|
Thread.sleep(100000);
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
cache.endBatch(true);
|
||||||
|
|
||||||
|
}
|
||||||
|
});
|
||||||
|
Thread.sleep(10);
|
||||||
|
cache.startBatch();
|
||||||
|
cache.getAdvancedCache().lock("key");
|
||||||
|
cache.put("key", "1234");
|
||||||
|
System.out.println("after put");
|
||||||
|
cache.endBatch(true);
|
||||||
|
|
||||||
|
Thread.sleep(1000000);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
protected DefaultCacheManager getVersionedCacheManager() {
|
||||||
|
GlobalConfigurationBuilder gcb = new GlobalConfigurationBuilder();
|
||||||
|
|
||||||
|
|
||||||
|
boolean allowDuplicateJMXDomains = true;
|
||||||
|
|
||||||
|
gcb.globalJmxStatistics().allowDuplicateDomains(allowDuplicateJMXDomains);
|
||||||
|
|
||||||
|
final DefaultCacheManager cacheManager = new DefaultCacheManager(gcb.build());
|
||||||
|
ConfigurationBuilder invalidationConfigBuilder = new ConfigurationBuilder();
|
||||||
|
Configuration invalidationCacheConfiguration = invalidationConfigBuilder.build();
|
||||||
|
cacheManager.defineConfiguration(InfinispanConnectionProvider.REALM_CACHE_NAME, invalidationCacheConfiguration);
|
||||||
|
|
||||||
|
ConfigurationBuilder counterConfigBuilder = new ConfigurationBuilder();
|
||||||
|
counterConfigBuilder.invocationBatching().enable();
|
||||||
|
counterConfigBuilder.transaction().transactionManagerLookup(new DummyTransactionManagerLookup());
|
||||||
|
counterConfigBuilder.transaction().lockingMode(LockingMode.PESSIMISTIC);
|
||||||
|
Configuration counterCacheConfiguration = counterConfigBuilder.build();
|
||||||
|
|
||||||
|
cacheManager.defineConfiguration("COUNTER_CACHE", counterCacheConfiguration);
|
||||||
|
return cacheManager;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -14,6 +14,7 @@ import org.infinispan.transaction.TransactionProtocol;
|
||||||
import org.infinispan.transaction.lookup.DummyTransactionManagerLookup;
|
import org.infinispan.transaction.lookup.DummyTransactionManagerLookup;
|
||||||
import org.infinispan.util.concurrent.IsolationLevel;
|
import org.infinispan.util.concurrent.IsolationLevel;
|
||||||
import org.junit.Assert;
|
import org.junit.Assert;
|
||||||
|
import org.junit.Ignore;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
import org.keycloak.connections.infinispan.InfinispanConnectionProvider;
|
import org.keycloak.connections.infinispan.InfinispanConnectionProvider;
|
||||||
|
|
||||||
|
@ -30,6 +31,7 @@ import java.util.concurrent.Executors;
|
||||||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||||
* @version $Revision: 1 $
|
* @version $Revision: 1 $
|
||||||
*/
|
*/
|
||||||
|
@Ignore
|
||||||
public class ConcurrencyVersioningTest {
|
public class ConcurrencyVersioningTest {
|
||||||
|
|
||||||
public static abstract class AbstractThread implements Runnable {
|
public static abstract class AbstractThread implements Runnable {
|
||||||
|
|
|
@ -633,8 +633,8 @@ public class ClientAdapter implements ClientModel {
|
||||||
roleEntity.setClient(entity);
|
roleEntity.setClient(entity);
|
||||||
roleEntity.setClientRole(true);
|
roleEntity.setClientRole(true);
|
||||||
roleEntity.setRealmId(realm.getId());
|
roleEntity.setRealmId(realm.getId());
|
||||||
|
//entity.getRoles().add(roleEntity);
|
||||||
em.persist(roleEntity);
|
em.persist(roleEntity);
|
||||||
entity.getRoles().add(roleEntity);
|
|
||||||
em.flush();
|
em.flush();
|
||||||
return new RoleAdapter(realm, em, roleEntity);
|
return new RoleAdapter(realm, em, roleEntity);
|
||||||
}
|
}
|
||||||
|
@ -650,7 +650,7 @@ public class ClientAdapter implements ClientModel {
|
||||||
RoleEntity role = RoleAdapter.toRoleEntity(roleModel, em);
|
RoleEntity role = RoleAdapter.toRoleEntity(roleModel, em);
|
||||||
if (!role.isClientRole()) return false;
|
if (!role.isClientRole()) return false;
|
||||||
|
|
||||||
entity.getRoles().remove(role);
|
//entity.getRoles().remove(role);
|
||||||
entity.getDefaultRoles().remove(role);
|
entity.getDefaultRoles().remove(role);
|
||||||
String compositeRoleTable = JpaUtils.getTableNameForNativeQuery("COMPOSITE_ROLE", em);
|
String compositeRoleTable = JpaUtils.getTableNameForNativeQuery("COMPOSITE_ROLE", em);
|
||||||
em.createNativeQuery("delete from " + compositeRoleTable + " where CHILD_ROLE = :role").setParameter("role", role).executeUpdate();
|
em.createNativeQuery("delete from " + compositeRoleTable + " where CHILD_ROLE = :role").setParameter("role", role).executeUpdate();
|
||||||
|
@ -667,12 +667,22 @@ public class ClientAdapter implements ClientModel {
|
||||||
@Override
|
@Override
|
||||||
public Set<RoleModel> getRoles() {
|
public Set<RoleModel> getRoles() {
|
||||||
Set<RoleModel> list = new HashSet<RoleModel>();
|
Set<RoleModel> list = new HashSet<RoleModel>();
|
||||||
|
/*
|
||||||
Collection<RoleEntity> roles = entity.getRoles();
|
Collection<RoleEntity> roles = entity.getRoles();
|
||||||
if (roles == null) return list;
|
if (roles == null) return list;
|
||||||
for (RoleEntity entity : roles) {
|
for (RoleEntity entity : roles) {
|
||||||
list.add(new RoleAdapter(realm, em, entity));
|
list.add(new RoleAdapter(realm, em, entity));
|
||||||
}
|
}
|
||||||
return list;
|
return list;
|
||||||
|
*/
|
||||||
|
TypedQuery<RoleEntity> query = em.createNamedQuery("getClientRoles", RoleEntity.class);
|
||||||
|
query.setParameter("client", entity);
|
||||||
|
List<RoleEntity> roles = query.getResultList();
|
||||||
|
for (RoleEntity roleEntity : roles) {
|
||||||
|
list.add(new RoleAdapter(realm, em, roleEntity));
|
||||||
|
}
|
||||||
|
return list;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -124,9 +124,18 @@ public class JpaRealmProvider implements RealmProvider {
|
||||||
.setParameter("realm", realm).executeUpdate();
|
.setParameter("realm", realm).executeUpdate();
|
||||||
num = em.createNamedQuery("deleteGroupsByRealm")
|
num = em.createNamedQuery("deleteGroupsByRealm")
|
||||||
.setParameter("realm", realm).executeUpdate();
|
.setParameter("realm", realm).executeUpdate();
|
||||||
|
|
||||||
|
TypedQuery<ClientEntity> query = em.createNamedQuery("getClientsByRealm", ClientEntity.class);
|
||||||
|
query.setParameter("realm", realm);
|
||||||
|
List<ClientEntity> clients = query.getResultList();
|
||||||
|
for (ClientEntity a : clients) {
|
||||||
|
adapter.removeClient(a.getId());
|
||||||
|
}
|
||||||
|
/*
|
||||||
for (ClientEntity a : new LinkedList<>(realm.getClients())) {
|
for (ClientEntity a : new LinkedList<>(realm.getClients())) {
|
||||||
adapter.removeClient(a.getId());
|
adapter.removeClient(a.getId());
|
||||||
}
|
}
|
||||||
|
*/
|
||||||
for (ClientTemplateEntity a : new LinkedList<>(realm.getClientTemplates())) {
|
for (ClientTemplateEntity a : new LinkedList<>(realm.getClientTemplates())) {
|
||||||
adapter.removeClientTemplate(a.getId());
|
adapter.removeClientTemplate(a.getId());
|
||||||
}
|
}
|
||||||
|
|
|
@ -729,12 +729,14 @@ public class RealmAdapter implements RealmModel {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public List<ClientModel> getClients() {
|
public List<ClientModel> getClients() {
|
||||||
List<ClientModel> list = new ArrayList<ClientModel>();
|
List<ClientModel> list = new LinkedList<>();
|
||||||
if (realm.getClients() == null) return list;
|
TypedQuery<ClientEntity> query = em.createNamedQuery("getClientsByRealm", ClientEntity.class);
|
||||||
for (ClientEntity entity : realm.getClients()) {
|
query.setParameter("realm", realm);
|
||||||
|
List<ClientEntity> clients = query.getResultList();
|
||||||
|
for (ClientEntity entity : clients) {
|
||||||
list.add(new ClientAdapter(this, em, session, entity));
|
list.add(new ClientAdapter(this, em, session, entity));
|
||||||
}
|
}
|
||||||
return list;
|
return list;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -753,7 +755,7 @@ public class RealmAdapter implements RealmModel {
|
||||||
entity.setEnabled(true);
|
entity.setEnabled(true);
|
||||||
entity.setStandardFlowEnabled(true);
|
entity.setStandardFlowEnabled(true);
|
||||||
entity.setRealm(realm);
|
entity.setRealm(realm);
|
||||||
realm.getClients().add(entity);
|
//realm.getClients().add(entity);
|
||||||
em.persist(entity);
|
em.persist(entity);
|
||||||
em.flush();
|
em.flush();
|
||||||
final ClientModel resource = new ClientAdapter(this, em, session, entity);
|
final ClientModel resource = new ClientAdapter(this, em, session, entity);
|
||||||
|
@ -779,6 +781,7 @@ public class RealmAdapter implements RealmModel {
|
||||||
client.removeRole(role);
|
client.removeRole(role);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
ClientEntity clientEntity = null;
|
ClientEntity clientEntity = null;
|
||||||
Iterator<ClientEntity> it = realm.getClients().iterator();
|
Iterator<ClientEntity> it = realm.getClients().iterator();
|
||||||
while (it.hasNext()) {
|
while (it.hasNext()) {
|
||||||
|
@ -794,11 +797,11 @@ public class RealmAdapter implements RealmModel {
|
||||||
clientEntity = a;
|
clientEntity = a;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (client == null) {
|
*/
|
||||||
return false;
|
ClientEntity clientEntity = em.find(ClientEntity.class, id);
|
||||||
}
|
if (clientEntity == null) return false;
|
||||||
em.remove(clientEntity);
|
|
||||||
em.createNamedQuery("deleteScopeMappingByClient").setParameter("client", clientEntity).executeUpdate();
|
em.createNamedQuery("deleteScopeMappingByClient").setParameter("client", clientEntity).executeUpdate();
|
||||||
|
em.remove(clientEntity);
|
||||||
em.flush();
|
em.flush();
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
|
|
|
@ -28,6 +28,8 @@ import javax.persistence.JoinColumn;
|
||||||
import javax.persistence.JoinTable;
|
import javax.persistence.JoinTable;
|
||||||
import javax.persistence.ManyToOne;
|
import javax.persistence.ManyToOne;
|
||||||
import javax.persistence.MapKeyColumn;
|
import javax.persistence.MapKeyColumn;
|
||||||
|
import javax.persistence.NamedQueries;
|
||||||
|
import javax.persistence.NamedQuery;
|
||||||
import javax.persistence.OneToMany;
|
import javax.persistence.OneToMany;
|
||||||
import javax.persistence.Table;
|
import javax.persistence.Table;
|
||||||
import javax.persistence.UniqueConstraint;
|
import javax.persistence.UniqueConstraint;
|
||||||
|
@ -44,6 +46,9 @@ import java.util.Set;
|
||||||
*/
|
*/
|
||||||
@Entity
|
@Entity
|
||||||
@Table(name="CLIENT", uniqueConstraints = {@UniqueConstraint(columnNames = {"REALM_ID", "CLIENT_ID"})})
|
@Table(name="CLIENT", uniqueConstraints = {@UniqueConstraint(columnNames = {"REALM_ID", "CLIENT_ID"})})
|
||||||
|
@NamedQueries({
|
||||||
|
@NamedQuery(name="getClientsByRealm", query="select client from ClientEntity client where client.realm = :realm"),
|
||||||
|
})
|
||||||
public class ClientEntity {
|
public class ClientEntity {
|
||||||
|
|
||||||
@Id
|
@Id
|
||||||
|
@ -146,8 +151,8 @@ public class ClientEntity {
|
||||||
@Column(name="NODE_REREG_TIMEOUT")
|
@Column(name="NODE_REREG_TIMEOUT")
|
||||||
private int nodeReRegistrationTimeout;
|
private int nodeReRegistrationTimeout;
|
||||||
|
|
||||||
@OneToMany(fetch = FetchType.EAGER, cascade ={CascadeType.REMOVE}, orphanRemoval = true, mappedBy = "client")
|
//@OneToMany(fetch = FetchType.LAZY, cascade ={CascadeType.REMOVE}, mappedBy = "client")
|
||||||
Collection<RoleEntity> roles = new ArrayList<RoleEntity>();
|
//Collection<RoleEntity> roles = new ArrayList<RoleEntity>();
|
||||||
|
|
||||||
@OneToMany(fetch = FetchType.LAZY, cascade ={CascadeType.REMOVE}, orphanRemoval = true)
|
@OneToMany(fetch = FetchType.LAZY, cascade ={CascadeType.REMOVE}, orphanRemoval = true)
|
||||||
@JoinTable(name="CLIENT_DEFAULT_ROLES", joinColumns = { @JoinColumn(name="CLIENT_ID")}, inverseJoinColumns = { @JoinColumn(name="ROLE_ID")})
|
@JoinTable(name="CLIENT_DEFAULT_ROLES", joinColumns = { @JoinColumn(name="CLIENT_ID")}, inverseJoinColumns = { @JoinColumn(name="ROLE_ID")})
|
||||||
|
@ -343,6 +348,7 @@ public class ClientEntity {
|
||||||
this.managementUrl = managementUrl;
|
this.managementUrl = managementUrl;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
public Collection<RoleEntity> getRoles() {
|
public Collection<RoleEntity> getRoles() {
|
||||||
return roles;
|
return roles;
|
||||||
}
|
}
|
||||||
|
@ -350,6 +356,7 @@ public class ClientEntity {
|
||||||
public void setRoles(Collection<RoleEntity> roles) {
|
public void setRoles(Collection<RoleEntity> roles) {
|
||||||
this.roles = roles;
|
this.roles = roles;
|
||||||
}
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
public Collection<RoleEntity> getDefaultRoles() {
|
public Collection<RoleEntity> getDefaultRoles() {
|
||||||
return defaultRoles;
|
return defaultRoles;
|
||||||
|
|
|
@ -145,16 +145,16 @@ public class RealmEntity {
|
||||||
@OneToMany(cascade ={CascadeType.REMOVE}, orphanRemoval = true, mappedBy = "realm")
|
@OneToMany(cascade ={CascadeType.REMOVE}, orphanRemoval = true, mappedBy = "realm")
|
||||||
Collection<UserFederationMapperEntity> userFederationMappers = new ArrayList<UserFederationMapperEntity>();
|
Collection<UserFederationMapperEntity> userFederationMappers = new ArrayList<UserFederationMapperEntity>();
|
||||||
|
|
||||||
@OneToMany(fetch = FetchType.LAZY, cascade ={CascadeType.REMOVE}, orphanRemoval = true, mappedBy="realm")
|
//@OneToMany(fetch = FetchType.LAZY, cascade ={CascadeType.REMOVE}, orphanRemoval = true, mappedBy="realm")
|
||||||
//@OneToMany(fetch = FetchType.LAZY, cascade ={CascadeType.REMOVE}, orphanRemoval = true)
|
//@OneToMany(fetch = FetchType.LAZY, cascade ={CascadeType.REMOVE}, orphanRemoval = true)
|
||||||
//@JoinTable(name="REALM_CLIENT", joinColumns={ @JoinColumn(name="REALM_ID") }, inverseJoinColumns={ @JoinColumn(name="CLIENT_ID") })
|
//@JoinTable(name="REALM_CLIENT", joinColumns={ @JoinColumn(name="REALM_ID") }, inverseJoinColumns={ @JoinColumn(name="CLIENT_ID") })
|
||||||
Collection<ClientEntity> clients = new ArrayList<>();
|
//Collection<ClientEntity> clients = new ArrayList<>();
|
||||||
|
|
||||||
@OneToMany(fetch = FetchType.LAZY, cascade ={CascadeType.REMOVE}, orphanRemoval = true)
|
@OneToMany(fetch = FetchType.LAZY, cascade ={CascadeType.REMOVE}, orphanRemoval = true)
|
||||||
@JoinTable(name="REALM_CLIENT_TEMPLATE", joinColumns={ @JoinColumn(name="REALM_ID") }, inverseJoinColumns={ @JoinColumn(name="CLIENT_TEMPLATE_ID") })
|
@JoinTable(name="REALM_CLIENT_TEMPLATE", joinColumns={ @JoinColumn(name="REALM_ID") }, inverseJoinColumns={ @JoinColumn(name="CLIENT_TEMPLATE_ID") })
|
||||||
Collection<ClientTemplateEntity> clientTemplates = new ArrayList<>();
|
Collection<ClientTemplateEntity> clientTemplates = new ArrayList<>();
|
||||||
|
|
||||||
@OneToMany(fetch = FetchType.LAZY, cascade ={CascadeType.REMOVE}, orphanRemoval = true, mappedBy = "realm")
|
@OneToMany(fetch = FetchType.LAZY, cascade ={CascadeType.REMOVE}, mappedBy = "realm")
|
||||||
Collection<RoleEntity> roles = new ArrayList<RoleEntity>();
|
Collection<RoleEntity> roles = new ArrayList<RoleEntity>();
|
||||||
|
|
||||||
@ElementCollection
|
@ElementCollection
|
||||||
|
@ -422,7 +422,7 @@ public class RealmEntity {
|
||||||
public void setRequiredCredentials(Collection<RequiredCredentialEntity> requiredCredentials) {
|
public void setRequiredCredentials(Collection<RequiredCredentialEntity> requiredCredentials) {
|
||||||
this.requiredCredentials = requiredCredentials;
|
this.requiredCredentials = requiredCredentials;
|
||||||
}
|
}
|
||||||
|
/*
|
||||||
public Collection<ClientEntity> getClients() {
|
public Collection<ClientEntity> getClients() {
|
||||||
return clients;
|
return clients;
|
||||||
}
|
}
|
||||||
|
@ -430,6 +430,7 @@ public class RealmEntity {
|
||||||
public void setClients(Collection<ClientEntity> clients) {
|
public void setClients(Collection<ClientEntity> clients) {
|
||||||
this.clients = clients;
|
this.clients = clients;
|
||||||
}
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
public Collection<RoleEntity> getRoles() {
|
public Collection<RoleEntity> getRoles() {
|
||||||
return roles;
|
return roles;
|
||||||
|
|
|
@ -41,6 +41,7 @@ import java.util.Collection;
|
||||||
@UniqueConstraint(columnNames = { "NAME", "CLIENT_REALM_CONSTRAINT" })
|
@UniqueConstraint(columnNames = { "NAME", "CLIENT_REALM_CONSTRAINT" })
|
||||||
})
|
})
|
||||||
@NamedQueries({
|
@NamedQueries({
|
||||||
|
@NamedQuery(name="getClientRoles", query="select role from RoleEntity role where role.client = :client"),
|
||||||
@NamedQuery(name="getClientRoleByName", query="select role from RoleEntity role where role.name = :name and role.client = :client"),
|
@NamedQuery(name="getClientRoleByName", query="select role from RoleEntity role where role.name = :name and role.client = :client"),
|
||||||
@NamedQuery(name="getRealmRoleByName", query="select role from RoleEntity role where role.clientRole = false and role.name = :name and role.realm = :realm")
|
@NamedQuery(name="getRealmRoleByName", query="select role from RoleEntity role where role.clientRole = false and role.name = :name and role.realm = :realm")
|
||||||
})
|
})
|
||||||
|
|
|
@ -26,4 +26,5 @@ public interface KeycloakTransactionManager extends KeycloakTransaction {
|
||||||
void enlist(KeycloakTransaction transaction);
|
void enlist(KeycloakTransaction transaction);
|
||||||
void enlistAfterCompletion(KeycloakTransaction transaction);
|
void enlistAfterCompletion(KeycloakTransaction transaction);
|
||||||
|
|
||||||
|
void enlistPrepare(KeycloakTransaction transaction);
|
||||||
}
|
}
|
||||||
|
|
|
@ -30,6 +30,7 @@ public class DefaultKeycloakTransactionManager implements KeycloakTransactionMan
|
||||||
|
|
||||||
public static final ServicesLogger logger = ServicesLogger.ROOT_LOGGER;
|
public static final ServicesLogger logger = ServicesLogger.ROOT_LOGGER;
|
||||||
|
|
||||||
|
private List<KeycloakTransaction> prepare = new LinkedList<KeycloakTransaction>();
|
||||||
private List<KeycloakTransaction> transactions = new LinkedList<KeycloakTransaction>();
|
private List<KeycloakTransaction> transactions = new LinkedList<KeycloakTransaction>();
|
||||||
private List<KeycloakTransaction> afterCompletion = new LinkedList<KeycloakTransaction>();
|
private List<KeycloakTransaction> afterCompletion = new LinkedList<KeycloakTransaction>();
|
||||||
private boolean active;
|
private boolean active;
|
||||||
|
@ -53,6 +54,15 @@ public class DefaultKeycloakTransactionManager implements KeycloakTransactionMan
|
||||||
afterCompletion.add(transaction);
|
afterCompletion.add(transaction);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void enlistPrepare(KeycloakTransaction transaction) {
|
||||||
|
if (active && !transaction.isActive()) {
|
||||||
|
transaction.begin();
|
||||||
|
}
|
||||||
|
|
||||||
|
prepare.add(transaction);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void begin() {
|
public void begin() {
|
||||||
if (active) {
|
if (active) {
|
||||||
|
@ -69,6 +79,17 @@ public class DefaultKeycloakTransactionManager implements KeycloakTransactionMan
|
||||||
@Override
|
@Override
|
||||||
public void commit() {
|
public void commit() {
|
||||||
RuntimeException exception = null;
|
RuntimeException exception = null;
|
||||||
|
for (KeycloakTransaction tx : prepare) {
|
||||||
|
try {
|
||||||
|
tx.commit();
|
||||||
|
} catch (RuntimeException e) {
|
||||||
|
exception = exception == null ? e : exception;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (exception != null) {
|
||||||
|
rollback(exception);
|
||||||
|
return;
|
||||||
|
}
|
||||||
for (KeycloakTransaction tx : transactions) {
|
for (KeycloakTransaction tx : transactions) {
|
||||||
try {
|
try {
|
||||||
tx.commit();
|
tx.commit();
|
||||||
|
@ -105,6 +126,10 @@ public class DefaultKeycloakTransactionManager implements KeycloakTransactionMan
|
||||||
@Override
|
@Override
|
||||||
public void rollback() {
|
public void rollback() {
|
||||||
RuntimeException exception = null;
|
RuntimeException exception = null;
|
||||||
|
rollback(exception);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void rollback(RuntimeException exception) {
|
||||||
for (KeycloakTransaction tx : transactions) {
|
for (KeycloakTransaction tx : transactions) {
|
||||||
try {
|
try {
|
||||||
tx.rollback();
|
tx.rollback();
|
||||||
|
|
|
@ -18,6 +18,7 @@
|
||||||
package org.keycloak.testsuite.admin;
|
package org.keycloak.testsuite.admin;
|
||||||
|
|
||||||
import org.jboss.logging.Logger;
|
import org.jboss.logging.Logger;
|
||||||
|
import org.junit.Assert;
|
||||||
import org.junit.Ignore;
|
import org.junit.Ignore;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
import org.keycloak.admin.client.Keycloak;
|
import org.keycloak.admin.client.Keycloak;
|
||||||
|
@ -30,6 +31,8 @@ import javax.ws.rs.core.Response;
|
||||||
import java.util.LinkedList;
|
import java.util.LinkedList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.concurrent.CountDownLatch;
|
import java.util.concurrent.CountDownLatch;
|
||||||
|
import java.util.concurrent.ExecutorService;
|
||||||
|
import java.util.concurrent.Executors;
|
||||||
import java.util.concurrent.atomic.AtomicReference;
|
import java.util.concurrent.atomic.AtomicReference;
|
||||||
import java.util.concurrent.locks.Lock;
|
import java.util.concurrent.locks.Lock;
|
||||||
import java.util.concurrent.locks.ReentrantLock;
|
import java.util.concurrent.locks.ReentrantLock;
|
||||||
|
@ -44,14 +47,51 @@ public class ConcurrencyTest extends AbstractClientTest {
|
||||||
|
|
||||||
private static final Logger log = Logger.getLogger(ConcurrencyTest.class);
|
private static final Logger log = Logger.getLogger(ConcurrencyTest.class);
|
||||||
|
|
||||||
private static final int DEFAULT_THREADS = 5;
|
private static final int DEFAULT_THREADS = 10;
|
||||||
private static final int DEFAULT_ITERATIONS = 30;
|
private static final int DEFAULT_ITERATIONS = 100;
|
||||||
|
|
||||||
// If enabled only one request is allowed at the time. Useful for checking that test is working.
|
// If enabled only one request is allowed at the time. Useful for checking that test is working.
|
||||||
private static final boolean SYNCHRONIZED = false;
|
private static final boolean SYNCHRONIZED = false;
|
||||||
|
|
||||||
|
boolean passedCreateClient = false;
|
||||||
|
boolean passedCreateRole = false;
|
||||||
|
|
||||||
|
//@Test
|
||||||
|
public void testAllConcurrently() throws Throwable {
|
||||||
|
Thread client = new Thread(new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
try {
|
||||||
|
createClient();
|
||||||
|
passedCreateClient = true;
|
||||||
|
} catch (Throwable throwable) {
|
||||||
|
throw new RuntimeException(throwable);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
Thread role = new Thread(new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
try {
|
||||||
|
createRole();
|
||||||
|
passedCreateRole = true;
|
||||||
|
} catch (Throwable throwable) {
|
||||||
|
throw new RuntimeException(throwable);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
client.start();
|
||||||
|
role.start();
|
||||||
|
client.join();
|
||||||
|
role.join();
|
||||||
|
Assert.assertTrue(passedCreateClient);
|
||||||
|
Assert.assertTrue(passedCreateRole);
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void createClient() throws Throwable {
|
public void createClient() throws Throwable {
|
||||||
|
long start = System.currentTimeMillis();
|
||||||
run(new KeycloakRunnable() {
|
run(new KeycloakRunnable() {
|
||||||
@Override
|
@Override
|
||||||
public void run(Keycloak keycloak, RealmResource realm, int threadNum, int iterationNum) {
|
public void run(Keycloak keycloak, RealmResource realm, int threadNum, int iterationNum) {
|
||||||
|
@ -75,10 +115,14 @@ public class ConcurrencyTest extends AbstractClientTest {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
long end = System.currentTimeMillis() - start;
|
||||||
|
System.out.println("createClient took " + end);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void createRole() throws Throwable {
|
public void createRole() throws Throwable {
|
||||||
|
long start = System.currentTimeMillis();
|
||||||
run(new KeycloakRunnable() {
|
run(new KeycloakRunnable() {
|
||||||
@Override
|
@Override
|
||||||
public void run(Keycloak keycloak, RealmResource realm, int threadNum, int iterationNum) {
|
public void run(Keycloak keycloak, RealmResource realm, int threadNum, int iterationNum) {
|
||||||
|
@ -88,10 +132,14 @@ public class ConcurrencyTest extends AbstractClientTest {
|
||||||
assertNotNull(realm.roles().get(name).toRepresentation());
|
assertNotNull(realm.roles().get(name).toRepresentation());
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
long end = System.currentTimeMillis() - start;
|
||||||
|
System.out.println("createRole took " + end);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void createClientRole() throws Throwable {
|
public void createClientRole() throws Throwable {
|
||||||
|
long start = System.currentTimeMillis();
|
||||||
ClientRepresentation c = new ClientRepresentation();
|
ClientRepresentation c = new ClientRepresentation();
|
||||||
c.setClientId("client");
|
c.setClientId("client");
|
||||||
Response response = realm.clients().create(c);
|
Response response = realm.clients().create(c);
|
||||||
|
@ -110,6 +158,9 @@ public class ConcurrencyTest extends AbstractClientTest {
|
||||||
assertNotNull(client.roles().get(name).toRepresentation());
|
assertNotNull(client.roles().get(name).toRepresentation());
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
long end = System.currentTimeMillis() - start;
|
||||||
|
System.out.println("createClientRole took " + end);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void run(final KeycloakRunnable runnable) throws Throwable {
|
private void run(final KeycloakRunnable runnable) throws Throwable {
|
||||||
|
|
|
@ -33,7 +33,8 @@
|
||||||
},
|
},
|
||||||
|
|
||||||
"realmCache": {
|
"realmCache": {
|
||||||
"infinispan" : {
|
"provider": "infinispan-revisioned",
|
||||||
|
"infinispan-locking" : {
|
||||||
"enabled": true
|
"enabled": true
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -85,7 +86,8 @@
|
||||||
},
|
},
|
||||||
|
|
||||||
"connectionsInfinispan": {
|
"connectionsInfinispan": {
|
||||||
"default": {
|
"provider": "revisioned",
|
||||||
|
"locking": {
|
||||||
"clustered": "${keycloak.connectionsInfinispan.clustered:false}",
|
"clustered": "${keycloak.connectionsInfinispan.clustered:false}",
|
||||||
"async": "${keycloak.connectionsInfinispan.async:true}",
|
"async": "${keycloak.connectionsInfinispan.async:true}",
|
||||||
"sessionsOwners": "${keycloak.connectionsInfinispan.sessionsOwners:2}"
|
"sessionsOwners": "${keycloak.connectionsInfinispan.sessionsOwners:2}"
|
||||||
|
|
Loading…
Reference in a new issue