Merge pull request #2215 from patriot1burke/master

concurrency, some JPA cleanup too
This commit is contained in:
Bill Burke 2016-02-11 09:41:37 -05:00
commit 67ce3fdbd2
64 changed files with 3960 additions and 181 deletions

View file

@ -89,6 +89,9 @@
<local-cache name="sessions"/>
<local-cache name="offlineSessions"/>
<local-cache name="loginFailures"/>
<local-cache name="realmVersions">
<transaction mode="BATCH" locking="PESSIMISTIC"/>
</local-cache>
</cache-container>
<xsl:apply-templates select="node()|@*"/>
</xsl:copy>

View file

@ -28,12 +28,6 @@
}
},
"realmCache": {
"infinispan" : {
"enabled": true
}
},
"userSessionPersister": {
"provider": "jpa"
},
@ -67,8 +61,16 @@
}
},
"realmCache": {
"provider": "infinispan-locking",
"infinispan-locking" : {
"enabled": true
}
},
"connectionsInfinispan": {
"default" : {
"provider": "locking",
"locking": {
"cacheContainer" : "java:comp/env/infinispan/Keycloak"
}
}

View file

@ -37,11 +37,11 @@ public class DefaultInfinispanConnectionProviderFactory implements InfinispanCon
protected static final Logger logger = Logger.getLogger(DefaultInfinispanConnectionProviderFactory.class);
private Config.Scope config;
protected Config.Scope config;
private EmbeddedCacheManager cacheManager;
protected EmbeddedCacheManager cacheManager;
private boolean containerManaged;
protected boolean containerManaged;
@Override
public InfinispanConnectionProvider create(KeycloakSession session) {
@ -73,7 +73,7 @@ public class DefaultInfinispanConnectionProviderFactory implements InfinispanCon
}
private void lazyInit() {
protected void lazyInit() {
if (cacheManager == null) {
synchronized (this) {
if (cacheManager == null) {
@ -88,7 +88,7 @@ public class DefaultInfinispanConnectionProviderFactory implements InfinispanCon
}
}
private void initContainerManaged(String cacheContainerLookup) {
protected void initContainerManaged(String cacheContainerLookup) {
try {
cacheManager = (EmbeddedCacheManager) new InitialContext().lookup(cacheContainerLookup);
containerManaged = true;
@ -99,7 +99,7 @@ public class DefaultInfinispanConnectionProviderFactory implements InfinispanCon
}
}
private void initEmbedded() {
protected void initEmbedded() {
GlobalConfigurationBuilder gcb = new GlobalConfigurationBuilder();
boolean clustered = config.getBoolean("clustered", false);

View file

@ -546,7 +546,9 @@ public class ClientAdapter implements ClientModel {
public RoleModel getRole(String name) {
if (updated != null) return updated.getRole(name);
String id = cached.getRoles().get(name);
if (id == null) return null;
if (id == null) {
return null;
}
return cacheSession.getRoleById(id, cachedRealm);
}

View file

@ -0,0 +1,10 @@
package org.keycloak.models.cache.infinispan.counter;
/**
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
* @version $Revision: 1 $
*/
public interface Revisioned {
Long getRevision();
void setRevision(Long revision);
}

View file

@ -0,0 +1,401 @@
/*
* 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.counter;
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.RealmCache;
import org.keycloak.models.cache.entities.CachedClient;
import org.keycloak.models.cache.entities.CachedClientRole;
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.CachedRealmRole;
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.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 RevisionedCacheRealmProvider implements CacheRealmProvider {
protected static final Logger logger = Logger.getLogger(RevisionedCacheRealmProvider.class);
protected RevisionedRealmCache 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 RevisionedCacheRealmProvider(RevisionedRealmCache cache, KeycloakSession session) {
this.cache = cache;
this.session = session;
session.getTransaction().enlistAfterCompletion(getTransaction());
}
@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 getTransaction() {
return new KeycloakTransaction() {
@Override
public void begin() {
transactionActive = true;
}
@Override
public void commit() {
if (delegate == null) return;
if (clearAll) {
cache.clear();
}
runInvalidations();
transactionActive = false;
}
@Override
public void rollback() {
setRollbackOnly = true;
runInvalidations();
transactionActive = false;
}
@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 = 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);
logger.tracev("try caching realm: {0} {1}", cached.getName(), loaded);
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) {
Long loaded = UpdateCounter.current();
RealmModel model = getDelegate().getRealmByName(name);
if (model == null) return null;
if (realmInvalidations.contains(model.getId())) return model;
cached = new RevisionedCachedRealm(loaded, cache, this, model);
logger.tracev("try caching realm: {0}", cached.getName());
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 = UpdateCounter.current();
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 = 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);
} 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) {
logger.tracev("client by id cache hit: {0}", cached.getClientId());
}
if (cached == null) {
Long 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);
} 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 = 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);
} 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;
}
}

View 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.counter;
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 RevisionedCacheRealmProviderFactory implements CacheRealmProviderFactory {
private static final Logger log = Logger.getLogger(RevisionedCacheRealmProviderFactory.class);
protected volatile RevisionedRealmCache realmCache;
protected final ConcurrentHashMap<String, String> realmLookup = new ConcurrentHashMap<>();
@Override
public CacheRealmProvider create(KeycloakSession session) {
lazyInit(session);
return new RevisionedCacheRealmProvider(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(RevisionedConnectionProviderFactory.VERSION_CACHE_NAME);
cache.addListener(new CacheListener());
realmCache = new RevisionedRealmCache(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-revisioned";
}
@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());
}
}
}
}

View file

@ -0,0 +1,47 @@
/*
* 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.counter;
import org.infinispan.configuration.cache.Configuration;
import org.infinispan.configuration.cache.ConfigurationBuilder;
import org.jboss.logging.Logger;
import org.keycloak.connections.infinispan.DefaultInfinispanConnectionProviderFactory;
/**
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
*/
public class RevisionedConnectionProviderFactory extends DefaultInfinispanConnectionProviderFactory {
public static final String VERSION_CACHE_NAME = "realmVersions";
protected static final Logger logger = Logger.getLogger(RevisionedConnectionProviderFactory.class);
@Override
public String getId() {
return "revisioned";
}
protected void initEmbedded() {
super.initEmbedded();
ConfigurationBuilder counterConfigBuilder = new ConfigurationBuilder();
Configuration counterCacheConfiguration = counterConfigBuilder.build();
cacheManager.defineConfiguration(VERSION_CACHE_NAME, counterCacheConfiguration);
}
}

View file

@ -0,0 +1,255 @@
/*
* 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.counter;
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 java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicLong;
/**
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
*/
public class RevisionedRealmCache implements RealmCache {
protected static final Logger logger = Logger.getLogger(RevisionedRealmCache.class);
protected final Cache<String, Long> revisions;
protected final Cache<String, Object> cache;
protected final ConcurrentHashMap<String, String> realmLookup;
public RevisionedRealmCache(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;
}
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) {
logger.tracev("rev was null in addRevisioned, adding one");
rev = UpdateCounter.next();
revisions.put(id, rev);
return;
}
cache.putForExternalRead(id, object);
}
@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());
realmLookup.remove(realm.getName());
}
@Override
public void invalidateCachedRealmById(String id) {
CachedRealm cached = (CachedRealm) invalidateObject(id);
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);
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());
}
@Override
public void addCachedClient(CachedClient app) {
logger.tracev("Adding application {0}", app.getId());
addRevisioned(app.getId(), (Revisioned) app);
}
@Override
public void invalidateCachedApplicationById(String id) {
CachedClient client = (CachedClient)invalidateObject(id);
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());
}
@Override
public void addCachedGroup(CachedGroup role) {
logger.tracev("Adding group {0}", role.getId());
addRevisioned(role.getId(), (Revisioned) role);
}
@Override
public void invalidateCachedGroupById(String id) {
logger.tracev("Removing group {0}", id);
invalidateObject(id);
}
@Override
public void invalidateGroupById(String id) {
logger.tracev("Removing group {0}", id);
invalidateObject(id);
}
@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());
}
@Override
public void invalidateRoleById(String id) {
logger.tracev("Removing role {0}", id);
invalidateObject(id);
}
@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);
}
@Override
public void invalidateCachedRoleById(String id) {
logger.tracev("Removing role {0}", id);
invalidateObject(id);
}
@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());
}
@Override
public void addCachedClientTemplate(CachedClientTemplate app) {
logger.tracev("Adding client template {0}", app.getId());
addRevisioned(app.getId(), (Revisioned) app);
}
@Override
public void invalidateCachedClientTemplateById(String id) {
logger.tracev("Removing client template {0}", id);
invalidateObject(id);
}
@Override
public void evictCachedClientTemplateById(String id) {
logger.tracev("Evicting client template {0}", id);
cache.evict(id);
}
}

View 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();
}
}

View file

@ -0,0 +1,41 @@
package org.keycloak.models.cache.infinispan.counter.entities;
import org.keycloak.models.ClientModel;
import org.keycloak.models.RealmModel;
import org.keycloak.models.RealmProvider;
import org.keycloak.models.RoleModel;
import org.keycloak.models.cache.RealmCache;
import org.keycloak.models.cache.entities.CachedClient;
import org.keycloak.models.cache.entities.CachedClientRole;
import org.keycloak.models.cache.infinispan.counter.Revisioned;
/**
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
* @version $Revision: 1 $
*/
public class RevisionedCachedClient extends CachedClient implements Revisioned {
public RevisionedCachedClient(Long revision, RealmCache cache, RealmProvider delegate, RealmModel realm, ClientModel model) {
super(cache, delegate, realm, model);
this.revision = revision;
}
private Long revision;
@Override
public Long getRevision() {
return revision;
}
@Override
public void setRevision(Long revision) {
this.revision = revision;
}
@Override
protected void cacheRoles(RealmCache cache, RealmModel realm, ClientModel model) {
for (RoleModel role : model.getRoles()) {
roles.put(role.getName(), role.getId());
}
}
}

View file

@ -0,0 +1,32 @@
package org.keycloak.models.cache.infinispan.counter.entities;
import org.keycloak.models.RealmModel;
import org.keycloak.models.RoleModel;
import org.keycloak.models.cache.entities.CachedClientRole;
import org.keycloak.models.cache.infinispan.counter.Revisioned;
/**
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
* @version $Revision: 1 $
*/
public class RevisionedCachedClientRole extends CachedClientRole implements Revisioned {
public RevisionedCachedClientRole(Long revision, String idClient, RoleModel model, RealmModel realm) {
super(idClient, model, realm);
this.revision = revision;
}
private Long revision;
@Override
public Long getRevision() {
return revision;
}
@Override
public void setRevision(Long revision) {
this.revision = revision;
}
}

View file

@ -0,0 +1,34 @@
package org.keycloak.models.cache.infinispan.counter.entities;
import org.keycloak.models.ClientTemplateModel;
import org.keycloak.models.RealmModel;
import org.keycloak.models.RealmProvider;
import org.keycloak.models.cache.RealmCache;
import org.keycloak.models.cache.entities.CachedClientTemplate;
import org.keycloak.models.cache.infinispan.counter.Revisioned;
/**
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
* @version $Revision: 1 $
*/
public class RevisionedCachedClientTemplate extends CachedClientTemplate implements Revisioned {
public RevisionedCachedClientTemplate(Long revision, RealmCache cache, RealmProvider delegate, RealmModel realm, ClientTemplateModel model) {
super(cache, delegate, realm, model);
this.revision = revision;
}
private Long revision;
@Override
public Long getRevision() {
return revision;
}
@Override
public void setRevision(Long revision) {
this.revision = revision;
}
}

View file

@ -0,0 +1,31 @@
package org.keycloak.models.cache.infinispan.counter.entities;
import org.keycloak.models.GroupModel;
import org.keycloak.models.RealmModel;
import org.keycloak.models.cache.entities.CachedGroup;
import org.keycloak.models.cache.infinispan.counter.Revisioned;
/**
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
* @version $Revision: 1 $
*/
public class RevisionedCachedGroup extends CachedGroup implements Revisioned {
public RevisionedCachedGroup(Long revision, RealmModel realm, GroupModel group) {
super(realm, group);
this.revision = revision;
}
private Long revision;
@Override
public Long getRevision() {
return revision;
}
@Override
public void setRevision(Long revision) {
this.revision = revision;
}
}

View file

@ -0,0 +1,59 @@
package org.keycloak.models.cache.infinispan.counter.entities;
import org.keycloak.models.ClientModel;
import org.keycloak.models.ClientTemplateModel;
import org.keycloak.models.RealmModel;
import org.keycloak.models.RealmProvider;
import org.keycloak.models.RoleModel;
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.CachedRealm;
import org.keycloak.models.cache.entities.CachedRealmRole;
import org.keycloak.models.cache.entities.CachedRole;
import org.keycloak.models.cache.infinispan.counter.Revisioned;
/**
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
* @version $Revision: 1 $
*/
public class RevisionedCachedRealm extends CachedRealm implements Revisioned {
public RevisionedCachedRealm(Long revision, RealmCache cache, RealmProvider delegate, RealmModel model) {
super(cache, delegate, model);
this.revision = revision;
}
private Long revision;
@Override
public Long getRevision() {
return revision;
}
@Override
public void setRevision(Long revision) {
this.revision = revision;
}
@Override
protected void cacheClientTemplates(RealmCache cache, RealmProvider delegate, RealmModel model) {
for (ClientTemplateModel template : model.getClientTemplates()) {
clientTemplates.add(template.getId());
}
}
@Override
protected void cacheClients(RealmCache cache, RealmProvider delegate, RealmModel model) {
for (ClientModel client : model.getClients()) {
clients.put(client.getClientId(), client.getId());
}
}
@Override
protected void cacheRealmRoles(RealmCache cache, RealmModel model) {
for (RoleModel role : model.getRoles()) {
realmRoles.put(role.getName(), role.getId());
}
}
}

View file

@ -0,0 +1,31 @@
package org.keycloak.models.cache.infinispan.counter.entities;
import org.keycloak.models.RealmModel;
import org.keycloak.models.RoleModel;
import org.keycloak.models.cache.entities.CachedRealmRole;
import org.keycloak.models.cache.infinispan.counter.Revisioned;
/**
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
* @version $Revision: 1 $
*/
public class RevisionedCachedRealmRole extends CachedRealmRole implements Revisioned {
public RevisionedCachedRealmRole(Long revision, RoleModel model, RealmModel realm) {
super(model, realm);
this.revision = revision;
}
private Long revision;
@Override
public Long getRevision() {
return revision;
}
@Override
public void setRevision(Long revision) {
this.revision = revision;
}
}

View file

@ -0,0 +1,30 @@
package org.keycloak.models.cache.infinispan.counter.entities;
import org.keycloak.models.RealmModel;
import org.keycloak.models.UserModel;
import org.keycloak.models.cache.entities.CachedUser;
import org.keycloak.models.cache.infinispan.counter.Revisioned;
/**
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
* @version $Revision: 1 $
*/
public class RevisionedCachedUser extends CachedUser implements Revisioned {
public RevisionedCachedUser(Long revision, RealmModel realm, UserModel user) {
super(realm, user);
this.revision = revision;
}
private Long revision;
@Override
public Long getRevision() {
return revision;
}
@Override
public void setRevision(Long revision) {
this.revision = revision;
}
}

View 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;
}
}

View 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.VERSION_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());
}
}
}
}

View file

@ -0,0 +1,54 @@
/*
* 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.TransactionMode;
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 VERSION_CACHE_NAME = "realmVersions";
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()
.transaction().transactionMode(TransactionMode.TRANSACTIONAL);
counterConfigBuilder.transaction().transactionManagerLookup(new DummyTransactionManagerLookup());
counterConfigBuilder.transaction().lockingMode(LockingMode.PESSIMISTIC);
Configuration counterCacheConfiguration = counterConfigBuilder.build();
cacheManager.defineConfiguration(VERSION_CACHE_NAME, counterCacheConfiguration);
}
}

View 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);
}
}

View file

@ -0,0 +1,160 @@
/*
* 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.skewed;
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 RepeatableReadWriteSkewCacheRealmProviderFactory implements CacheRealmProviderFactory {
private static final Logger log = Logger.getLogger(RepeatableReadWriteSkewCacheRealmProviderFactory.class);
protected volatile RepeatableReadWriteSkewRealmCache realmCache;
protected final ConcurrentHashMap<String, String> realmLookup = new ConcurrentHashMap<>();
@Override
public CacheRealmProvider create(KeycloakSession session) {
lazyInit(session);
return new RepeatableReadWriteSkewRealmCacheProvider(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.addListener(new CacheListener());
realmCache = new RepeatableReadWriteSkewRealmCache(cache, realmLookup);
}
}
}
}
@Override
public void init(Config.Scope config) {
}
@Override
public void postInit(KeycloakSessionFactory factory) {
}
@Override
public void close() {
}
@Override
public String getId() {
return "infinispan-versioned";
}
@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());
}
}
}
}

View file

@ -0,0 +1,164 @@
/*
* 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.skewed;
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.lookup.DummyTransactionManagerLookup;
import org.infinispan.util.concurrent.IsolationLevel;
import org.jboss.logging.Logger;
import org.keycloak.Config;
import org.keycloak.connections.infinispan.DefaultInfinispanConnectionProvider;
import org.keycloak.connections.infinispan.InfinispanConnectionProvider;
import org.keycloak.connections.infinispan.InfinispanConnectionProviderFactory;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.KeycloakSessionFactory;
import javax.naming.InitialContext;
/**
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
*/
public class RepeatableReadWriteSkewConnectionProviderFactory implements InfinispanConnectionProviderFactory {
protected static final Logger logger = Logger.getLogger(RepeatableReadWriteSkewConnectionProviderFactory.class);
private Config.Scope config;
private EmbeddedCacheManager cacheManager;
private boolean containerManaged;
@Override
public InfinispanConnectionProvider create(KeycloakSession session) {
lazyInit();
return new DefaultInfinispanConnectionProvider(cacheManager);
}
@Override
public void close() {
if (cacheManager != null && !containerManaged) {
cacheManager.stop();
}
cacheManager = null;
}
@Override
public String getId() {
return "versioned";
}
@Override
public void init(Config.Scope config) {
this.config = config;
}
@Override
public void postInit(KeycloakSessionFactory factory) {
}
private void lazyInit() {
if (cacheManager == null) {
synchronized (this) {
if (cacheManager == null) {
String cacheContainer = config.get("cacheContainer");
if (cacheContainer != null) {
initContainerManaged(cacheContainer);
} else {
initEmbedded();
}
}
}
}
}
private void initContainerManaged(String cacheContainerLookup) {
try {
cacheManager = (EmbeddedCacheManager) new InitialContext().lookup(cacheContainerLookup);
containerManaged = true;
logger.debugv("Using container managed Infinispan cache container, lookup={1}", cacheContainerLookup);
} catch (Exception e) {
throw new RuntimeException("Failed to retrieve cache container", e);
}
}
private void initEmbedded() {
GlobalConfigurationBuilder gcb = new GlobalConfigurationBuilder();
boolean clustered = config.getBoolean("clustered", false);
boolean async = config.getBoolean("async", true);
boolean allowDuplicateJMXDomains = config.getBoolean("allowDuplicateJMXDomains", true);
if (clustered) {
gcb.transport().defaultTransport();
}
gcb.globalJmxStatistics().allowDuplicateDomains(allowDuplicateJMXDomains);
cacheManager = new DefaultCacheManager(gcb.build());
containerManaged = false;
logger.debug("Started embedded Infinispan cache container");
ConfigurationBuilder invalidationConfigBuilder = new ConfigurationBuilder();
if (clustered) {
invalidationConfigBuilder.clustering().cacheMode(async ? CacheMode.INVALIDATION_ASYNC : CacheMode.INVALIDATION_SYNC);
}
invalidationConfigBuilder.transaction().transactionManagerLookup(new DummyTransactionManagerLookup());
invalidationConfigBuilder.locking().isolationLevel(IsolationLevel.REPEATABLE_READ).writeSkewCheck(true).versioning().enable().scheme(VersioningScheme.SIMPLE);
cacheManager.defineConfiguration(InfinispanConnectionProvider.REALM_CACHE_NAME, invalidationConfigBuilder.build());
ConfigurationBuilder userConfigBuilder = new ConfigurationBuilder();
if (clustered) {
userConfigBuilder.clustering().cacheMode(async ? CacheMode.INVALIDATION_ASYNC : CacheMode.INVALIDATION_SYNC);
}
Configuration userCacheConfiguration = userConfigBuilder.build();
cacheManager.defineConfiguration(InfinispanConnectionProvider.USER_CACHE_NAME, userCacheConfiguration);
ConfigurationBuilder sessionConfigBuilder = new ConfigurationBuilder();
if (clustered) {
String sessionsMode = config.get("sessionsMode", "distributed");
if (sessionsMode.equalsIgnoreCase("replicated")) {
sessionConfigBuilder.clustering().cacheMode(async ? CacheMode.REPL_ASYNC : CacheMode.REPL_SYNC);
} else if (sessionsMode.equalsIgnoreCase("distributed")) {
sessionConfigBuilder.clustering().cacheMode(async ? CacheMode.DIST_ASYNC : CacheMode.DIST_SYNC);
} else {
throw new RuntimeException("Invalid value for sessionsMode");
}
sessionConfigBuilder.clustering().hash()
.numOwners(config.getInt("sessionsOwners", 2))
.numSegments(config.getInt("sessionsSegments", 60)).build();
}
Configuration sessionCacheConfiguration = sessionConfigBuilder.build();
cacheManager.defineConfiguration(InfinispanConnectionProvider.SESSION_CACHE_NAME, sessionCacheConfiguration);
cacheManager.defineConfiguration(InfinispanConnectionProvider.OFFLINE_SESSION_CACHE_NAME, sessionCacheConfiguration);
cacheManager.defineConfiguration(InfinispanConnectionProvider.LOGIN_FAILURE_CACHE_NAME, sessionCacheConfiguration);
}
}

View file

@ -0,0 +1,269 @@
/*
* 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.skewed;
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 javax.transaction.NotSupportedException;
import javax.transaction.Status;
import javax.transaction.SystemException;
import java.util.concurrent.ConcurrentHashMap;
/**
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
*/
public class RepeatableReadWriteSkewRealmCache implements RealmCache {
protected static final Logger logger = Logger.getLogger(RepeatableReadWriteSkewRealmCache.class);
protected final Cache<String, Object> cache;
protected final ConcurrentHashMap<String, String> realmLookup;
public RepeatableReadWriteSkewRealmCache(Cache<String, Object> cache, ConcurrentHashMap<String, String> realmLookup) {
this.cache = cache;
this.realmLookup = realmLookup;
}
public Cache<String, Object> getCache() {
return cache;
}
public void startBatch() {
logger.trace("*** START BATCH ***");
try {
if (cache.getAdvancedCache().getTransactionManager().getStatus() == Status.STATUS_NO_TRANSACTION) {
cache.getAdvancedCache().getTransactionManager().begin();
}
} catch (NotSupportedException e) {
throw new RuntimeException(e);
} catch (SystemException e) {
throw new RuntimeException(e);
}
}
public void endBatch(boolean commit) {
logger.trace("*** END BATCH ***");
try {
if (cache.getAdvancedCache().getTransactionManager().getStatus() == Status.STATUS_ACTIVE) {
if (commit) {
cache.getAdvancedCache().getTransactionManager().commit();
} else {
cache.getAdvancedCache().getTransactionManager().rollback();
}
}
} catch (Exception e) {
//throw new RuntimeException(e);
}
}
@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());
invalidate(realm.getId());
realmLookup.remove(realm.getName());
}
protected Object invalidate(String id) {
startBatch();
Object rtn = cache.remove(id);
logger.trace("*** END BATCH ***");
try {
if (cache.getAdvancedCache().getTransactionManager().getStatus() == Status.STATUS_ACTIVE) {
if (true) {
cache.getAdvancedCache().getTransactionManager().commit();
} else {
cache.getAdvancedCache().getTransactionManager().rollback();
}
}
} catch (Exception e) {
logger.trace("Failed to commit invalidate");
}
return rtn;
}
@Override
public void invalidateCachedRealmById(String id) {
CachedRealm cached = (CachedRealm) invalidate(id);
if (cached != null) realmLookup.remove(cached.getName());
}
@Override
public void addCachedRealm(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) {
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());
invalidate(app.getId());
}
@Override
public void addCachedClient(CachedClient app) {
logger.tracev("Adding application {0}", app.getId());
cache.putForExternalRead(app.getId(), app);
}
@Override
public void invalidateCachedApplicationById(String id) {
logger.tracev("Removing application {0}", id);
invalidate(id);
}
@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());
invalidate(role.getId());
}
@Override
public void addCachedGroup(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);
invalidate(id);
}
@Override
public void invalidateGroupById(String id) {
logger.tracev("Removing group {0}", id);
invalidate(id);
}
@Override
public CachedRole getRole(String id) {
return get(id, CachedRole.class);
}
@Override
public void invalidateRole(CachedRole role) {
logger.tracev("Removing role {0}", role.getId());
invalidate(role.getId());
}
@Override
public void invalidateRoleById(String id) {
logger.tracev("Removing role {0}", id);
invalidate(id);
}
@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());
cache.putForExternalRead(role.getId(), role);
}
@Override
public void invalidateCachedRoleById(String id) {
logger.tracev("Removing role {0}", id);
invalidate(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;
}
@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());
invalidate(app.getId());
}
@Override
public void addCachedClientTemplate(CachedClientTemplate app) {
logger.tracev("Adding client template {0}", app.getId());
cache.putForExternalRead(app.getId(), app);
}
@Override
public void invalidateCachedClientTemplateById(String id) {
logger.tracev("Removing client template {0}", id);
invalidate(id);
}
@Override
public void evictCachedClientTemplateById(String id) {
logger.tracev("Evicting client template {0}", id);
invalidate(id);
}
}

View file

@ -0,0 +1,472 @@
/*
* 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.skewed;
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.CachedClientRole;
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.CachedRealmRole;
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 java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
/**
* DO NOT USE THIS!!
*
* Tries unsuccessfully to use Infinispan with REPEATABLE_READ, write-skew-checking
*
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
* @version $Revision: 1 $
*/
public class RepeatableReadWriteSkewRealmCacheProvider implements CacheRealmProvider {
protected static final Logger logger = Logger.getLogger(RepeatableReadWriteSkewRealmCacheProvider.class);
protected RepeatableReadWriteSkewRealmCache 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 RepeatableReadWriteSkewRealmCacheProvider(RepeatableReadWriteSkewRealmCache cache, KeycloakSession session) {
this.cache = cache;
this.session = session;
session.getTransaction().enlistAfterCompletion(getTransaction());
}
@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 getTransaction() {
return new KeycloakTransaction() {
@Override
public void begin() {
transactionActive = true;
}
@Override
public void commit() {
if (delegate == null) return;
if (clearAll) {
cache.clear();
}
runInvalidations();
transactionActive = false;
}
@Override
public void rollback() {
setRollbackOnly = true;
runInvalidations();
transactionActive = false;
}
@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) {
//cache.startBatch();
cache.startBatch();
boolean batchEnded = false;
try {
CachedRealm cached = cache.getCachedRealm(id);
boolean wasNull = cached == null;
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);
try {
batchEnded = true;
cache.endBatch(true);
logger.trace("returning new cached realm");
} catch (Exception exception) {
logger.trace("failed to add to cache", exception);
return model;
}
} else if (realmInvalidations.contains(id)) {
return getDelegate().getRealm(id);
} else if (managedRealms.containsKey(id)) {
return managedRealms.get(id);
}
if (!wasNull) logger.trace("returning cached realm: " + cached.getName());
RealmAdapter adapter = new RealmAdapter(cached, this);
managedRealms.put(id, adapter);
return adapter;
} finally {
if (!batchEnded) cache.endBatch(true);
}
}
@Override
public RealmModel getRealmByName(String name) {
cache.startBatch();
boolean batchEnded = false;
try {
CachedRealm cached = cache.getCachedRealmByName(name);
boolean wasNull = cached == null;
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);
try {
batchEnded = true;
cache.endBatch(true);
logger.trace("returning new cached realm: " + cached.getName());
} catch (Exception exception) {
logger.trace("failed to add to cache", exception);
return model;
}
} else if (realmInvalidations.contains(cached.getId())) {
return getDelegate().getRealmByName(name);
} else if (managedRealms.containsKey(cached.getId())) {
return managedRealms.get(cached.getId());
}
if (!wasNull) logger.trace("returning cached realm: " + cached.getName());
RealmAdapter adapter = new RealmAdapter(cached, this);
managedRealms.put(cached.getId(), adapter);
return adapter;
} finally {
if (!batchEnded) cache.endBatch(true);
}
}
@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) {
cache.startBatch();
boolean batchEnded = false;
try {
CachedRole cached = cache.getRole(id);
if (cached != null && !cached.getRealm().equals(realm.getId())) {
cached = null;
}
if (cached == null) {
RoleModel model = getDelegate().getRoleById(id, realm);
if (model == null) return null;
if (roleInvalidations.contains(id)) return model;
if (model.getContainer() instanceof ClientModel) {
cached = new CachedClientRole(((ClientModel) model.getContainer()).getId(), model, realm);
} else {
cached = new CachedRealmRole(model, realm);
}
cache.addCachedRole(cached);
try {
batchEnded = true;
cache.endBatch(true);
} catch (Exception exception) {
logger.trace("failed to add to cache", exception);
return model;
}
} 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;
} finally {
if (!batchEnded) cache.endBatch(true);
}
}
@Override
public GroupModel getGroupById(String id, RealmModel realm) {
cache.startBatch();
boolean batchEnded = false;
try {
CachedGroup cached = cache.getGroup(id);
if (cached != null && !cached.getRealm().equals(realm.getId())) {
cached = null;
}
if (cached == null) {
GroupModel model = getDelegate().getGroupById(id, realm);
if (model == null) return null;
if (groupInvalidations.contains(id)) return model;
cached = new CachedGroup(realm, model);
cache.addCachedGroup(cached);
try {
batchEnded = true;
cache.endBatch(true);
} catch (Exception exception) {
logger.trace("failed to add to cache", exception);
return model;
}
} 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;
} finally {
if (!batchEnded) cache.endBatch(true);
}
}
@Override
public ClientModel getClientById(String id, RealmModel realm) {
cache.startBatch();
boolean batchEnded = false;
CachedClient cached = cache.getApplication(id);
try {
if (cached != null && !cached.getRealm().equals(realm.getId())) {
cached = null;
}
if (cached == null) {
ClientModel model = getDelegate().getClientById(id, realm);
if (model == null) return null;
if (appInvalidations.contains(id)) return model;
cached = new CachedClient(cache, getDelegate(), realm, model);
cache.addCachedClient(cached);
try {
batchEnded = true;
cache.endBatch(true);
} catch (Exception exception) {
logger.trace("failed to add to cache", exception);
return model;
}
} 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;
} finally {
if (!batchEnded) cache.endBatch(true);
}
}
@Override
public ClientTemplateModel getClientTemplateById(String id, RealmModel realm) {
cache.startBatch();
boolean batchEnded = false;
try {
CachedClientTemplate cached = cache.getClientTemplate(id);
if (cached != null && !cached.getRealm().equals(realm.getId())) {
cached = null;
}
if (cached == null) {
ClientTemplateModel model = getDelegate().getClientTemplateById(id, realm);
if (model == null) return null;
if (clientTemplateInvalidations.contains(id)) return model;
cached = new CachedClientTemplate(cache, getDelegate(), realm, model);
cache.addCachedClientTemplate(cached);
try {
batchEnded = true;
cache.endBatch(true);
} catch (Exception exception) {
logger.trace("failed to add to cache", exception);
return model;
}
} 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;
} finally {
if (!batchEnded) cache.endBatch(true);
}
}
}

View file

@ -15,4 +15,6 @@
# limitations under the License.
#
org.keycloak.connections.infinispan.DefaultInfinispanConnectionProviderFactory
org.keycloak.connections.infinispan.DefaultInfinispanConnectionProviderFactory
org.keycloak.models.cache.infinispan.counter.RevisionedConnectionProviderFactory
org.keycloak.models.cache.infinispan.locking.LockingConnectionProviderFactory

View file

@ -15,4 +15,6 @@
# limitations under the License.
#
org.keycloak.models.cache.infinispan.InfinispanCacheRealmProviderFactory
org.keycloak.models.cache.infinispan.InfinispanCacheRealmProviderFactory
org.keycloak.models.cache.infinispan.counter.RevisionedCacheRealmProviderFactory
org.keycloak.models.cache.infinispan.locking.LockingCacheRealmProviderFactory

View file

@ -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;
}
}

View file

@ -0,0 +1,269 @@
package org.keycloak.models.sessions.infinispan.initializer;
import org.infinispan.Cache;
import org.infinispan.commons.CacheException;
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.TransactionMode;
import org.infinispan.transaction.TransactionProtocol;
import org.infinispan.transaction.lookup.DummyTransactionManagerLookup;
import org.infinispan.util.concurrent.IsolationLevel;
import org.junit.Assert;
import org.junit.Ignore;
import org.junit.Test;
import org.keycloak.connections.infinispan.InfinispanConnectionProvider;
import javax.transaction.NotSupportedException;
import javax.transaction.Status;
import javax.transaction.SystemException;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/**
* Unit tests to make sure our model caching concurrency model will work.
*
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
* @version $Revision: 1 $
*/
@Ignore
public class ConcurrencyVersioningTest {
public static abstract class AbstractThread implements Runnable {
EmbeddedCacheManager cacheManager;
boolean success;
CountDownLatch latch = new CountDownLatch(1);
public AbstractThread(EmbeddedCacheManager cacheManager) {
this.cacheManager = cacheManager;
}
public boolean isSuccess() {
return success;
}
public CountDownLatch getLatch() {
return latch;
}
}
public static class RemoveThread extends AbstractThread {
public RemoveThread(EmbeddedCacheManager cacheManager) {
super(cacheManager);
}
public void run() {
Cache<String, String> cache = cacheManager.getCache(InfinispanConnectionProvider.REALM_CACHE_NAME);
try {
startBatch(cache);
cache.remove("key");
//cache.getAdvancedCache().getTransactionManager().commit();
endBatch(cache);
success = true;
} catch (Exception e) {
success = false;
}
latch.countDown();
}
}
public static class UpdateThread extends AbstractThread {
public UpdateThread(EmbeddedCacheManager cacheManager) {
super(cacheManager);
}
public void run() {
Cache<String, String> cache = cacheManager.getCache(InfinispanConnectionProvider.REALM_CACHE_NAME);
try {
startBatch(cache);
cache.putForExternalRead("key", "value2");
//cache.getAdvancedCache().getTransactionManager().commit();
endBatch(cache);
success = true;
} catch (Exception e) {
success = false;
}
latch.countDown();
}
}
/**
* Tests that if remove executes before put, then put still succeeds.
*
* @throws Exception
*/
@Test
public void testGetRemovePutOnNonExisting() throws Exception {
final DefaultCacheManager cacheManager = getVersionedCacheManager();
ExecutorService executor = Executors.newSingleThreadExecutor();
RemoveThread removeThread = new RemoveThread(cacheManager);
Cache<String, String> cache = cacheManager.getCache(InfinispanConnectionProvider.REALM_CACHE_NAME);
cache.remove("key");
startBatch(cache);
cache.get("key");
executor.execute(removeThread);
removeThread.getLatch().await();
cache.putForExternalRead("key", "value1");
endBatch(cache);
Assert.assertEquals(cache.get("key"), "value1");
Assert.assertTrue(removeThread.isSuccess());
}
/**
* Test that if a put of an existing key is removed after the put and before tx commit, it is evicted
*
* @throws Exception
*/
@Test
public void testGetRemovePutOnExisting() throws Exception {
final DefaultCacheManager cacheManager = getVersionedCacheManager();
ExecutorService executor = Executors.newSingleThreadExecutor();
RemoveThread removeThread = new RemoveThread(cacheManager);
Cache<String, String> cache = cacheManager.getCache(InfinispanConnectionProvider.REALM_CACHE_NAME);
cache.put("key", "value0");
startBatch(cache);
cache.get("key");
executor.execute(removeThread);
removeThread.getLatch().await();
cache.put("key", "value1");
try {
endBatch(cache);
Assert.fail("Write skew should be detected");
} catch (Exception e) {
}
Assert.assertNull(cache.get("key"));
Assert.assertTrue(removeThread.isSuccess());
}
/**
* Test that if a put of an existing key is removed after the put and before tx commit, it is evicted
*
* @throws Exception
*/
@Test
public void testGetRemovePutEternalOnExisting() throws Exception {
final DefaultCacheManager cacheManager = getVersionedCacheManager();
ExecutorService executor = Executors.newSingleThreadExecutor();
RemoveThread removeThread = new RemoveThread(cacheManager);
Cache<String, String> cache = cacheManager.getCache(InfinispanConnectionProvider.REALM_CACHE_NAME);
cache.put("key", "value0");
startBatch(cache);
cache.get("key");
executor.execute(removeThread);
cache.putForExternalRead("key", "value1");
removeThread.getLatch().await();
try {
endBatch(cache);
// Assert.fail("Write skew should be detected");
} catch (Exception e) {
}
Assert.assertNull(cache.get("key"));
Assert.assertTrue(removeThread.isSuccess());
}
@Test
public void testPutExternalRemoveOnExisting() throws Exception {
final DefaultCacheManager cacheManager = getVersionedCacheManager();
ExecutorService executor = Executors.newSingleThreadExecutor();
RemoveThread removeThread = new RemoveThread(cacheManager);
Cache<String, String> cache = cacheManager.getCache(InfinispanConnectionProvider.REALM_CACHE_NAME);
cache.put("key", "value0");
startBatch(cache);
cache.putForExternalRead("key", "value1");
executor.execute(removeThread);
removeThread.getLatch().await();
try {
endBatch(cache);
// Assert.fail("Write skew should be detected");
} catch (Exception e) {
}
Assert.assertNull(cache.get("key"));
Assert.assertTrue(removeThread.isSuccess());
}
public static void startBatch(Cache<String, String> cache) {
try {
if (cache.getAdvancedCache().getTransactionManager().getStatus() == Status.STATUS_NO_TRANSACTION) {
System.out.println("begin");
cache.getAdvancedCache().getTransactionManager().begin();
}
} catch (NotSupportedException e) {
throw new RuntimeException(e);
} catch (SystemException e) {
throw new RuntimeException(e);
}
}
public static void endBatch(Cache<String, String> cache) {
boolean commit = true;
try {
if (cache.getAdvancedCache().getTransactionManager().getStatus() == Status.STATUS_ACTIVE) {
if (commit) {
cache.getAdvancedCache().getTransactionManager().commit();
} else {
cache.getAdvancedCache().getTransactionManager().rollback();
}
}
} catch (Exception e) {
throw new RuntimeException(e);
}
}
protected DefaultCacheManager getVersionedCacheManager() {
GlobalConfigurationBuilder gcb = new GlobalConfigurationBuilder();
boolean clustered = false;
boolean async = false;
boolean allowDuplicateJMXDomains = true;
if (clustered) {
gcb.transport().defaultTransport();
}
gcb.globalJmxStatistics().allowDuplicateDomains(allowDuplicateJMXDomains);
final DefaultCacheManager cacheManager = new DefaultCacheManager(gcb.build());
ConfigurationBuilder invalidationConfigBuilder = new ConfigurationBuilder();
invalidationConfigBuilder
//.invocationBatching().enable()
.transaction().transactionMode(TransactionMode.TRANSACTIONAL)
.transaction().transactionManagerLookup(new DummyTransactionManagerLookup())
.locking().isolationLevel(IsolationLevel.REPEATABLE_READ).writeSkewCheck(true).versioning().enable().scheme(VersioningScheme.SIMPLE);
//invalidationConfigBuilder.locking().isolationLevel(IsolationLevel.REPEATABLE_READ).writeSkewCheck(true).versioning().enable().scheme(VersioningScheme.SIMPLE);
if (clustered) {
invalidationConfigBuilder.clustering().cacheMode(async ? CacheMode.INVALIDATION_ASYNC : CacheMode.INVALIDATION_SYNC);
}
Configuration invalidationCacheConfiguration = invalidationConfigBuilder.build();
cacheManager.defineConfiguration(InfinispanConnectionProvider.REALM_CACHE_NAME, invalidationCacheConfiguration);
return cacheManager;
}
}

View file

@ -633,8 +633,8 @@ public class ClientAdapter implements ClientModel {
roleEntity.setClient(entity);
roleEntity.setClientRole(true);
roleEntity.setRealmId(realm.getId());
em.persist(roleEntity);
entity.getRoles().add(roleEntity);
em.persist(roleEntity);
em.flush();
return new RoleAdapter(realm, em, roleEntity);
}
@ -667,12 +667,21 @@ public class ClientAdapter implements ClientModel {
@Override
public Set<RoleModel> getRoles() {
Set<RoleModel> list = new HashSet<RoleModel>();
/*
Collection<RoleEntity> roles = entity.getRoles();
if (roles == null) return list;
for (RoleEntity entity : roles) {
list.add(new RoleAdapter(realm, em, entity));
}
*/
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

View file

@ -124,9 +124,18 @@ public class JpaRealmProvider implements RealmProvider {
.setParameter("realm", realm).executeUpdate();
num = em.createNamedQuery("deleteGroupsByRealm")
.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())) {
adapter.removeClient(a.getId());
}
*/
for (ClientTemplateEntity a : new LinkedList<>(realm.getClientTemplates())) {
adapter.removeClientTemplate(a.getId());
}

View file

@ -729,12 +729,14 @@ public class RealmAdapter implements RealmModel {
@Override
public List<ClientModel> getClients() {
List<ClientModel> list = new ArrayList<ClientModel>();
if (realm.getClients() == null) return list;
for (ClientEntity entity : realm.getClients()) {
List<ClientModel> list = new LinkedList<>();
TypedQuery<ClientEntity> query = em.createNamedQuery("getClientsByRealm", ClientEntity.class);
query.setParameter("realm", realm);
List<ClientEntity> clients = query.getResultList();
for (ClientEntity entity : clients) {
list.add(new ClientAdapter(this, em, session, entity));
}
return list;
return list;
}
@Override
@ -794,11 +796,9 @@ public class RealmAdapter implements RealmModel {
clientEntity = a;
}
}
if (client == null) {
return false;
}
em.remove(clientEntity);
if (clientEntity == null) return false;
em.createNamedQuery("deleteScopeMappingByClient").setParameter("client", clientEntity).executeUpdate();
em.remove(clientEntity);
em.flush();
return true;
@ -1017,6 +1017,7 @@ public class RealmAdapter implements RealmModel {
entity.setFullSyncPeriod(model.getFullSyncPeriod());
entity.setChangedSyncPeriod(model.getChangedSyncPeriod());
entity.setLastSync(model.getLastSync());
entity.setRealm(realm);
em.persist(entity);
realm.getUserFederationProviders().add(entity);

View file

@ -19,6 +19,8 @@ package org.keycloak.models.jpa.entities;
import org.keycloak.models.AuthenticationExecutionModel;
import javax.persistence.Access;
import javax.persistence.AccessType;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.FetchType;
@ -43,6 +45,7 @@ import javax.persistence.Table;
public class AuthenticationExecutionEntity {
@Id
@Column(name="ID", length = 36)
@Access(AccessType.PROPERTY) // we do this because relationships often fetch id, but not entity. This avoids an extra SQL
protected String id;
@ManyToOne(fetch = FetchType.LAZY)

View file

@ -17,6 +17,8 @@
package org.keycloak.models.jpa.entities;
import javax.persistence.Access;
import javax.persistence.AccessType;
import javax.persistence.CascadeType;
import javax.persistence.Column;
import javax.persistence.Entity;
@ -44,6 +46,7 @@ import java.util.Collection;
public class AuthenticationFlowEntity {
@Id
@Column(name="ID", length = 36)
@Access(AccessType.PROPERTY) // we do this because relationships often fetch id, but not entity. This avoids an extra SQL
protected String id;
@ManyToOne(fetch = FetchType.LAZY)

View file

@ -17,6 +17,8 @@
package org.keycloak.models.jpa.entities;
import javax.persistence.Access;
import javax.persistence.AccessType;
import javax.persistence.CollectionTable;
import javax.persistence.Column;
import javax.persistence.ElementCollection;
@ -40,6 +42,7 @@ import java.util.Map;
public class AuthenticatorConfigEntity {
@Id
@Column(name="ID", length = 36)
@Access(AccessType.PROPERTY) // we do this because relationships often fetch id, but not entity. This avoids an extra SQL
protected String id;
@Column(name="ALIAS")

View file

@ -17,6 +17,8 @@
package org.keycloak.models.jpa.entities;
import javax.persistence.Access;
import javax.persistence.AccessType;
import javax.persistence.CascadeType;
import javax.persistence.CollectionTable;
import javax.persistence.Column;
@ -28,6 +30,8 @@ import javax.persistence.JoinColumn;
import javax.persistence.JoinTable;
import javax.persistence.ManyToOne;
import javax.persistence.MapKeyColumn;
import javax.persistence.NamedQueries;
import javax.persistence.NamedQuery;
import javax.persistence.OneToMany;
import javax.persistence.Table;
import javax.persistence.UniqueConstraint;
@ -44,10 +48,14 @@ import java.util.Set;
*/
@Entity
@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 {
@Id
@Column(name="ID", length = 36)
@Access(AccessType.PROPERTY) // we do this because relationships often fetch id, but not entity. This avoids an extra SQL
private String id;
@Column(name = "NAME")
private String name;
@ -146,7 +154,7 @@ public class ClientEntity {
@Column(name="NODE_REREG_TIMEOUT")
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>();
@OneToMany(fetch = FetchType.LAZY, cascade ={CascadeType.REMOVE}, orphanRemoval = true)

View file

@ -17,6 +17,8 @@
package org.keycloak.models.jpa.entities;
import javax.persistence.Access;
import javax.persistence.AccessType;
import javax.persistence.CascadeType;
import javax.persistence.CollectionTable;
import javax.persistence.Column;
@ -48,6 +50,7 @@ public class ClientTemplateEntity {
@Id
@Column(name="ID", length = 36)
@Access(AccessType.PROPERTY) // we do this because relationships often fetch id, but not entity. This avoids an extra SQL
private String id;
@Column(name = "NAME")
private String name;

View file

@ -17,6 +17,8 @@
package org.keycloak.models.jpa.entities;
import javax.persistence.Access;
import javax.persistence.AccessType;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.FetchType;
@ -42,6 +44,7 @@ import javax.persistence.Table;
public class CredentialEntity {
@Id
@Column(name="ID", length = 36)
@Access(AccessType.PROPERTY) // we do this because relationships often fetch id, but not entity. This avoids an extra SQL
protected String id;
@Column(name="TYPE")

View file

@ -17,6 +17,8 @@
package org.keycloak.models.jpa.entities;
import javax.persistence.Access;
import javax.persistence.AccessType;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.FetchType;
@ -42,6 +44,7 @@ public class GroupAttributeEntity {
@Id
@Column(name="ID", length = 36)
@Access(AccessType.PROPERTY) // we do this because relationships often fetch id, but not entity. This avoids an extra SQL
protected String id;
@ManyToOne(fetch= FetchType.LAZY)

View file

@ -17,6 +17,8 @@
package org.keycloak.models.jpa.entities;
import javax.persistence.Access;
import javax.persistence.AccessType;
import javax.persistence.CascadeType;
import javax.persistence.Column;
import javax.persistence.Entity;
@ -47,6 +49,7 @@ import java.util.Collection;
public class GroupEntity {
@Id
@Column(name="ID", length = 36)
@Access(AccessType.PROPERTY) // we do this because relationships often fetch id, but not entity. This avoids an extra SQL
protected String id;
@Column(name = "NAME")

View file

@ -17,6 +17,8 @@
package org.keycloak.models.jpa.entities;
import javax.persistence.Access;
import javax.persistence.AccessType;
import javax.persistence.CollectionTable;
import javax.persistence.Column;
import javax.persistence.ElementCollection;
@ -44,6 +46,7 @@ public class IdentityProviderEntity {
@Id
@Column(name="INTERNAL_ID", length = 36)
@Access(AccessType.PROPERTY) // we do this because relationships often fetch id, but not entity. This avoids an extra SQL
protected String internalId;
@ManyToOne(fetch = FetchType.LAZY)

View file

@ -17,6 +17,8 @@
package org.keycloak.models.jpa.entities;
import javax.persistence.Access;
import javax.persistence.AccessType;
import javax.persistence.CollectionTable;
import javax.persistence.Column;
import javax.persistence.ElementCollection;
@ -39,6 +41,7 @@ public class IdentityProviderMapperEntity {
@Id
@Column(name="ID", length = 36)
@Access(AccessType.PROPERTY) // we do this because relationships often fetch id, but not entity. This avoids an extra SQL
protected String id;
@Column(name="NAME")

View file

@ -17,6 +17,8 @@
package org.keycloak.models.jpa.entities;
import javax.persistence.Access;
import javax.persistence.AccessType;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.FetchType;
@ -37,6 +39,7 @@ public class MigrationModelEntity {
public static final String SINGLETON_ID = "SINGLETON";
@Id
@Column(name="ID", length = 36)
@Access(AccessType.PROPERTY) // we do this because relationships often fetch id, but not entity. This avoids an extra SQL
private String id;
@Column(name="VERSION", length = 36)

View file

@ -17,6 +17,8 @@
package org.keycloak.models.jpa.entities;
import javax.persistence.Access;
import javax.persistence.AccessType;
import javax.persistence.CollectionTable;
import javax.persistence.Column;
import javax.persistence.ElementCollection;
@ -39,6 +41,7 @@ public class ProtocolMapperEntity {
@Id
@Column(name="ID", length = 36)
@Access(AccessType.PROPERTY) // we do this because relationships often fetch id, but not entity. This avoids an extra SQL
protected String id;
@Column(name="NAME")

View file

@ -17,6 +17,8 @@
package org.keycloak.models.jpa.entities;
import javax.persistence.Access;
import javax.persistence.AccessType;
import javax.persistence.CascadeType;
import javax.persistence.CollectionTable;
import javax.persistence.Column;
@ -53,6 +55,7 @@ import java.util.Set;
public class RealmEntity {
@Id
@Column(name="ID", length = 36)
@Access(AccessType.PROPERTY) // we do this because relationships often fetch id, but not entity. This avoids an extra SQL
protected String id;
@Column(name="NAME", unique = true)
@ -138,22 +141,19 @@ public class RealmEntity {
@OneToMany(cascade ={CascadeType.REMOVE}, orphanRemoval = true, mappedBy = "realm")
Collection<RequiredCredentialEntity> requiredCredentials = new ArrayList<RequiredCredentialEntity>();
@OneToMany(cascade ={CascadeType.REMOVE}, orphanRemoval = true)
@JoinTable(name="FED_PROVIDERS", joinColumns={ @JoinColumn(name="REALM_ID") }, inverseJoinColumns={ @JoinColumn(name = "USERFEDERATIONPROVIDERS_ID") })
@OneToMany(cascade ={CascadeType.REMOVE}, orphanRemoval = true, mappedBy = "realm")
List<UserFederationProviderEntity> userFederationProviders = new ArrayList<UserFederationProviderEntity>();
@OneToMany(cascade ={CascadeType.REMOVE}, orphanRemoval = true, mappedBy = "realm")
Collection<UserFederationMapperEntity> userFederationMappers = new ArrayList<UserFederationMapperEntity>();
@OneToMany(fetch = FetchType.LAZY, cascade ={CascadeType.REMOVE}, orphanRemoval = true)
@JoinTable(name="REALM_CLIENT", joinColumns={ @JoinColumn(name="REALM_ID") }, inverseJoinColumns={ @JoinColumn(name="CLIENT_ID") })
@OneToMany(fetch = FetchType.LAZY, cascade ={CascadeType.REMOVE}, orphanRemoval = true, mappedBy="realm")
Collection<ClientEntity> clients = new ArrayList<>();
@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") })
@OneToMany(fetch = FetchType.LAZY, cascade ={CascadeType.REMOVE}, orphanRemoval = true, mappedBy = "realm")
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>();
@ElementCollection
@ -421,7 +421,6 @@ public class RealmEntity {
public void setRequiredCredentials(Collection<RequiredCredentialEntity> requiredCredentials) {
this.requiredCredentials = requiredCredentials;
}
public Collection<ClientEntity> getClients() {
return clients;
}

View file

@ -17,6 +17,8 @@
package org.keycloak.models.jpa.entities;
import javax.persistence.Access;
import javax.persistence.AccessType;
import javax.persistence.CollectionTable;
import javax.persistence.Column;
import javax.persistence.ElementCollection;
@ -42,6 +44,7 @@ import java.util.Map;
public class RequiredActionProviderEntity {
@Id
@Column(name="ID", length = 36)
@Access(AccessType.PROPERTY) // we do this because relationships often fetch id, but not entity. This avoids an extra SQL
protected String id;
@Column(name="ALIAS")

View file

@ -17,6 +17,11 @@
package org.keycloak.models.jpa.entities;
import org.hibernate.annotations.DynamicInsert;
import org.hibernate.annotations.DynamicUpdate;
import javax.persistence.Access;
import javax.persistence.AccessType;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.FetchType;
@ -37,10 +42,13 @@ import java.util.Collection;
* @version $Revision: 1 $
*/
@Entity
//@DynamicInsert
//@DynamicUpdate
@Table(name="KEYCLOAK_ROLE", uniqueConstraints = {
@UniqueConstraint(columnNames = { "NAME", "CLIENT_REALM_CONSTRAINT" })
})
@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="getRealmRoleByName", query="select role from RoleEntity role where role.clientRole = false and role.name = :name and role.realm = :realm")
})
@ -48,6 +56,7 @@ import java.util.Collection;
public class RoleEntity {
@Id
@Column(name="ID", length = 36)
@Access(AccessType.PROPERTY) // we do this because relationships often fetch id, but not entity. This avoids an extra SQL
private String id;
@Column(name = "NAME")

View file

@ -17,6 +17,8 @@
package org.keycloak.models.jpa.entities;
import javax.persistence.Access;
import javax.persistence.AccessType;
import javax.persistence.CollectionTable;
import javax.persistence.Column;
import javax.persistence.ElementCollection;
@ -48,6 +50,7 @@ public class UserAttributeEntity {
@Id
@Column(name="ID", length = 36)
@Access(AccessType.PROPERTY) // we do this because relationships often fetch id, but not entity. This avoids an extra SQL
protected String id;
@ManyToOne(fetch= FetchType.LAZY)

View file

@ -20,6 +20,8 @@ package org.keycloak.models.jpa.entities;
import java.util.ArrayList;
import java.util.Collection;
import javax.persistence.Access;
import javax.persistence.AccessType;
import javax.persistence.CascadeType;
import javax.persistence.Column;
import javax.persistence.Entity;
@ -51,6 +53,7 @@ public class UserConsentEntity {
@Id
@Column(name="ID", length = 36)
@Access(AccessType.PROPERTY) // we do this because relationships often fetch id, but not entity. This avoids an extra SQL
protected String id;
@ManyToOne(fetch= FetchType.LAZY)

View file

@ -19,6 +19,8 @@ package org.keycloak.models.jpa.entities;
import org.keycloak.models.utils.KeycloakModelUtils;
import javax.persistence.Access;
import javax.persistence.AccessType;
import javax.persistence.CascadeType;
import javax.persistence.Column;
import javax.persistence.Entity;
@ -59,6 +61,7 @@ import java.util.Collection;
public class UserEntity {
@Id
@Column(name="ID", length = 36)
@Access(AccessType.PROPERTY) // we do this because relationships often fetch id, but not entity. This avoids an extra SQL
protected String id;
@Column(name = "USERNAME")

View file

@ -19,6 +19,8 @@ package org.keycloak.models.jpa.entities;
import java.util.Map;
import javax.persistence.Access;
import javax.persistence.AccessType;
import javax.persistence.CollectionTable;
import javax.persistence.Column;
import javax.persistence.ElementCollection;
@ -41,6 +43,7 @@ public class UserFederationMapperEntity {
@Id
@Column(name="ID", length = 36)
@Access(AccessType.PROPERTY) // we do this because relationships often fetch id, but not entity. This avoids an extra SQL
protected String id;
@Column(name="NAME")

View file

@ -17,6 +17,8 @@
package org.keycloak.models.jpa.entities;
import javax.persistence.Access;
import javax.persistence.AccessType;
import javax.persistence.CollectionTable;
import javax.persistence.Column;
import javax.persistence.ElementCollection;
@ -39,6 +41,7 @@ public class UserFederationProviderEntity {
@Id
@Column(name="ID", length = 36)
@Access(AccessType.PROPERTY) // we do this because relationships often fetch id, but not entity. This avoids an extra SQL
protected String id;
@ManyToOne(fetch = FetchType.LAZY)

View file

@ -60,5 +60,21 @@
<where>ACCESS_TOKEN_LIFE_IMPLICIT is NULL</where>
</update>
<dropUniqueConstraint tableName="REALM_CLIENT" constraintName="UK_M6QGA3RFME47335JY8JXYXH3I" />
<dropForeignKeyConstraint baseTableName="REALM_CLIENT" constraintName="FK_93S3P0DIUXAWWQQSA528UBY2Q"/>
<dropForeignKeyConstraint baseTableName="REALM_CLIENT" constraintName="FK_M6QGA3RFME47335JY8JXYXH3I"/>
<dropTable tableName="REALM_CLIENT" cascadeConstraints="true"/>
<dropForeignKeyConstraint baseTableName="REALM_CLIENT_TEMPLATE" constraintName="FK_RLM_CLI_TMPLT_RLM" />
<dropForeignKeyConstraint baseTableName="REALM_CLIENT_TEMPLATE" constraintName="FK_RLM_CLI_TMPLT_CLI"/>
<dropTable tableName="REALM_CLIENT_TEMPLATE" cascadeConstraints="true"/>
<dropUniqueConstraint tableName="FED_PROVIDERS" constraintName="UK_DCCIRJLIPU1478VQC89DID88C" />
<dropForeignKeyConstraint baseTableName="FED_PROVIDERS" constraintName="FK_213LYQ09FKXQ8K8NY8DY3737T"/>
<dropForeignKeyConstraint baseTableName="FED_PROVIDERS" constraintName="FK_DCCIRJLIPU1478VQC89DID88C"/>
<dropTable tableName="FED_PROVIDERS" cascadeConstraints="true"/>
</changeSet>
</databaseChangeLog>

0
pom.xml Normal file → Executable file
View file

View file

@ -26,4 +26,5 @@ public interface KeycloakTransactionManager extends KeycloakTransaction {
void enlist(KeycloakTransaction transaction);
void enlistAfterCompletion(KeycloakTransaction transaction);
void enlistPrepare(KeycloakTransaction transaction);
}

View file

@ -39,43 +39,43 @@ import java.util.TreeMap;
*/
public class CachedClient implements Serializable {
private String id;
private String clientId;
private String name;
private String description;
private String realm;
private Set<String> redirectUris = new HashSet<String>();
private boolean enabled;
private String clientAuthenticatorType;
private String secret;
private String registrationToken;
private String protocol;
private Map<String, String> attributes = new HashMap<String, String>();
private boolean publicClient;
private boolean fullScopeAllowed;
private boolean frontchannelLogout;
private int notBefore;
private Set<String> scope = new HashSet<String>();
private Set<String> webOrigins = new HashSet<String>();
private Set<ProtocolMapperModel> protocolMappers = new HashSet<ProtocolMapperModel>();
private boolean surrogateAuthRequired;
private String managementUrl;
private String rootUrl;
private String baseUrl;
private List<String> defaultRoles = new LinkedList<String>();
private boolean bearerOnly;
private boolean consentRequired;
private boolean standardFlowEnabled;
private boolean implicitFlowEnabled;
private boolean directAccessGrantsEnabled;
private boolean serviceAccountsEnabled;
private Map<String, String> roles = new HashMap<String, String>();
private int nodeReRegistrationTimeout;
private Map<String, Integer> registeredNodes;
private String clientTemplate;
private boolean useTemplateScope;
private boolean useTemplateConfig;
private boolean useTemplateMappers;
protected String id;
protected String clientId;
protected String name;
protected String description;
protected String realm;
protected Set<String> redirectUris = new HashSet<String>();
protected boolean enabled;
protected String clientAuthenticatorType;
protected String secret;
protected String registrationToken;
protected String protocol;
protected Map<String, String> attributes = new HashMap<String, String>();
protected boolean publicClient;
protected boolean fullScopeAllowed;
protected boolean frontchannelLogout;
protected int notBefore;
protected Set<String> scope = new HashSet<String>();
protected Set<String> webOrigins = new HashSet<String>();
protected Set<ProtocolMapperModel> protocolMappers = new HashSet<ProtocolMapperModel>();
protected boolean surrogateAuthRequired;
protected String managementUrl;
protected String rootUrl;
protected String baseUrl;
protected List<String> defaultRoles = new LinkedList<String>();
protected boolean bearerOnly;
protected boolean consentRequired;
protected boolean standardFlowEnabled;
protected boolean implicitFlowEnabled;
protected boolean directAccessGrantsEnabled;
protected boolean serviceAccountsEnabled;
protected Map<String, String> roles = new HashMap<String, String>();
protected int nodeReRegistrationTimeout;
protected Map<String, Integer> registeredNodes;
protected String clientTemplate;
protected boolean useTemplateScope;
protected boolean useTemplateConfig;
protected boolean useTemplateMappers;
public CachedClient(RealmCache cache, RealmProvider delegate, RealmModel realm, ClientModel model) {
id = model.getId();
@ -112,10 +112,7 @@ public class CachedClient implements Serializable {
implicitFlowEnabled = model.isImplicitFlowEnabled();
directAccessGrantsEnabled = model.isDirectAccessGrantsEnabled();
serviceAccountsEnabled = model.isServiceAccountsEnabled();
for (RoleModel role : model.getRoles()) {
roles.put(role.getName(), role.getId());
cache.addCachedRole(new CachedClientRole(id, role, realm));
}
cacheRoles(cache, realm, model);
nodeReRegistrationTimeout = model.getNodeReRegistrationTimeout();
registeredNodes = new TreeMap<String, Integer>(model.getRegisteredNodes());
@ -126,6 +123,14 @@ public class CachedClient implements Serializable {
useTemplateMappers = model.useTemplateMappers();
useTemplateScope = model.useTemplateScope();
}
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));
}
}
public String getId() {
return id;
}

View file

@ -53,90 +53,90 @@ import java.util.Set;
*/
public class CachedRealm implements Serializable {
private String id;
private String name;
private String displayName;
private String displayNameHtml;
private boolean enabled;
private SslRequired sslRequired;
private boolean registrationAllowed;
private boolean registrationEmailAsUsername;
private boolean rememberMe;
private boolean verifyEmail;
private boolean resetPasswordAllowed;
private boolean identityFederationEnabled;
private boolean editUsernameAllowed;
protected String id;
protected String name;
protected String displayName;
protected String displayNameHtml;
protected boolean enabled;
protected SslRequired sslRequired;
protected boolean registrationAllowed;
protected boolean registrationEmailAsUsername;
protected boolean rememberMe;
protected boolean verifyEmail;
protected boolean resetPasswordAllowed;
protected boolean identityFederationEnabled;
protected boolean editUsernameAllowed;
//--- brute force settings
private boolean bruteForceProtected;
private int maxFailureWaitSeconds;
private int minimumQuickLoginWaitSeconds;
private int waitIncrementSeconds;
private long quickLoginCheckMilliSeconds;
private int maxDeltaTimeSeconds;
private int failureFactor;
protected boolean bruteForceProtected;
protected int maxFailureWaitSeconds;
protected int minimumQuickLoginWaitSeconds;
protected int waitIncrementSeconds;
protected long quickLoginCheckMilliSeconds;
protected int maxDeltaTimeSeconds;
protected int failureFactor;
//--- end brute force settings
private boolean revokeRefreshToken;
private int ssoSessionIdleTimeout;
private int ssoSessionMaxLifespan;
private int offlineSessionIdleTimeout;
private int accessTokenLifespan;
private int accessTokenLifespanForImplicitFlow;
private int accessCodeLifespan;
private int accessCodeLifespanUserAction;
private int accessCodeLifespanLogin;
private int notBefore;
private PasswordPolicy passwordPolicy;
private OTPPolicy otpPolicy;
protected boolean revokeRefreshToken;
protected int ssoSessionIdleTimeout;
protected int ssoSessionMaxLifespan;
protected int offlineSessionIdleTimeout;
protected int accessTokenLifespan;
protected int accessTokenLifespanForImplicitFlow;
protected int accessCodeLifespan;
protected int accessCodeLifespanUserAction;
protected int accessCodeLifespanLogin;
protected int notBefore;
protected PasswordPolicy passwordPolicy;
protected OTPPolicy otpPolicy;
private String publicKeyPem;
private String privateKeyPem;
private String certificatePem;
private String codeSecret;
protected String publicKeyPem;
protected String privateKeyPem;
protected String certificatePem;
protected String codeSecret;
private String loginTheme;
private String accountTheme;
private String adminTheme;
private String emailTheme;
private String masterAdminClient;
protected String loginTheme;
protected String accountTheme;
protected String adminTheme;
protected String emailTheme;
protected String masterAdminClient;
private List<RequiredCredentialModel> requiredCredentials = new ArrayList<RequiredCredentialModel>();
private List<UserFederationProviderModel> userFederationProviders = new ArrayList<UserFederationProviderModel>();
private MultivaluedHashMap<String, UserFederationMapperModel> userFederationMappers = new MultivaluedHashMap<String, UserFederationMapperModel>();
private List<IdentityProviderModel> identityProviders = new ArrayList<IdentityProviderModel>();
protected List<RequiredCredentialModel> requiredCredentials = new ArrayList<RequiredCredentialModel>();
protected List<UserFederationProviderModel> userFederationProviders = new ArrayList<UserFederationProviderModel>();
protected MultivaluedHashMap<String, UserFederationMapperModel> userFederationMappers = new MultivaluedHashMap<String, UserFederationMapperModel>();
protected List<IdentityProviderModel> identityProviders = new ArrayList<IdentityProviderModel>();
private Map<String, String> browserSecurityHeaders = new HashMap<String, String>();
private Map<String, String> smtpConfig = new HashMap<String, String>();
private Map<String, AuthenticationFlowModel> authenticationFlows = new HashMap<>();
private Map<String, AuthenticatorConfigModel> authenticatorConfigs = new HashMap<>();
private Map<String, RequiredActionProviderModel> requiredActionProviders = new HashMap<>();
private Map<String, RequiredActionProviderModel> requiredActionProvidersByAlias = new HashMap<>();
private MultivaluedHashMap<String, AuthenticationExecutionModel> authenticationExecutions = new MultivaluedHashMap<>();
private Map<String, AuthenticationExecutionModel> executionsById = new HashMap<>();
protected Map<String, String> browserSecurityHeaders = new HashMap<String, String>();
protected Map<String, String> smtpConfig = new HashMap<String, String>();
protected Map<String, AuthenticationFlowModel> authenticationFlows = new HashMap<>();
protected Map<String, AuthenticatorConfigModel> authenticatorConfigs = new HashMap<>();
protected Map<String, RequiredActionProviderModel> requiredActionProviders = new HashMap<>();
protected Map<String, RequiredActionProviderModel> requiredActionProvidersByAlias = new HashMap<>();
protected MultivaluedHashMap<String, AuthenticationExecutionModel> authenticationExecutions = new MultivaluedHashMap<>();
protected Map<String, AuthenticationExecutionModel> executionsById = new HashMap<>();
private AuthenticationFlowModel browserFlow;
private AuthenticationFlowModel registrationFlow;
private AuthenticationFlowModel directGrantFlow;
private AuthenticationFlowModel resetCredentialsFlow;
private AuthenticationFlowModel clientAuthenticationFlow;
protected AuthenticationFlowModel browserFlow;
protected AuthenticationFlowModel registrationFlow;
protected AuthenticationFlowModel directGrantFlow;
protected AuthenticationFlowModel resetCredentialsFlow;
protected AuthenticationFlowModel clientAuthenticationFlow;
private boolean eventsEnabled;
private long eventsExpiration;
private Set<String> eventsListeners = new HashSet<String>();
private Set<String> enabledEventTypes = new HashSet<String>();
protected boolean eventsEnabled;
protected long eventsExpiration;
protected Set<String> eventsListeners = new HashSet<String>();
protected Set<String> enabledEventTypes = new HashSet<String>();
protected boolean adminEventsEnabled;
protected Set<String> adminEnabledEventOperations = new HashSet<String>();
protected boolean adminEventsDetailsEnabled;
private List<String> defaultRoles = new LinkedList<String>();
private List<String> defaultGroups = new LinkedList<String>();
private Set<String> groups = new HashSet<String>();
private Map<String, String> realmRoles = new HashMap<String, String>();
private Map<String, String> clients = new HashMap<String, String>();
private List<String> clientTemplates= new LinkedList<>();
private boolean internationalizationEnabled;
private Set<String> supportedLocales = new HashSet<String>();
private String defaultLocale;
private MultivaluedHashMap<String, IdentityProviderMapperModel> identityProviderMappers = new MultivaluedHashMap<>();
protected List<String> defaultRoles = new LinkedList<String>();
protected List<String> defaultGroups = new LinkedList<String>();
protected Set<String> groups = new HashSet<String>();
protected Map<String, String> realmRoles = new HashMap<String, String>();
protected Map<String, String> clients = new HashMap<String, String>();
protected List<String> clientTemplates= new LinkedList<>();
protected boolean internationalizationEnabled;
protected Set<String> supportedLocales = new HashSet<String>();
protected String defaultLocale;
protected MultivaluedHashMap<String, IdentityProviderMapperModel> identityProviderMappers = new MultivaluedHashMap<>();
public CachedRealm() {
}
@ -221,23 +221,11 @@ public class CachedRealm implements Serializable {
ClientModel masterAdminClient = model.getMasterAdminClient();
this.masterAdminClient = (masterAdminClient != null) ? masterAdminClient.getId() : null;
for (RoleModel role : model.getRoles()) {
realmRoles.put(role.getName(), role.getId());
CachedRole cachedRole = new CachedRealmRole(role, model);
cache.addCachedRole(cachedRole);
}
cacheRealmRoles(cache, model);
for (ClientModel client : model.getClients()) {
clients.put(client.getClientId(), client.getId());
CachedClient cachedClient = new CachedClient(cache, delegate, model, client);
cache.addCachedClient(cachedClient);
}
cacheClients(cache, delegate, model);
for (ClientTemplateModel template : model.getClientTemplates()) {
clientTemplates.add(template.getId());
CachedClientTemplate cachedClient = new CachedClientTemplate(cache, delegate, model, template);
cache.addCachedClientTemplate(cachedClient);
}
cacheClientTemplates(cache, delegate, model);
internationalizationEnabled = model.isInternationalizationEnabled();
supportedLocales.addAll(model.getSupportedLocales());
@ -273,6 +261,30 @@ public class CachedRealm implements Serializable {
}
protected void cacheClientTemplates(RealmCache cache, RealmProvider delegate, RealmModel model) {
for (ClientTemplateModel template : model.getClientTemplates()) {
clientTemplates.add(template.getId());
CachedClientTemplate cachedClient = new CachedClientTemplate(cache, delegate, model, template);
cache.addCachedClientTemplate(cachedClient);
}
}
protected void cacheClients(RealmCache cache, RealmProvider delegate, RealmModel model) {
for (ClientModel client : model.getClients()) {
clients.put(client.getClientId(), client.getId());
CachedClient cachedClient = new CachedClient(cache, delegate, model, client);
cache.addCachedClient(cachedClient);
}
}
protected void cacheRealmRoles(RealmCache cache, RealmModel model) {
for (RoleModel role : model.getRoles()) {
realmRoles.put(role.getName(), role.getId());
CachedRole cachedRole = new CachedRealmRole(role, model);
cache.addCachedRole(cachedRole);
}
}
public String getId() {
return id;

View file

@ -30,6 +30,7 @@ public class DefaultKeycloakTransactionManager implements KeycloakTransactionMan
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> afterCompletion = new LinkedList<KeycloakTransaction>();
private boolean active;
@ -53,6 +54,15 @@ public class DefaultKeycloakTransactionManager implements KeycloakTransactionMan
afterCompletion.add(transaction);
}
@Override
public void enlistPrepare(KeycloakTransaction transaction) {
if (active && !transaction.isActive()) {
transaction.begin();
}
prepare.add(transaction);
}
@Override
public void begin() {
if (active) {
@ -69,6 +79,17 @@ public class DefaultKeycloakTransactionManager implements KeycloakTransactionMan
@Override
public void commit() {
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) {
try {
tx.commit();
@ -105,6 +126,10 @@ public class DefaultKeycloakTransactionManager implements KeycloakTransactionMan
@Override
public void rollback() {
RuntimeException exception = null;
rollback(exception);
}
protected void rollback(RuntimeException exception) {
for (KeycloakTransaction tx : transactions) {
try {
tx.rollback();

View file

@ -26,14 +26,6 @@
"provider": "${keycloak.userSessionPersister.provider:jpa}"
},
"userSessions": {
"provider" : "${keycloak.userSessions.provider:infinispan}"
},
"realmCache": {
"provider": "${keycloak.realm.cache.provider:infinispan}"
},
"userCache": {
"provider": "${keycloak.user.cache.provider:infinispan}",
"mem": {
@ -41,6 +33,10 @@
}
},
"userSessions": {
"provider" : "${keycloak.userSessions.provider:infinispan}"
},
"timer": {
"provider": "basic"
},
@ -99,6 +95,23 @@
}
},
"realmCache": {
"provider": "infinispan-locking",
"infinispan-locking" : {
"enabled": true
}
},
"connectionsInfinispan": {
"provider": "locking",
"locking": {
"clustered": "${keycloak.connectionsInfinispan.clustered:false}",
"async": "${keycloak.connectionsInfinispan.async:true}",
"sessionsOwners": "${keycloak.connectionsInfinispan.sessionsOwners:2}"
}
},
"truststore": {
"file": {
"file": "${keycloak.truststore.file:src/main/keystore/keycloak.truststore}",

View file

@ -18,6 +18,7 @@
package org.keycloak.testsuite.admin;
import org.jboss.logging.Logger;
import org.junit.Assert;
import org.junit.Ignore;
import org.junit.Test;
import org.keycloak.admin.client.Keycloak;
@ -30,6 +31,8 @@ import javax.ws.rs.core.Response;
import java.util.LinkedList;
import java.util.List;
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.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
@ -39,19 +42,55 @@ import static org.junit.Assert.*;
/**
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
*/
@Ignore
public class ConcurrencyTest extends AbstractClientTest {
private static final Logger log = Logger.getLogger(ConcurrencyTest.class);
private static final int DEFAULT_THREADS = 3;
private static final int DEFAULT_ITERATIONS = 10;
private static final int DEFAULT_THREADS = 1;
private static final int DEFAULT_ITERATIONS = 5;
// If enabled only one request is allowed at the time. Useful for checking that test is working.
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
public void createClient() throws Throwable {
long start = System.currentTimeMillis();
run(new KeycloakRunnable() {
@Override
public void run(Keycloak keycloak, RealmResource realm, int threadNum, int iterationNum) {
@ -75,10 +114,14 @@ public class ConcurrencyTest extends AbstractClientTest {
}
}
});
long end = System.currentTimeMillis() - start;
System.out.println("createClient took " + end);
}
@Test
public void createRole() throws Throwable {
long start = System.currentTimeMillis();
run(new KeycloakRunnable() {
@Override
public void run(Keycloak keycloak, RealmResource realm, int threadNum, int iterationNum) {
@ -88,16 +131,22 @@ public class ConcurrencyTest extends AbstractClientTest {
assertNotNull(realm.roles().get(name).toRepresentation());
}
});
long end = System.currentTimeMillis() - start;
System.out.println("createRole took " + end);
}
@Test
public void createClientRole() throws Throwable {
long start = System.currentTimeMillis();
ClientRepresentation c = new ClientRepresentation();
c.setClientId("client");
Response response = realm.clients().create(c);
final String clientId = ApiUtil.getCreatedId(response);
response.close();
System.out.println("*********************************************");
run(new KeycloakRunnable() {
@Override
public void run(Keycloak keycloak, RealmResource realm, int threadNum, int iterationNum) {
@ -110,6 +159,10 @@ public class ConcurrencyTest extends AbstractClientTest {
assertNotNull(client.roles().get(name).toRepresentation());
}
});
long end = System.currentTimeMillis() - start;
System.out.println("createClientRole took " + end);
System.out.println("*********************************************");
}
private void run(final KeycloakRunnable runnable) throws Throwable {

View file

@ -32,12 +32,6 @@
}
},
"realmCache": {
"infinispan" : {
"enabled": true
}
},
"timer": {
"provider": "basic"
},
@ -84,8 +78,16 @@
}
},
"realmCache": {
"provider": "infinispan-locking",
"infinispan-locking" : {
"enabled": true
}
},
"connectionsInfinispan": {
"default": {
"provider": "locking",
"locking": {
"clustered": "${keycloak.connectionsInfinispan.clustered:false}",
"async": "${keycloak.connectionsInfinispan.async:true}",
"sessionsOwners": "${keycloak.connectionsInfinispan.sessionsOwners:2}"

View file

@ -30,7 +30,9 @@ log4j.logger.org.keycloak=info
# log4j.logger.org.keycloak.services.DefaultKeycloakSessionFactory=debug
# log4j.logger.org.keycloak.provider.ProviderManager=debug
# log4j.logger.org.keycloak.provider.FileSystemProviderLoaderFactory=debug
#log4j.logger.org.infinispan.transaction.impl.TransactionCoordinator=OFF
#log4j.logger.org.infinispan.transaction.tm.DummyTransaction=OFF
#log4j.logger.org.infinispan.container.entries.RepeatableReadEntry=OFF
# Broker logging
keycloak.testsuite.logging.level=info
log4j.logger.org.keycloak.testsuite=${keycloak.testsuite.logging.level}

View file

@ -62,6 +62,7 @@ public class KeycloakServerDeploymentProcessor implements DeploymentUnitProcesso
st.addDependency(cacheContainerService.append("sessions"));
st.addDependency(cacheContainerService.append("offlineSessions"));
st.addDependency(cacheContainerService.append("loginFailures"));
st.addDependency(cacheContainerService.append("realmVersions"));
}
}

View file

@ -30,6 +30,9 @@
<local-cache name="sessions"/>
<local-cache name="offlineSessions"/>
<local-cache name="loginFailures"/>
<local-cache name="realmVersions">
<transaction mode="BATCH" locking="PESSIMISTIC"/>
</local-cache>
</cache-container>
<cache-container name="server" default-cache="default" module="org.wildfly.clustering.server">
<local-cache name="default">
@ -87,6 +90,9 @@
<distributed-cache name="sessions" mode="SYNC" owners="1"/>
<distributed-cache name="offlineSessions" mode="SYNC" owners="1"/>
<distributed-cache name="loginFailures" mode="SYNC" owners="1"/>
<local-cache name="realmVersions">
<transaction mode="BATCH" locking="PESSIMISTIC"/>
</local-cache>
</cache-container>
<cache-container name="server" aliases="singleton cluster" default-cache="default" module="org.wildfly.clustering.server">
<transport lock-timeout="60000"/>