This commit is contained in:
Bill Burke 2016-02-19 15:44:19 -05:00
parent fd49213cb9
commit c45524d8d4
63 changed files with 1403 additions and 1549 deletions

View file

@ -23,7 +23,7 @@
}, },
"userCache": { "userCache": {
"infinispan" : { "default" : {
"enabled": true "enabled": true
} }
}, },
@ -61,15 +61,15 @@
}, },
"realmCache": { "realmCache": {
"provider": "infinispan-locking", "provider": "default",
"infinispan-locking" : { "default" : {
"enabled": true "enabled": true
} }
}, },
"connectionsInfinispan": { "connectionsInfinispan": {
"provider": "locking", "provider": "default",
"locking": { "default": {
"cacheContainer" : "java:comp/env/infinispan/Keycloak" "cacheContainer" : "java:comp/env/infinispan/Keycloak"
} }
} }

View file

@ -24,6 +24,7 @@ import org.infinispan.configuration.global.GlobalConfigurationBuilder;
import org.infinispan.manager.DefaultCacheManager; import org.infinispan.manager.DefaultCacheManager;
import org.infinispan.manager.EmbeddedCacheManager; import org.infinispan.manager.EmbeddedCacheManager;
import org.infinispan.transaction.LockingMode; import org.infinispan.transaction.LockingMode;
import org.infinispan.transaction.TransactionMode;
import org.infinispan.transaction.lookup.DummyTransactionManagerLookup; import org.infinispan.transaction.lookup.DummyTransactionManagerLookup;
import org.jboss.logging.Logger; import org.jboss.logging.Logger;
import org.keycloak.Config; import org.keycloak.Config;
@ -154,6 +155,15 @@ public class DefaultInfinispanConnectionProviderFactory implements InfinispanCon
} }
Configuration replicationCacheConfiguration = replicationConfigBuilder.build(); Configuration replicationCacheConfiguration = replicationConfigBuilder.build();
cacheManager.defineConfiguration(InfinispanConnectionProvider.WORK_CACHE_NAME, replicationCacheConfiguration); cacheManager.defineConfiguration(InfinispanConnectionProvider.WORK_CACHE_NAME, replicationCacheConfiguration);
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(InfinispanConnectionProvider.VERSION_CACHE_NAME, counterCacheConfiguration);
} }
} }

View file

@ -25,6 +25,7 @@ import org.keycloak.provider.Provider;
*/ */
public interface InfinispanConnectionProvider extends Provider { public interface InfinispanConnectionProvider extends Provider {
public static final String VERSION_CACHE_NAME = "realmVersions";
static final String REALM_CACHE_NAME = "realms"; static final String REALM_CACHE_NAME = "realms";
static final String USER_CACHE_NAME = "users"; static final String USER_CACHE_NAME = "users";
static final String SESSION_CACHE_NAME = "sessions"; static final String SESSION_CACHE_NAME = "sessions";

View file

@ -45,7 +45,7 @@ public class ClientAdapter implements ClientModel {
private void getDelegateForUpdate() { private void getDelegateForUpdate() {
if (updated == null) { if (updated == null) {
cacheSession.registerApplicationInvalidation(getId()); cacheSession.registerClientInvalidation(getId());
updated = cacheSession.getDelegate().getClientById(getId(), cachedRealm); updated = cacheSession.getDelegate().getClientById(getId(), cachedRealm);
if (updated == null) throw new IllegalStateException("Not found in database"); if (updated == null) throw new IllegalStateException("Not found in database");
} }
@ -377,7 +377,6 @@ public class ClientAdapter implements ClientModel {
public void setClientId(String clientId) { public void setClientId(String clientId) {
getDelegateForUpdate(); getDelegateForUpdate();
updated.setClientId(clientId); updated.setClientId(clientId);
cacheSession.registerRealmInvalidation(cachedRealm.getId());
} }
@Override @Override

View file

@ -1,396 +0,0 @@
/*
* Copyright 2016 Red Hat, Inc. and/or its affiliates
* and other contributors as indicated by the @author tags.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.keycloak.models.cache.infinispan;
import org.keycloak.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 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 DefaultCacheRealmProvider implements CacheRealmProvider {
protected RealmCache 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 DefaultCacheRealmProvider(RealmCache 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.invalidateRealmById(id);
}
for (String id : roleInvalidations) {
cache.invalidateRoleById(id);
}
for (String id : groupInvalidations) {
cache.invalidateGroupById(id);
}
for (String id : appInvalidations) {
cache.invalidateClientById(id);
}
for (String id : clientTemplateInvalidations) {
cache.invalidateClientTemplateById(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.getRealm(id);
if (cached == null) {
RealmModel model = getDelegate().getRealm(id);
if (model == null) return null;
if (realmInvalidations.contains(id)) return model;
cached = new CachedRealm(cache, this, model);
cache.addRealm(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.getRealmByName(name);
if (cached == null) {
RealmModel model = getDelegate().getRealmByName(name);
if (model == null) return null;
if (realmInvalidations.contains(model.getId())) return model;
cached = new CachedRealm(cache, this, model);
cache.addRealm(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.invalidateRealmById(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) {
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.addRole(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) {
GroupModel model = getDelegate().getGroupById(id, realm);
if (model == null) return null;
if (groupInvalidations.contains(id)) return model;
cached = new CachedGroup(realm, model);
cache.addGroup(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.getClient(id);
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.addClient(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 ClientModel getClientByClientId(String clientId, RealmModel realm) {
return getDelegate().getClientByClientId(clientId, realm);
}
@Override
public boolean removeClient(String id, RealmModel realm) {
ClientModel client = getClientById(id, realm);
if (client == null) return false;
registerApplicationInvalidation(id);
registerRealmInvalidation(realm.getId());
cache.invalidateClientById(id);
cache.invalidateRealmById(realm.getId());
Set<RoleModel> roles = client.getRoles();
for (RoleModel role : roles) {
registerRoleInvalidation(role.getId());
}
return getDelegate().removeClient(id, realm);
}
@Override
public ClientTemplateModel getClientTemplateById(String id, RealmModel realm) {
CachedClientTemplate cached = cache.getClientTemplate(id);
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.addClientTemplate(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

@ -36,6 +36,8 @@ import org.keycloak.models.cache.CacheRealmProvider;
import org.keycloak.models.cache.CacheRealmProviderFactory; import org.keycloak.models.cache.CacheRealmProviderFactory;
import org.keycloak.models.cache.entities.CachedClient; import org.keycloak.models.cache.entities.CachedClient;
import org.keycloak.models.cache.entities.CachedRealm; import org.keycloak.models.cache.entities.CachedRealm;
import org.keycloak.models.cache.infinispan.stream.StreamCacheRealmProvider;
import org.keycloak.models.cache.infinispan.stream.StreamRealmCache;
import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentHashMap;
@ -47,14 +49,12 @@ public class InfinispanCacheRealmProviderFactory implements CacheRealmProviderFa
private static final Logger log = Logger.getLogger(InfinispanCacheRealmProviderFactory.class); private static final Logger log = Logger.getLogger(InfinispanCacheRealmProviderFactory.class);
protected volatile InfinispanRealmCache realmCache; protected volatile StreamRealmCache realmCache;
protected final ConcurrentHashMap<String, String> realmLookup = new ConcurrentHashMap<>();
@Override @Override
public CacheRealmProvider create(KeycloakSession session) { public CacheRealmProvider create(KeycloakSession session) {
lazyInit(session); lazyInit(session);
return new DefaultCacheRealmProvider(realmCache, session); return new StreamCacheRealmProvider(realmCache, session);
} }
private void lazyInit(KeycloakSession session) { private void lazyInit(KeycloakSession session) {
@ -62,8 +62,8 @@ public class InfinispanCacheRealmProviderFactory implements CacheRealmProviderFa
synchronized (this) { synchronized (this) {
if (realmCache == null) { if (realmCache == null) {
Cache<String, Object> cache = session.getProvider(InfinispanConnectionProvider.class).getCache(InfinispanConnectionProvider.REALM_CACHE_NAME); Cache<String, Object> cache = session.getProvider(InfinispanConnectionProvider.class).getCache(InfinispanConnectionProvider.REALM_CACHE_NAME);
cache.addListener(new CacheListener()); Cache<String, Long> revisions = session.getProvider(InfinispanConnectionProvider.class).getCache(InfinispanConnectionProvider.VERSION_CACHE_NAME);
realmCache = new InfinispanRealmCache(cache, realmLookup); realmCache = new StreamRealmCache(cache, revisions);
} }
} }
} }
@ -84,77 +84,7 @@ public class InfinispanCacheRealmProviderFactory implements CacheRealmProviderFa
@Override @Override
public String getId() { public String getId() {
return "infinispan"; return "default";
} }
@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.evictRoleById(r);
}
for (String c : realm.getClients()) {
realmCache.evictClientById(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.evictRoleById(r);
}
log.tracev("Client removed client={0}", client.getId());
}
}
}
} }

View file

@ -78,7 +78,7 @@ public class InfinispanCacheUserProviderFactory implements CacheUserProviderFact
@Override @Override
public String getId() { public String getId() {
return "infinispan"; return "default";
} }
@Listener @Listener

View file

@ -1,202 +0,0 @@
/*
* Copyright 2016 Red Hat, Inc. and/or its affiliates
* and other contributors as indicated by the @author tags.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.keycloak.models.cache.infinispan;
import org.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;
/**
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
*/
public class InfinispanRealmCache implements RealmCache {
protected static final Logger logger = Logger.getLogger(InfinispanRealmCache.class);
protected final Cache<String, Object> cache;
protected final ConcurrentHashMap<String, String> realmLookup;
public InfinispanRealmCache(Cache<String, Object> cache, ConcurrentHashMap<String, String> realmLookup) {
this.cache = cache;
this.realmLookup = realmLookup;
}
public Cache<String, Object> getCache() {
return cache;
}
@Override
public void clear() {
cache.clear();
}
@Override
public CachedRealm getRealm(String id) {
return get(id, CachedRealm.class);
}
@Override
public void invalidateRealm(CachedRealm realm) {
logger.tracev("Invalidating realm {0}", realm.getId());
cache.remove(realm.getId());
realmLookup.remove(realm.getName());
}
@Override
public void invalidateRealmById(String id) {
CachedRealm cached = (CachedRealm) cache.remove(id);
if (cached != null) realmLookup.remove(cached.getName());
}
@Override
public void addRealm(CachedRealm realm) {
logger.tracev("Adding realm {0}", realm.getId());
cache.putForExternalRead(realm.getId(), realm);
realmLookup.put(realm.getName(), realm.getId());
}
@Override
public CachedRealm getRealmByName(String name) {
String id = realmLookup.get(name);
return id != null ? getRealm(id) : null;
}
@Override
public CachedClient getClient(String id) {
return get(id, CachedClient.class);
}
@Override
public void invalidateClient(CachedClient app) {
logger.tracev("Removing application {0}", app.getId());
cache.remove(app.getId());
}
@Override
public void addClient(CachedClient app) {
logger.tracev("Adding application {0}", app.getId());
cache.putForExternalRead(app.getId(), app);
}
@Override
public void invalidateClientById(String id) {
logger.tracev("Removing application {0}", id);
cache.remove(id);
}
@Override
public void evictClientById(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());
cache.remove(role.getId());
}
@Override
public void addGroup(CachedGroup role) {
logger.tracev("Adding group {0}", role.getId());
cache.putForExternalRead(role.getId(), role);
}
@Override
public void invalidateGroupById(String id) {
logger.tracev("Removing group {0}", id);
cache.remove(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());
cache.remove(role.getId());
}
@Override
public void invalidateRoleById(String id) {
logger.tracev("Removing role {0}", id);
cache.remove(id);
}
@Override
public void evictRoleById(String id) {
logger.tracev("Evicting role {0}", id);
cache.evict(id);
}
@Override
public void addRole(CachedRole role) {
logger.tracev("Adding role {0}", role.getId());
cache.putForExternalRead(role.getId(), role);
}
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());
cache.remove(app.getId());
}
@Override
public void addClientTemplate(CachedClientTemplate app) {
logger.tracev("Adding client template {0}", app.getId());
cache.putForExternalRead(app.getId(), app);
}
@Override
public void invalidateClientTemplateById(String id) {
logger.tracev("Removing client template {0}", id);
cache.remove(id);
}
@Override
public void evictClientTemplateById(String id) {
logger.tracev("Evicting client template {0}", id);
cache.evict(id);
}
}

View file

@ -27,7 +27,7 @@ import org.keycloak.models.cache.entities.CachedUser;
*/ */
public class InfinispanUserCache implements UserCache { public class InfinispanUserCache implements UserCache {
protected static final Logger logger = Logger.getLogger(InfinispanRealmCache.class); protected static final Logger logger = Logger.getLogger(InfinispanUserCache.class);
protected volatile boolean enabled = true; protected volatile boolean enabled = true;

View file

@ -589,40 +589,23 @@ public class RealmAdapter implements RealmModel {
@Override @Override
public List<ClientModel> getClients() { public List<ClientModel> getClients() {
if (updated != null) return updated.getClients(); return cacheSession.getClients(this);
List<ClientModel> apps = new LinkedList<>();
for (String id : cached.getClients()) {
ClientModel model = cacheSession.getClientById(id, this);
if (model == null) {
throw new IllegalStateException("Cached application not found: " + id);
}
apps.add(model);
}
return Collections.unmodifiableList(apps);
} }
@Override @Override
public ClientModel addClient(String name) { public ClientModel addClient(String name) {
getDelegateForUpdate(); return cacheSession.addClient(this, name);
ClientModel app = updated.addClient(name);
cacheSession.registerApplicationInvalidation(app.getId());
return app;
} }
@Override @Override
public ClientModel addClient(String id, String clientId) { public ClientModel addClient(String id, String clientId) {
getDelegateForUpdate(); return cacheSession.addClient(this, id, clientId);
ClientModel app = updated.addClient(id, clientId);
cacheSession.registerApplicationInvalidation(app.getId());
return app;
} }
@Override @Override
public boolean removeClient(String id) { public boolean removeClient(String id) {
cacheSession.registerApplicationInvalidation(id); return cacheSession.removeClient(id, this);
getDelegateForUpdate();
return updated.removeClient(id);
} }
@Override @Override

View file

@ -125,7 +125,7 @@ public class RoleAdapter implements RoleModel {
for (String id : cached.getComposites()) { for (String id : cached.getComposites()) {
RoleModel role = realm.getRoleById(id); RoleModel role = realm.getRoleById(id);
if (role == null) { if (role == null) {
throw new IllegalStateException("Could not find composite: " + id); throw new IllegalStateException("Could not find composite in role " + getName() + ": " + id);
} }
set.add(role); set.add(role);
} }
@ -138,7 +138,7 @@ public class RoleAdapter implements RoleModel {
return realm; return realm;
} else { } else {
CachedClientRole appRole = (CachedClientRole)cached; CachedClientRole appRole = (CachedClientRole)cached;
return realm.getClientById(appRole.getIdClient()); return realm.getClientById(appRole.getClientId());
} }
} }

View file

@ -1,159 +0,0 @@
/*
* Copyright 2016 Red Hat, Inc. and/or its affiliates
* and other contributors as indicated by the @author tags.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.keycloak.models.cache.infinispan.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;
/**
* @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;
@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);
}
}
}
}
@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;
realmCache.getRealmLookup().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;
realmCache.getRealmLookup().remove(realm.getName());
for (String r : realm.getRealmRoles().values()) {
realmCache.evictRoleById(r);
}
for (String c : realm.getClients()) {
realmCache.evictClientById(c);
}
log.tracev("Realm removed realm={0}", realm.getName());
} else if (object instanceof CachedClient) {
CachedClient client = (CachedClient) object;
realmCache.getClientLookup().remove(client.getRealm() + "." + client.getClientId());
for (String r : client.getRoles().values()) {
realmCache.evictRoleById(r);
}
log.tracev("Client removed client={0}", client.getId());
}
}
}
}

View file

@ -1,54 +0,0 @@
/*
* Copyright 2016 Red Hat, Inc. and/or its affiliates
* and other contributors as indicated by the @author tags.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.keycloak.models.cache.infinispan.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

@ -1,300 +0,0 @@
/*
* Copyright 2016 Red Hat, Inc. and/or its affiliates
* and other contributors as indicated by the @author tags.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.keycloak.models.cache.infinispan.locking;
import org.infinispan.Cache;
import org.jboss.logging.Logger;
import org.keycloak.models.RealmModel;
import org.keycloak.models.cache.RealmCache;
import org.keycloak.models.cache.entities.CachedClient;
import org.keycloak.models.cache.entities.CachedClientTemplate;
import org.keycloak.models.cache.entities.CachedGroup;
import org.keycloak.models.cache.entities.CachedRealm;
import org.keycloak.models.cache.entities.CachedRole;
import java.util.concurrent.ConcurrentHashMap;
/**
* @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;
protected final ConcurrentHashMap<String, String> realmLookup = new ConcurrentHashMap<>();
protected final ConcurrentHashMap<String, String> clientLookup = new ConcurrentHashMap<>();
public LockingRealmCache(Cache<String, Object> cache, Cache<String, Long> revisions) {
this.cache = cache;
this.revisions = revisions;
}
public Cache<String, Object> getCache() {
return cache;
}
public Cache<String, Long> getRevisions() {
return revisions;
}
public ConcurrentHashMap<String, String> getRealmLookup() {
return realmLookup;
}
public ConcurrentHashMap<String, String> getClientLookup() {
return clientLookup;
}
public Long getCurrentRevision(String id) {
return revisions.get(id);
}
public void endRevisionBatch() {
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) {
Object removed = cache.remove(id);
revisions.put(id, UpdateCounter.next());
return removed;
}
protected void addRevisioned(String id, Revisioned object) {
//startRevisionBatch();
try {
//revisions.getAdvancedCache().lock(id);
Long rev = revisions.get(id);
if (rev == null) {
rev = UpdateCounter.current();
revisions.put(id, rev);
}
revisions.startBatch();
if (!revisions.getAdvancedCache().lock(id)) {
logger.trace("Could not obtain version lock");
}
rev = revisions.get(id);
if (rev == null) {
return;
}
if (rev.equals(object.getRevision())) {
cache.putForExternalRead(id, object);
return;
}
if (rev > object.getRevision()) { // revision is ahead, don't cache
return;
}
// revisions cache has a lower value than the object.revision, so update revision and add it to cache
revisions.put(id, object.getRevision());
cache.putForExternalRead(id, object);
} finally {
endRevisionBatch();
}
}
@Override
public void clear() {
cache.clear();
}
@Override
public CachedRealm getRealm(String id) {
return get(id, CachedRealm.class);
}
@Override
public void invalidateRealm(CachedRealm realm) {
logger.tracev("Invalidating realm {0}", realm.getId());
invalidateObject(realm.getId());
realmLookup.remove(realm.getName());
}
@Override
public void invalidateRealmById(String id) {
CachedRealm cached = (CachedRealm) invalidateObject(id);
if (cached != null) realmLookup.remove(cached.getName());
}
@Override
public void addRealm(CachedRealm realm) {
logger.tracev("Adding realm {0}", realm.getId());
addRevisioned(realm.getId(), (Revisioned) realm);
realmLookup.put(realm.getName(), realm.getId());
}
@Override
public CachedRealm getRealmByName(String name) {
String id = realmLookup.get(name);
return id != null ? getRealm(id) : null;
}
@Override
public CachedClient getClient(String id) {
return get(id, CachedClient.class);
}
public CachedClient getClientByClientId(RealmModel realm, String clientId) {
String id = clientLookup.get(realm.getId() + "." + clientId);
return id != null ? getClient(id) : null;
}
@Override
public void invalidateClient(CachedClient app) {
logger.tracev("Removing application {0}", app.getId());
invalidateObject(app.getId());
clientLookup.remove(getClientIdKey(app));
}
@Override
public void addClient(CachedClient app) {
logger.tracev("Adding application {0}", app.getId());
addRevisioned(app.getId(), (Revisioned) app);
clientLookup.put(getClientIdKey(app), app.getId());
}
@Override
public void invalidateClientById(String id) {
CachedClient client = (CachedClient)invalidateObject(id);
if (client != null) {
logger.tracev("Removing application {0}", client.getClientId());
clientLookup.remove(getClientIdKey(client));
}
}
protected String getClientIdKey(CachedClient client) {
return client.getRealm() + "." + client.getClientId();
}
@Override
public void evictClientById(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 addGroup(CachedGroup role) {
logger.tracev("Adding group {0}", role.getId());
addRevisioned(role.getId(), (Revisioned) role);
}
@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 evictRoleById(String id) {
logger.tracev("Evicting role {0}", id);
cache.evict(id);
}
@Override
public void addRole(CachedRole role) {
logger.tracev("Adding role {0}", role.getId());
addRevisioned(role.getId(), (Revisioned) role);
}
@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 addClientTemplate(CachedClientTemplate app) {
logger.tracev("Adding client template {0}", app.getId());
addRevisioned(app.getId(), (Revisioned) app);
}
@Override
public void invalidateClientTemplateById(String id) {
logger.tracev("Removing client template {0}", id);
invalidateObject(id);
}
@Override
public void evictClientTemplateById(String id) {
logger.tracev("Evicting client template {0}", id);
cache.evict(id);
}
}

View file

@ -0,0 +1,50 @@
package org.keycloak.models.cache.infinispan.stream;
import org.keycloak.models.RealmModel;
import org.keycloak.models.cache.infinispan.stream.entities.AbstractRevisioned;
import java.util.HashSet;
import java.util.Set;
/**
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
* @version $Revision: 1 $
*/
public class ClientListQuery extends AbstractRevisioned implements ClientQuery {
private final Set<String> clients;
private final String realm;
private final String realmName;
public ClientListQuery(Long revisioned, String id, RealmModel realm, Set<String> clients) {
super(revisioned, id);
this.realm = realm.getId();
this.realmName = realm.getName();
this.clients = clients;
}
public ClientListQuery(Long revisioned, String id, RealmModel realm, String client) {
super(revisioned, id);
this.realm = realm.getId();
this.realmName = realm.getName();
this.clients = new HashSet<>();
this.clients.add(client);
}
@Override
public Set<String> getClients() {
return clients;
}
@Override
public String getRealm() {
return realm;
}
@Override
public String toString() {
return "ClientListQuery{" +
"id='" + getId() + "'" +
"realmName='" + realmName + '\'' +
'}';
}
}

View file

@ -0,0 +1,14 @@
package org.keycloak.models.cache.infinispan.stream;
import org.keycloak.models.cache.infinispan.stream.entities.InRealm;
import java.util.List;
import java.util.Set;
/**
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
* @version $Revision: 1 $
*/
public interface ClientQuery extends InRealm {
Set<String> getClients();
}

View file

@ -0,0 +1,46 @@
package org.keycloak.models.cache.infinispan.stream;
import org.jboss.logging.Logger;
import java.io.Serializable;
import java.util.Map;
import java.util.function.Predicate;
/**
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
* @version $Revision: 1 $
*/
public class ClientQueryPredicate implements Predicate<Map.Entry<String, Object>>, Serializable {
protected static final Logger logger = Logger.getLogger(ClientQueryPredicate.class);
private String client;
private String inRealm;
public static ClientQueryPredicate create() {
return new ClientQueryPredicate();
}
public ClientQueryPredicate client(String client) {
this.client = client;
return this;
}
public ClientQueryPredicate inRealm(String inRealm) {
this.inRealm = inRealm;
return this;
}
@Override
public boolean test(Map.Entry<String, Object> entry) {
Object value = entry.getValue();
if (value == null) return false;
if (!(value instanceof ClientQuery)) return false;
ClientQuery query = (ClientQuery)value;
if (client != null && !query.getClients().contains(client)) return false;
if (inRealm != null && !query.getRealm().equals(inRealm)) return false;
return true;
}
}

View file

@ -0,0 +1,13 @@
package org.keycloak.models.cache.infinispan.stream;
import org.keycloak.models.cache.infinispan.stream.entities.InRealm;
import java.util.Set;
/**
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
* @version $Revision: 1 $
*/
public interface ClientTemplateQuery extends InRealm {
Set<String> getTemplates();
}

View file

@ -0,0 +1,37 @@
package org.keycloak.models.cache.infinispan.stream;
import java.io.Serializable;
import java.util.Map;
import java.util.function.Predicate;
/**
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
* @version $Revision: 1 $
*/
public class ClientTemplateQueryPredicate implements Predicate<Map.Entry<String, Object>>, Serializable {
private String template;
public static ClientTemplateQueryPredicate create() {
return new ClientTemplateQueryPredicate();
}
public ClientTemplateQueryPredicate template(String template) {
this.template = template;
return this;
}
@Override
public boolean test(Map.Entry<String, Object> entry) {
Object value = entry.getValue();
if (value == null) return false;
if (!(value instanceof ClientTemplateQuery)) return false;
ClientTemplateQuery query = (ClientTemplateQuery)value;
return query.getTemplates().contains(template);
}
}

View file

@ -0,0 +1,13 @@
package org.keycloak.models.cache.infinispan.stream;
import org.keycloak.models.cache.infinispan.stream.entities.InRealm;
import java.util.Set;
/**
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
* @version $Revision: 1 $
*/
public interface GroupQuery extends InRealm {
Set<String> getGroups();
}

View file

@ -0,0 +1,37 @@
package org.keycloak.models.cache.infinispan.stream;
import java.io.Serializable;
import java.util.Map;
import java.util.function.Predicate;
/**
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
* @version $Revision: 1 $
*/
public class GroupQueryPredicate implements Predicate<Map.Entry<String, Object>>, Serializable {
private String group;
public static GroupQueryPredicate create() {
return new GroupQueryPredicate();
}
public GroupQueryPredicate group(String group) {
this.group = group;
return this;
}
@Override
public boolean test(Map.Entry<String, Object> entry) {
Object value = entry.getValue();
if (value == null) return false;
if (!(value instanceof GroupQuery)) return false;
GroupQuery query = (GroupQuery)value;
return query.getGroups().contains(group);
}
}

View file

@ -0,0 +1,40 @@
package org.keycloak.models.cache.infinispan.stream;
import org.keycloak.models.cache.entities.CachedGroup;
import org.keycloak.models.cache.entities.CachedRole;
import java.io.Serializable;
import java.util.Map;
import java.util.function.Predicate;
/**
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
* @version $Revision: 1 $
*/
public class HasRolePredicate implements Predicate<Map.Entry<String, Object>>, Serializable {
private String role;
public static HasRolePredicate create() {
return new HasRolePredicate();
}
public HasRolePredicate role(String role) {
this.role = role;
return this;
}
@Override
public boolean test(Map.Entry<String, Object> entry) {
Object value = entry.getValue();
if (value == null) return false;
if (value instanceof CachedRole) {
CachedRole cachedRole = (CachedRole)value;
if (cachedRole.getComposites().contains(role)) return true;
}
if (value instanceof CachedGroup) {
CachedGroup cachedRole = (CachedGroup)value;
if (cachedRole.getRoleMappings().contains(role)) return true;
}
return false;
}
}

View file

@ -0,0 +1,34 @@
package org.keycloak.models.cache.infinispan.stream;
import org.keycloak.models.cache.infinispan.stream.entities.InClient;
import org.keycloak.models.cache.infinispan.stream.entities.InRealm;
import java.io.Serializable;
import java.util.Map;
import java.util.function.Predicate;
/**
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
* @version $Revision: 1 $
*/
public class InClientPredicate implements Predicate<Map.Entry<String, Object>>, Serializable {
private String clientId;
public static InClientPredicate create() {
return new InClientPredicate();
}
public InClientPredicate client(String id) {
clientId = id;
return this;
}
@Override
public boolean test(Map.Entry<String, Object> entry) {
Object value = entry.getValue();
if (value == null) return false;
if (!(value instanceof InClient)) return false;
return clientId.equals(((InClient)value).getClientId());
}
}

View file

@ -0,0 +1,33 @@
package org.keycloak.models.cache.infinispan.stream;
import org.keycloak.models.cache.infinispan.stream.entities.InRealm;
import java.io.Serializable;
import java.util.Map;
import java.util.function.Predicate;
/**
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
* @version $Revision: 1 $
*/
public class InRealmPredicate implements Predicate<Map.Entry<String, Object>>, Serializable {
private String realm;
public static InRealmPredicate create() {
return new InRealmPredicate();
}
public InRealmPredicate realm(String id) {
realm = id;
return this;
}
@Override
public boolean test(Map.Entry<String, Object> entry) {
Object value = entry.getValue();
if (value == null) return false;
if (!(value instanceof InRealm)) return false;
return realm.equals(((InRealm)value).getRealm());
}
}

View file

@ -0,0 +1,29 @@
package org.keycloak.models.cache.infinispan.stream;
import org.keycloak.models.cache.infinispan.stream.entities.AbstractRevisioned;
import java.util.HashSet;
import java.util.Set;
/**
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
* @version $Revision: 1 $
*/
public class RealmListQuery extends AbstractRevisioned implements RealmQuery {
private final Set<String> realms;
public RealmListQuery(Long revision, String id, String realm) {
super(revision, id);
realms = new HashSet<>();
realms.add(realm);
}
public RealmListQuery(Long revision, String id, Set<String> realms) {
super(revision, id);
this.realms = realms;
}
@Override
public Set<String> getRealms() {
return realms;
}
}

View file

@ -0,0 +1,13 @@
package org.keycloak.models.cache.infinispan.stream;
import org.keycloak.models.cache.infinispan.stream.entities.InRealm;
import java.util.Set;
/**
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
* @version $Revision: 1 $
*/
public interface RealmQuery {
Set<String> getRealms();
}

View file

@ -0,0 +1,37 @@
package org.keycloak.models.cache.infinispan.stream;
import java.io.Serializable;
import java.util.Map;
import java.util.function.Predicate;
/**
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
* @version $Revision: 1 $
*/
public class RealmQueryPredicate implements Predicate<Map.Entry<String, Object>>, Serializable {
private String realm;
public static RealmQueryPredicate create() {
return new RealmQueryPredicate();
}
public RealmQueryPredicate realm(String realm) {
this.realm = realm;
return this;
}
@Override
public boolean test(Map.Entry<String, Object> entry) {
Object value = entry.getValue();
if (value == null) return false;
if (!(value instanceof RealmQuery)) return false;
RealmQuery query = (RealmQuery)value;
return query.getRealms().contains(realm);
}
}

View file

@ -0,0 +1,13 @@
package org.keycloak.models.cache.infinispan.stream;
import org.keycloak.models.cache.infinispan.stream.entities.InRealm;
import java.util.Set;
/**
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
* @version $Revision: 1 $
*/
public interface RoleQuery extends InRealm {
Set<String> getRoles();
}

View file

@ -0,0 +1,37 @@
package org.keycloak.models.cache.infinispan.stream;
import java.io.Serializable;
import java.util.Map;
import java.util.function.Predicate;
/**
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
* @version $Revision: 1 $
*/
public class RoleQueryPredicate implements Predicate<Map.Entry<String, Object>>, Serializable {
private String role;
public static RoleQueryPredicate create() {
return new RoleQueryPredicate();
}
public RoleQueryPredicate role(String role) {
this.role = role;
return this;
}
@Override
public boolean test(Map.Entry<String, Object> entry) {
Object value = entry.getValue();
if (value == null) return false;
if (!(value instanceof RoleQuery)) return false;
RoleQuery query = (RoleQuery)value;
return query.getRoles().contains(role);
}
}

View file

@ -15,7 +15,7 @@
* limitations under the License. * limitations under the License.
*/ */
package org.keycloak.models.cache.infinispan.locking; package org.keycloak.models.cache.infinispan.stream;
import org.jboss.logging.Logger; import org.jboss.logging.Logger;
import org.keycloak.migration.MigrationModel; import org.keycloak.migration.MigrationModel;
@ -28,22 +28,19 @@ import org.keycloak.models.RealmModel;
import org.keycloak.models.RealmProvider; import org.keycloak.models.RealmProvider;
import org.keycloak.models.RoleModel; import org.keycloak.models.RoleModel;
import org.keycloak.models.cache.CacheRealmProvider; 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.entities.CachedRole;
import org.keycloak.models.cache.infinispan.ClientAdapter; import org.keycloak.models.cache.infinispan.ClientAdapter;
import org.keycloak.models.cache.infinispan.ClientTemplateAdapter; import org.keycloak.models.cache.infinispan.ClientTemplateAdapter;
import org.keycloak.models.cache.infinispan.GroupAdapter; import org.keycloak.models.cache.infinispan.GroupAdapter;
import org.keycloak.models.cache.infinispan.RealmAdapter; import org.keycloak.models.cache.infinispan.RealmAdapter;
import org.keycloak.models.cache.infinispan.RoleAdapter; import org.keycloak.models.cache.infinispan.RoleAdapter;
import org.keycloak.models.cache.infinispan.locking.entities.RevisionedCachedClient; import org.keycloak.models.cache.infinispan.stream.entities.Revisioned;
import org.keycloak.models.cache.infinispan.locking.entities.RevisionedCachedClientRole; import org.keycloak.models.cache.infinispan.stream.entities.RevisionedCachedClient;
import org.keycloak.models.cache.infinispan.locking.entities.RevisionedCachedClientTemplate; import org.keycloak.models.cache.infinispan.stream.entities.RevisionedCachedClientRole;
import org.keycloak.models.cache.infinispan.locking.entities.RevisionedCachedGroup; import org.keycloak.models.cache.infinispan.stream.entities.RevisionedCachedClientTemplate;
import org.keycloak.models.cache.infinispan.locking.entities.RevisionedCachedRealm; import org.keycloak.models.cache.infinispan.stream.entities.RevisionedCachedGroup;
import org.keycloak.models.cache.infinispan.locking.entities.RevisionedCachedRealmRole; import org.keycloak.models.cache.infinispan.stream.entities.RevisionedCachedRealm;
import org.keycloak.models.cache.infinispan.stream.entities.RevisionedCachedRealmRole;
import java.util.Collections; import java.util.Collections;
import java.util.HashMap; import java.util.HashMap;
@ -55,34 +52,36 @@ import java.util.Set;
/** /**
*
* - any relationship should be resolved from session.realms(). For example if JPA.getClientByClientId() is invoked,
* JPA should find the id of the client and then call session.realms().getClientById(). THis is to ensure that the cached
* object is invoked and all proper invalidation are being invoked.
*
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a> * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
* @version $Revision: 1 $ * @version $Revision: 1 $
*/ */
public class LockingCacheRealmProvider implements CacheRealmProvider { public class StreamCacheRealmProvider implements CacheRealmProvider {
protected static final Logger logger = Logger.getLogger(LockingCacheRealmProvider.class); protected static final Logger logger = Logger.getLogger(StreamCacheRealmProvider.class);
protected LockingRealmCache cache; public static final String REALM_CLIENTS_QUERY_SUFFIX = ".realm.clients";
protected StreamRealmCache cache;
protected KeycloakSession session; protected KeycloakSession session;
protected RealmProvider delegate; protected RealmProvider delegate;
protected boolean transactionActive; protected boolean transactionActive;
protected boolean setRollbackOnly; 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, RealmModel> managedRealms = new HashMap<>();
protected Map<String, ClientModel> managedApplications = new HashMap<>(); protected Map<String, ClientModel> managedApplications = new HashMap<>();
protected Map<String, ClientTemplateModel> managedClientTemplates = new HashMap<>(); protected Map<String, ClientTemplateModel> managedClientTemplates = new HashMap<>();
protected Map<String, RoleModel> managedRoles = new HashMap<>(); protected Map<String, RoleModel> managedRoles = new HashMap<>();
protected Map<String, GroupModel> managedGroups = new HashMap<>(); protected Map<String, GroupModel> managedGroups = new HashMap<>();
protected Set<String> clientListInvalidations = new HashSet<>();
protected Set<String> invalidations = new HashSet<>();
protected boolean clearAll; protected boolean clearAll;
public LockingCacheRealmProvider(LockingRealmCache cache, KeycloakSession session) { public StreamCacheRealmProvider(StreamRealmCache cache, KeycloakSession session) {
this.cache = cache; this.cache = cache;
this.session = session; this.session = session;
session.getTransaction().enlistPrepare(getPrepareTransaction()); session.getTransaction().enlistPrepare(getPrepareTransaction());
session.getTransaction().enlistAfterCompletion(getAfterTransaction()); session.getTransaction().enlistAfterCompletion(getAfterTransaction());
} }
@ -105,50 +104,38 @@ public class LockingCacheRealmProvider implements CacheRealmProvider {
return delegate; return delegate;
} }
public LockingRealmCache getCache() {
return cache;
}
@Override @Override
public void registerRealmInvalidation(String id) { public void registerRealmInvalidation(String id) {
realmInvalidations.add(id); invalidations.add(id);
cache.realmInvalidation(id, invalidations);
} }
@Override @Override
public void registerApplicationInvalidation(String id) { public void registerClientInvalidation(String id) {
appInvalidations.add(id); invalidations.add(id);
cache.clientInvalidation(id, invalidations);
} }
@Override @Override
public void registerClientTemplateInvalidation(String id) { public void registerClientTemplateInvalidation(String id) {
clientTemplateInvalidations.add(id); invalidations.add(id);
cache.clientTemplateInvalidation(id, invalidations);
} }
@Override @Override
public void registerRoleInvalidation(String id) { public void registerRoleInvalidation(String id) {
roleInvalidations.add(id); invalidations.add(id);
cache.roleInvalidation(id, invalidations);
} }
@Override @Override
public void registerGroupInvalidation(String id) { public void registerGroupInvalidation(String id) {
groupInvalidations.add(id); invalidations.add(id);
cache.groupInvalidation(id, invalidations);
} }
protected void runInvalidations() { protected void runInvalidations() {
for (String id : realmInvalidations) { for (String id : invalidations) {
cache.invalidateRealmById(id); cache.invalidateObject(id);
}
for (String id : roleInvalidations) {
cache.invalidateRoleById(id);
}
for (String id : groupInvalidations) {
cache.invalidateGroupById(id);
}
for (String id : appInvalidations) {
cache.invalidateClientById(id);
}
for (String id : clientTemplateInvalidations) {
cache.invalidateClientTemplateById(id);
} }
} }
@ -162,27 +149,14 @@ public class LockingCacheRealmProvider implements CacheRealmProvider {
@Override @Override
public void commit() { public void commit() {
if (delegate == null) return; if (delegate == null) return;
List<String> invalidates = new LinkedList<>(); List<String> locks = new LinkedList<>();
for (String id : realmInvalidations) { locks.addAll(invalidations);
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 Collections.sort(locks); // lock ordering
cache.getRevisions().startBatch(); cache.getRevisions().startBatch();
for (String id : invalidates) { //if (!invalidates.isEmpty()) cache.getRevisions().getAdvancedCache().lock(invalidates);
cache.getRevisions().getAdvancedCache().lock(id); for (String lock : locks) {
boolean success = cache.getRevisions().getAdvancedCache().lock(lock);
} }
} }
@ -275,19 +249,18 @@ public class LockingCacheRealmProvider implements CacheRealmProvider {
@Override @Override
public RealmModel getRealm(String id) { public RealmModel getRealm(String id) {
CachedRealm cached = cache.getRealm(id); RevisionedCachedRealm cached = cache.get(id, RevisionedCachedRealm.class);
if (cached != null) { if (cached != null) {
logger.tracev("by id cache hit: {0}", cached.getName()); logger.tracev("by id cache hit: {0}", cached.getName());
} }
if (cached == null) { if (cached == null) {
Long loaded = cache.getCurrentRevision(id); Long loaded = cache.getCurrentRevision(id);
if (loaded == null) loaded = UpdateCounter.current();
RealmModel model = getDelegate().getRealm(id); RealmModel model = getDelegate().getRealm(id);
if (model == null) return null; if (model == null) return null;
if (realmInvalidations.contains(id)) return model; if (invalidations.contains(id)) return model;
cached = new RevisionedCachedRealm(loaded, cache, this, model); cached = new RevisionedCachedRealm(loaded, null, this, model);
cache.addRealm(cached); cache.addRevisioned(cached);
} else if (realmInvalidations.contains(id)) { } else if (invalidations.contains(id)) {
return getDelegate().getRealm(id); return getDelegate().getRealm(id);
} else if (managedRealms.containsKey(id)) { } else if (managedRealms.containsKey(id)) {
return managedRealms.get(id); return managedRealms.get(id);
@ -299,25 +272,28 @@ public class LockingCacheRealmProvider implements CacheRealmProvider {
@Override @Override
public RealmModel getRealmByName(String name) { public RealmModel getRealmByName(String name) {
CachedRealm cached = cache.getRealmByName(name); String cacheKey = "realm.query.by.name." + name;
if (cached != null) { RealmListQuery query = cache.get(cacheKey, RealmListQuery.class);
logger.tracev("by name cache hit: {0}", cached.getName()); if (query != null) {
logger.tracev("realm by name cache hit: {0}", name);
} }
if (cached == null) { if (query == null) {
Long loaded = UpdateCounter.current(); Long loaded = cache.getCurrentRevision(cacheKey);
RealmModel model = getDelegate().getRealmByName(name); RealmModel model = getDelegate().getRealmByName(name);
if (model == null) return null; if (model == null) return null;
if (realmInvalidations.contains(model.getId())) return model; if (invalidations.contains(model.getId())) return model;
cached = new RevisionedCachedRealm(loaded, cache, this, model); query = new RealmListQuery(loaded, cacheKey, model.getId());
cache.addRealm(cached); cache.addRevisioned(query);
} else if (realmInvalidations.contains(cached.getId())) { return model;
} else if (invalidations.contains(cacheKey)) {
return getDelegate().getRealmByName(name); return getDelegate().getRealmByName(name);
} else if (managedRealms.containsKey(cached.getId())) { } else {
return managedRealms.get(cached.getId()); String realmId = query.getRealms().iterator().next();
if (invalidations.contains(realmId)) {
return getDelegate().getRealmByName(name);
}
return getRealm(realmId);
} }
RealmAdapter adapter = new RealmAdapter(cached, this);
managedRealms.put(cached.getId(), adapter);
return adapter;
} }
@Override @Override
@ -336,42 +312,87 @@ public class LockingCacheRealmProvider implements CacheRealmProvider {
@Override @Override
public boolean removeRealm(String id) { public boolean removeRealm(String id) {
cache.invalidateRealmById(id); if (getRealm(id) == null) return false;
RealmModel realm = getDelegate().getRealm(id); invalidations.add(getRealmClientsQueryCacheKey(id));
Set<RoleModel> realmRoles = null; cache.invalidateObject(id);
if (realm != null) { cache.realmRemoval(id, invalidations);
realmRoles = realm.getRoles(); return getDelegate().removeRealm(id);
}
@Override
public ClientModel addClient(RealmModel realm, String clientId) {
ClientModel client = getDelegate().addClient(realm, clientId);
return addedClient(realm, client);
}
@Override
public ClientModel addClient(RealmModel realm, String id, String clientId) {
ClientModel client = getDelegate().addClient(realm, id, clientId);
return addedClient(realm, client);
}
private ClientModel addedClient(RealmModel realm, ClientModel client) {
logger.trace("added Client.....");
// need to invalidate realm client query cache every time as it may not be loaded on this node, but loaded on another
invalidations.add(getRealmClientsQueryCacheKey(realm.getId()));
invalidations.add(client.getId());
cache.clientAdded(realm.getId(), client.getId(), invalidations);
clientListInvalidations.add(realm.getId());
return client;
}
private String getRealmClientsQueryCacheKey(String realm) {
return realm + REALM_CLIENTS_QUERY_SUFFIX;
}
@Override
public List<ClientModel> getClients(RealmModel realm) {
String cacheKey = getRealmClientsQueryCacheKey(realm.getId());
boolean queryDB = invalidations.contains(cacheKey) || clientListInvalidations.contains(realm.getId());
if (queryDB) {
return getDelegate().getClients(realm);
} }
boolean didIt = getDelegate().removeRealm(id); ClientListQuery query = cache.get(cacheKey, ClientListQuery.class);
realmInvalidations.add(id); if (query != null) {
logger.tracev("getClients cache hit: {0}", realm.getName());
}
// TODO: Temporary workaround to invalidate cached realm roles if (query == null) {
if (didIt && realmRoles != null) { Long loaded = cache.getCurrentRevision(cacheKey);
for (RoleModel role : realmRoles) { List<ClientModel> model = getDelegate().getClients(realm);
roleInvalidations.add(role.getId()); if (model == null) return null;
Set<String> ids = new HashSet<>();
for (ClientModel client : model) ids.add(client.getId());
query = new ClientListQuery(loaded, cacheKey, realm, ids);
logger.tracev("adding realm clients cache miss: realm {0} key {1}", realm.getName(), cacheKey);
cache.addRevisioned(query);
return model;
}
List<ClientModel> list = new LinkedList<>();
for (String id : query.getClients()) {
ClientModel client = session.realms().getClientById(id, realm);
if (client == null) {
invalidations.add(cacheKey);
return getDelegate().getClients(realm);
} }
list.add(client);
} }
return list;
return didIt;
} }
@Override @Override
public boolean removeClient(String id, RealmModel realm) { public boolean removeClient(String id, RealmModel realm) {
ClientModel client = getClientById(id, realm); ClientModel client = getClientById(id, realm);
if (client == null) return false; if (client == null) return false;
// need to invalidate realm client query cache every time client list is changed
registerApplicationInvalidation(id); invalidations.add(getRealmClientsQueryCacheKey(realm.getId()));
registerRealmInvalidation(realm.getId()); clientListInvalidations.add(realm.getId());
cache.invalidateClientById(id); registerClientInvalidation(id);
cache.invalidateRealmById(realm.getId()); cache.clientRemoval(realm.getId(), id, invalidations);
for (RoleModel role : client.getRoles()) {
cache.roleInvalidation(role.getId(), invalidations);
Set<RoleModel> roles = client.getRoles();
for (RoleModel role : roles) {
registerRoleInvalidation(role.getId());
} }
return getDelegate().removeClient(id, realm); return getDelegate().removeClient(id, realm);
} }
@ -383,51 +404,49 @@ public class LockingCacheRealmProvider implements CacheRealmProvider {
@Override @Override
public RoleModel getRoleById(String id, RealmModel realm) { public RoleModel getRoleById(String id, RealmModel realm) {
CachedRole cached = cache.getRole(id); CachedRole cached = cache.get(id, CachedRole.class);
if (cached != null && !cached.getRealm().equals(realm.getId())) { if (cached != null && !cached.getRealm().equals(realm.getId())) {
cached = null; cached = null;
} }
if (cached == null) { if (cached == null) {
Long loaded = cache.getCurrentRevision(id); Long loaded = cache.getCurrentRevision(id);
if (loaded == null) loaded = UpdateCounter.current();
RoleModel model = getDelegate().getRoleById(id, realm); RoleModel model = getDelegate().getRoleById(id, realm);
if (model == null) return null; if (model == null) return null;
if (roleInvalidations.contains(id)) return model; if (invalidations.contains(id)) return model;
if (model.getContainer() instanceof ClientModel) { if (model.getContainer() instanceof ClientModel) {
cached = new RevisionedCachedClientRole(loaded, ((ClientModel) model.getContainer()).getId(), model, realm); cached = new RevisionedCachedClientRole(loaded, ((ClientModel) model.getContainer()).getId(), model, realm);
} else { } else {
cached = new RevisionedCachedRealmRole(loaded, model, realm); cached = new RevisionedCachedRealmRole(loaded, model, realm);
} }
cache.addRole(cached); cache.addRevisioned((Revisioned)cached);
} else if (roleInvalidations.contains(id)) { } else if (invalidations.contains(id)) {
return getDelegate().getRoleById(id, realm); return getDelegate().getRoleById(id, realm);
} else if (managedRoles.containsKey(id)) { } else if (managedRoles.containsKey(id)) {
return managedRoles.get(id); return managedRoles.get(id);
} }
RoleAdapter adapter = new RoleAdapter(cached, cache, this, realm); RoleAdapter adapter = new RoleAdapter(cached, null, this, realm);
managedRoles.put(id, adapter); managedRoles.put(id, adapter);
return adapter; return adapter;
} }
@Override @Override
public GroupModel getGroupById(String id, RealmModel realm) { public GroupModel getGroupById(String id, RealmModel realm) {
CachedGroup cached = cache.getGroup(id); RevisionedCachedGroup cached = cache.get(id, RevisionedCachedGroup.class);
if (cached != null && !cached.getRealm().equals(realm.getId())) { if (cached != null && !cached.getRealm().equals(realm.getId())) {
cached = null; cached = null;
} }
if (cached == null) { if (cached == null) {
Long loaded = cache.getCurrentRevision(id); Long loaded = cache.getCurrentRevision(id);
if (loaded == null) loaded = UpdateCounter.current();
GroupModel model = getDelegate().getGroupById(id, realm); GroupModel model = getDelegate().getGroupById(id, realm);
if (model == null) return null; if (model == null) return null;
if (groupInvalidations.contains(id)) return model; if (invalidations.contains(id)) return model;
cached = new RevisionedCachedGroup(loaded, realm, model); cached = new RevisionedCachedGroup(loaded, realm, model);
cache.addGroup(cached); cache.addRevisioned(cached);
} else if (groupInvalidations.contains(id)) { } else if (invalidations.contains(id)) {
return getDelegate().getGroupById(id, realm); return getDelegate().getGroupById(id, realm);
} else if (managedGroups.containsKey(id)) { } else if (managedGroups.containsKey(id)) {
return managedGroups.get(id); return managedGroups.get(id);
@ -439,7 +458,7 @@ public class LockingCacheRealmProvider implements CacheRealmProvider {
@Override @Override
public ClientModel getClientById(String id, RealmModel realm) { public ClientModel getClientById(String id, RealmModel realm) {
CachedClient cached = cache.getClient(id); RevisionedCachedClient cached = cache.get(id, RevisionedCachedClient.class);
if (cached != null && !cached.getRealm().equals(realm.getId())) { if (cached != null && !cached.getRealm().equals(realm.getId())) {
cached = null; cached = null;
} }
@ -449,73 +468,72 @@ public class LockingCacheRealmProvider implements CacheRealmProvider {
if (cached == null) { if (cached == null) {
Long loaded = cache.getCurrentRevision(id); Long loaded = cache.getCurrentRevision(id);
if (loaded == null) loaded = UpdateCounter.current();
ClientModel model = getDelegate().getClientById(id, realm); ClientModel model = getDelegate().getClientById(id, realm);
if (model == null) return null; if (model == null) return null;
if (appInvalidations.contains(id)) return model; if (invalidations.contains(id)) return model;
cached = new RevisionedCachedClient(loaded, cache, getDelegate(), realm, model); cached = new RevisionedCachedClient(loaded, null, getDelegate(), realm, model);
logger.tracev("adding client by id cache miss: {0}", cached.getClientId()); logger.tracev("adding client by id cache miss: {0}", cached.getClientId());
cache.addClient(cached); cache.addRevisioned(cached);
} else if (appInvalidations.contains(id)) { } else if (invalidations.contains(id)) {
return getDelegate().getClientById(id, realm); return getDelegate().getClientById(id, realm);
} else if (managedApplications.containsKey(id)) { } else if (managedApplications.containsKey(id)) {
return managedApplications.get(id); return managedApplications.get(id);
} }
ClientAdapter adapter = new ClientAdapter(realm, cached, this, cache); ClientAdapter adapter = new ClientAdapter(realm, cached, this, null);
managedApplications.put(id, adapter); managedApplications.put(id, adapter);
return adapter; return adapter;
} }
@Override @Override
public ClientModel getClientByClientId(String clientId, RealmModel realm) { public ClientModel getClientByClientId(String clientId, RealmModel realm) {
CachedClient cached = cache.getClientByClientId(realm, clientId); String cacheKey = realm.getId() + ".client.query.by.clientId." + clientId;
if (cached != null && !cached.getRealm().equals(realm.getId())) { ClientListQuery query = cache.get(cacheKey, ClientListQuery.class);
cached = null; String id = null;
}
if (cached != null) { if (query != null) {
logger.tracev("client by name cache hit: {0}", cached.getClientId()); logger.tracev("client by name cache hit: {0}", clientId);
} }
if (cached == null) { if (query == null) {
Long loaded = UpdateCounter.current(); Long loaded = cache.getCurrentRevision(cacheKey);
if (loaded == null) loaded = UpdateCounter.current();
ClientModel model = getDelegate().getClientByClientId(clientId, realm); ClientModel model = getDelegate().getClientByClientId(clientId, realm);
if (model == null) return null; if (model == null) return null;
if (appInvalidations.contains(model.getId())) return model; if (invalidations.contains(model.getId())) return model;
cached = new RevisionedCachedClient(loaded, cache, getDelegate(), realm, model); id = model.getId();
logger.tracev("adding client by name cache miss: {0}", cached.getClientId()); query = new ClientListQuery(loaded, cacheKey, realm, id);
cache.addClient(cached); logger.tracev("adding client by name cache miss: {0}", clientId);
} else if (appInvalidations.contains(cached.getId())) { cache.addRevisioned(query);
return getDelegate().getClientById(cached.getId(), realm); } else if (invalidations.contains(cacheKey)) {
} else if (managedApplications.containsKey(cached.getId())) { return getDelegate().getClientByClientId(clientId, realm);
return managedApplications.get(cached.getId()); } else {
id = query.getClients().iterator().next();
if (invalidations.contains(id)) {
return getDelegate().getClientByClientId(clientId, realm);
}
} }
ClientAdapter adapter = new ClientAdapter(realm, cached, this, cache); return getClientById(id, realm);
managedApplications.put(cached.getId(), adapter);
return adapter;
} }
@Override @Override
public ClientTemplateModel getClientTemplateById(String id, RealmModel realm) { public ClientTemplateModel getClientTemplateById(String id, RealmModel realm) {
CachedClientTemplate cached = cache.getClientTemplate(id); RevisionedCachedClientTemplate cached = cache.get(id, RevisionedCachedClientTemplate.class);
if (cached != null && !cached.getRealm().equals(realm.getId())) { if (cached != null && !cached.getRealm().equals(realm.getId())) {
cached = null; cached = null;
} }
if (cached == null) { if (cached == null) {
Long loaded = cache.getCurrentRevision(id); Long loaded = cache.getCurrentRevision(id);
if (loaded == null) loaded = UpdateCounter.current();
ClientTemplateModel model = getDelegate().getClientTemplateById(id, realm); ClientTemplateModel model = getDelegate().getClientTemplateById(id, realm);
if (model == null) return null; if (model == null) return null;
if (clientTemplateInvalidations.contains(id)) return model; if (invalidations.contains(id)) return model;
cached = new RevisionedCachedClientTemplate(loaded, cache, getDelegate(), realm, model); cached = new RevisionedCachedClientTemplate(loaded, null, getDelegate(), realm, model);
cache.addClientTemplate(cached); cache.addRevisioned(cached);
} else if (clientTemplateInvalidations.contains(id)) { } else if (invalidations.contains(id)) {
return getDelegate().getClientTemplateById(id, realm); return getDelegate().getClientTemplateById(id, realm);
} else if (managedClientTemplates.containsKey(id)) { } else if (managedClientTemplates.containsKey(id)) {
return managedClientTemplates.get(id); return managedClientTemplates.get(id);
} }
ClientTemplateModel adapter = new ClientTemplateAdapter(realm, cached, this, cache); ClientTemplateModel adapter = new ClientTemplateAdapter(realm, cached, this, null);
managedClientTemplates.put(id, adapter); managedClientTemplates.put(id, adapter);
return adapter; return adapter;
} }

View file

@ -0,0 +1,314 @@
/*
* 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.stream;
import org.infinispan.Cache;
import org.infinispan.notifications.Listener;
import org.infinispan.notifications.cachelistener.annotation.CacheEntriesEvicted;
import org.infinispan.notifications.cachelistener.annotation.CacheEntryInvalidated;
import org.infinispan.notifications.cachelistener.event.CacheEntriesEvictedEvent;
import org.infinispan.notifications.cachelistener.event.CacheEntryInvalidatedEvent;
import org.jboss.logging.Logger;
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.stream.entities.Revisioned;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
import java.util.function.Predicate;
/**
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
*/
@Listener
public class StreamRealmCache {
protected static final Logger logger = Logger.getLogger(StreamRealmCache.class);
protected final Cache<String, Long> revisions;
protected final Cache<String, Object> cache;
public StreamRealmCache(Cache<String, Object> cache, Cache<String, Long> revisions) {
this.cache = cache;
this.cache.addListener(this);
this.revisions = revisions;
}
public Cache<String, Object> getCache() {
return cache;
}
public Cache<String, Long> getRevisions() {
return revisions;
}
public Long getCurrentRevision(String id) {
Long revision = revisions.get(id);
if (revision == null) return UpdateCounter.current();
return revision;
}
public void endRevisionBatch() {
try {
revisions.endBatch(true);
} catch (Exception e) {
}
}
public <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;
}
public Object invalidateObject(String id) {
Revisioned removed = (Revisioned)cache.remove(id);
long next = UpdateCounter.next();
Object rev = revisions.put(id, next);
return removed;
}
public void addRevisioned(Revisioned object) {
//startRevisionBatch();
String id = object.getId();
try {
//revisions.getAdvancedCache().lock(id);
Long rev = revisions.get(id);
if (rev == null) {
if (id.endsWith("realm.clients")) logger.trace("addRevisioned rev == null realm.clients");
rev = UpdateCounter.current();
revisions.put(id, rev);
}
revisions.startBatch();
if (!revisions.getAdvancedCache().lock(id)) {
logger.trace("Could not obtain version lock");
}
rev = revisions.get(id);
if (rev == null) {
if (id.endsWith("realm.clients")) logger.trace("addRevisioned rev2 == null realm.clients");
return;
}
if (rev.equals(object.getRevision())) {
if (id.endsWith("realm.clients")) logger.tracev("adding Object.revision {0} rev {1}", object.getRevision(), rev);
cache.putForExternalRead(id, object);
return;
}
if (rev > object.getRevision()) { // revision is ahead, don't cache
if (id.endsWith("realm.clients")) logger.trace("addRevisioned revision is ahead realm.clients");
return;
}
// revisions cache has a lower value than the object.revision, so update revision and add it to cache
if (id.endsWith("realm.clients")) logger.tracev("adding Object.revision {0} rev {1}", object.getRevision(), rev);
revisions.put(id, object.getRevision());
cache.putForExternalRead(id, object);
} finally {
endRevisionBatch();
}
}
public void clear() {
cache.clear();
}
public void realmInvalidation(String id, Set<String> invalidations) {
Predicate<Map.Entry<String, Object>> predicate = getRealmInvalidationPredicate(id);
addInvalidations(predicate, invalidations);
}
public Predicate<Map.Entry<String, Object>> getRealmInvalidationPredicate(String id) {
return RealmQueryPredicate.create().realm(id);
}
public void clientInvalidation(String id, Set<String> invalidations) {
addInvalidations(getClientInvalidationPredicate(id), invalidations);
}
public Predicate<Map.Entry<String, Object>> getClientInvalidationPredicate(String id) {
return ClientQueryPredicate.create().client(id);
}
public void roleInvalidation(String id, Set<String> invalidations) {
addInvalidations(getRoleInvalidationPredicate(id), invalidations);
}
public Predicate<Map.Entry<String, Object>> getRoleInvalidationPredicate(String id) {
return HasRolePredicate.create().role(id);
}
public void groupInvalidation(String id, Set<String> invalidations) {
addInvalidations(getGroupInvalidationPredicate(id), invalidations);
}
public Predicate<Map.Entry<String, Object>> getGroupInvalidationPredicate(String id) {
return GroupQueryPredicate.create().group(id);
}
public void clientTemplateInvalidation(String id, Set<String> invalidations) {
addInvalidations(getClientTemplateInvalidationPredicate(id), invalidations);
}
public Predicate<Map.Entry<String, Object>> getClientTemplateInvalidationPredicate(String id) {
return ClientTemplateQueryPredicate.create().template(id);
}
public void realmRemoval(String id, Set<String> invalidations) {
Predicate<Map.Entry<String, Object>> predicate = getRealmRemovalPredicate(id);
addInvalidations(predicate, invalidations);
}
public Predicate<Map.Entry<String, Object>> getRealmRemovalPredicate(String id) {
Predicate<Map.Entry<String, Object>> predicate = null;
predicate = RealmQueryPredicate.create().realm(id)
.or(InRealmPredicate.create().realm(id));
return predicate;
}
public void clientAdded(String realmId, String id, Set<String> invalidations) {
addInvalidations(getClientAddedPredicate(realmId), invalidations);
}
public Predicate<Map.Entry<String, Object>> getClientAddedPredicate(String realmId) {
return ClientQueryPredicate.create().inRealm(realmId);
}
public void clientRemoval(String realmId, String id, Set<String> invalidations) {
Predicate<Map.Entry<String, Object>> predicate = null;
predicate = getClientRemovalPredicate(realmId, id);
addInvalidations(predicate, invalidations);
}
public Predicate<Map.Entry<String, Object>> getClientRemovalPredicate(String realmId, String id) {
Predicate<Map.Entry<String, Object>> predicate;
predicate = ClientQueryPredicate.create().inRealm(realmId)
.or(ClientQueryPredicate.create().client(id))
.or(InClientPredicate.create().client(id));
return predicate;
}
public void roleRemoval(String id, Set<String> invalidations) {
addInvalidations(getRoleRemovalPredicate(id), invalidations);
}
public Predicate<Map.Entry<String, Object>> getRoleRemovalPredicate(String id) {
return getRoleInvalidationPredicate(id);
}
public void addInvalidations(Predicate<Map.Entry<String, Object>> predicate, Set<String> invalidations) {
Iterator<Map.Entry<String, Object>> it = getEntryIterator(predicate);
while (it.hasNext()) {
invalidations.add(it.next().getKey());
}
}
private Iterator<Map.Entry<String, Object>> getEntryIterator(Predicate<Map.Entry<String, Object>> predicate) {
return cache
.entrySet()
.stream()
.filter(predicate).iterator();
}
@CacheEntryInvalidated
public void cacheInvalidated(CacheEntryInvalidatedEvent<String, Object> event) {
if (event.isPre()) {
Object object = event.getValue();
if (object != null) {
Predicate<Map.Entry<String, Object>> predicate = getInvalidationPredicate(object);
if (predicate != null) runEvictions(predicate);
}
}
}
@CacheEntriesEvicted
public void cacheEvicted(CacheEntriesEvictedEvent<String, Object> event) {
for (Object object : event.getEntries().values()) {
Predicate<Map.Entry<String, Object>> predicate = getEvictionPredicate(object);
if (predicate != null) runEvictions(predicate);
}
}
public void runEvictions(Predicate<Map.Entry<String, Object>> current) {
Set<String> evictions = new HashSet<>();
addInvalidations(current, evictions);
for (String key : evictions) cache.evict(key);
}
protected Predicate<Map.Entry<String, Object>> getEvictionPredicate(Object object) {
if (object instanceof CachedRealm) {
CachedRealm cached = (CachedRealm)object;
return getRealmInvalidationPredicate(cached.getId());
} else if (object instanceof CachedClient) {
CachedClient cached = (CachedClient)object;
return getClientInvalidationPredicate(cached.getId());
} else if (object instanceof CachedRole) {
CachedRole cached = (CachedRole)object;
return getRoleInvalidationPredicate(cached.getId());
} else if (object instanceof CachedGroup) {
CachedGroup cached = (CachedGroup)object;
return getGroupInvalidationPredicate(cached.getId());
} else if (object instanceof CachedClientTemplate) {
CachedClientTemplate cached = (CachedClientTemplate)object;
return getClientTemplateInvalidationPredicate(cached.getId());
}
return null;
}
protected Predicate<Map.Entry<String, Object>> getInvalidationPredicate(Object object) {
if (object instanceof CachedRealm) {
CachedRealm cached = (CachedRealm)object;
return getRealmRemovalPredicate(cached.getId());
} else if (object instanceof CachedClient) {
CachedClient cached = (CachedClient)object;
Predicate<Map.Entry<String, Object>> predicate = getClientRemovalPredicate(cached.getRealm(), cached.getId());
for (String roleId : cached.getRoles().values()) {
predicate.or(getRoleRemovalPredicate(roleId));
}
return predicate;
} else if (object instanceof CachedRole) {
CachedRole cached = (CachedRole)object;
return getRoleRemovalPredicate(cached.getId());
} else if (object instanceof CachedGroup) {
CachedGroup cached = (CachedGroup)object;
return getGroupInvalidationPredicate(cached.getId());
} else if (object instanceof CachedClientTemplate) {
CachedClientTemplate cached = (CachedClientTemplate)object;
return getClientTemplateInvalidationPredicate(cached.getId());
}
return null;
}
}

View file

@ -1,4 +1,4 @@
package org.keycloak.models.cache.infinispan.locking; package org.keycloak.models.cache.infinispan.stream;
import java.util.concurrent.atomic.AtomicLong; import java.util.concurrent.atomic.AtomicLong;

View file

@ -0,0 +1,31 @@
package org.keycloak.models.cache.infinispan.stream.entities;
/**
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
* @version $Revision: 1 $
*/
public class AbstractRevisioned implements Revisioned {
private String id;
private Long revision;
public AbstractRevisioned(Long revision, String id) {
this.revision = revision;
this.id = id;
}
@Override
public String getId() {
return id;
}
@Override
public Long getRevision() {
return revision;
}
@Override
public void setRevision(Long revision) {
this.revision = revision;
}
}

View file

@ -0,0 +1,9 @@
package org.keycloak.models.cache.infinispan.stream.entities;
/**
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
* @version $Revision: 1 $
*/
public interface InClient extends InRealm {
String getClientId();
}

View file

@ -0,0 +1,9 @@
package org.keycloak.models.cache.infinispan.stream.entities;
/**
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
* @version $Revision: 1 $
*/
public interface InRealm extends Revisioned {
String getRealm();
}

View file

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

View file

@ -1,4 +1,4 @@
package org.keycloak.models.cache.infinispan.locking.entities; package org.keycloak.models.cache.infinispan.stream.entities;
import org.keycloak.models.ClientModel; import org.keycloak.models.ClientModel;
import org.keycloak.models.RealmModel; import org.keycloak.models.RealmModel;
@ -6,13 +6,12 @@ import org.keycloak.models.RealmProvider;
import org.keycloak.models.RoleModel; import org.keycloak.models.RoleModel;
import org.keycloak.models.cache.RealmCache; import org.keycloak.models.cache.RealmCache;
import org.keycloak.models.cache.entities.CachedClient; import org.keycloak.models.cache.entities.CachedClient;
import org.keycloak.models.cache.infinispan.locking.Revisioned;
/** /**
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a> * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
* @version $Revision: 1 $ * @version $Revision: 1 $
*/ */
public class RevisionedCachedClient extends CachedClient implements Revisioned { public class RevisionedCachedClient extends CachedClient implements Revisioned, InRealm {
public RevisionedCachedClient(Long revision, RealmCache cache, RealmProvider delegate, RealmModel realm, ClientModel model) { public RevisionedCachedClient(Long revision, RealmCache cache, RealmProvider delegate, RealmModel realm, ClientModel model) {
super(cache, delegate, realm, model); super(cache, delegate, realm, model);

View file

@ -1,15 +1,14 @@
package org.keycloak.models.cache.infinispan.locking.entities; package org.keycloak.models.cache.infinispan.stream.entities;
import org.keycloak.models.RealmModel; import org.keycloak.models.RealmModel;
import org.keycloak.models.RoleModel; import org.keycloak.models.RoleModel;
import org.keycloak.models.cache.entities.CachedClientRole; import org.keycloak.models.cache.entities.CachedClientRole;
import org.keycloak.models.cache.infinispan.locking.Revisioned;
/** /**
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a> * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
* @version $Revision: 1 $ * @version $Revision: 1 $
*/ */
public class RevisionedCachedClientRole extends CachedClientRole implements Revisioned { public class RevisionedCachedClientRole extends CachedClientRole implements Revisioned, InClient {
public RevisionedCachedClientRole(Long revision, String idClient, RoleModel model, RealmModel realm) { public RevisionedCachedClientRole(Long revision, String idClient, RoleModel model, RealmModel realm) {
super(idClient, model, realm); super(idClient, model, realm);

View file

@ -1,17 +1,16 @@
package org.keycloak.models.cache.infinispan.locking.entities; package org.keycloak.models.cache.infinispan.stream.entities;
import org.keycloak.models.ClientTemplateModel; import org.keycloak.models.ClientTemplateModel;
import org.keycloak.models.RealmModel; import org.keycloak.models.RealmModel;
import org.keycloak.models.RealmProvider; import org.keycloak.models.RealmProvider;
import org.keycloak.models.cache.RealmCache; import org.keycloak.models.cache.RealmCache;
import org.keycloak.models.cache.entities.CachedClientTemplate; import org.keycloak.models.cache.entities.CachedClientTemplate;
import org.keycloak.models.cache.infinispan.locking.Revisioned;
/** /**
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a> * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
* @version $Revision: 1 $ * @version $Revision: 1 $
*/ */
public class RevisionedCachedClientTemplate extends CachedClientTemplate implements Revisioned { public class RevisionedCachedClientTemplate extends CachedClientTemplate implements Revisioned, InRealm {
public RevisionedCachedClientTemplate(Long revision, RealmCache cache, RealmProvider delegate, RealmModel realm, ClientTemplateModel model) { public RevisionedCachedClientTemplate(Long revision, RealmCache cache, RealmProvider delegate, RealmModel realm, ClientTemplateModel model) {
super(cache, delegate, realm, model); super(cache, delegate, realm, model);

View file

@ -1,15 +1,14 @@
package org.keycloak.models.cache.infinispan.locking.entities; package org.keycloak.models.cache.infinispan.stream.entities;
import org.keycloak.models.GroupModel; import org.keycloak.models.GroupModel;
import org.keycloak.models.RealmModel; import org.keycloak.models.RealmModel;
import org.keycloak.models.cache.entities.CachedGroup; import org.keycloak.models.cache.entities.CachedGroup;
import org.keycloak.models.cache.infinispan.locking.Revisioned;
/** /**
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a> * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
* @version $Revision: 1 $ * @version $Revision: 1 $
*/ */
public class RevisionedCachedGroup extends CachedGroup implements Revisioned { public class RevisionedCachedGroup extends CachedGroup implements Revisioned, InRealm {
public RevisionedCachedGroup(Long revision, RealmModel realm, GroupModel group) { public RevisionedCachedGroup(Long revision, RealmModel realm, GroupModel group) {
super(realm, group); super(realm, group);
this.revision = revision; this.revision = revision;

View file

@ -1,4 +1,4 @@
package org.keycloak.models.cache.infinispan.locking.entities; package org.keycloak.models.cache.infinispan.stream.entities;
import org.keycloak.models.ClientModel; import org.keycloak.models.ClientModel;
import org.keycloak.models.ClientTemplateModel; import org.keycloak.models.ClientTemplateModel;
@ -7,7 +7,6 @@ import org.keycloak.models.RealmProvider;
import org.keycloak.models.RoleModel; import org.keycloak.models.RoleModel;
import org.keycloak.models.cache.RealmCache; import org.keycloak.models.cache.RealmCache;
import org.keycloak.models.cache.entities.CachedRealm; import org.keycloak.models.cache.entities.CachedRealm;
import org.keycloak.models.cache.infinispan.locking.Revisioned;
/** /**
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a> * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>

View file

@ -1,15 +1,14 @@
package org.keycloak.models.cache.infinispan.locking.entities; package org.keycloak.models.cache.infinispan.stream.entities;
import org.keycloak.models.RealmModel; import org.keycloak.models.RealmModel;
import org.keycloak.models.RoleModel; import org.keycloak.models.RoleModel;
import org.keycloak.models.cache.entities.CachedRealmRole; import org.keycloak.models.cache.entities.CachedRealmRole;
import org.keycloak.models.cache.infinispan.locking.Revisioned;
/** /**
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a> * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
* @version $Revision: 1 $ * @version $Revision: 1 $
*/ */
public class RevisionedCachedRealmRole extends CachedRealmRole implements Revisioned { public class RevisionedCachedRealmRole extends CachedRealmRole implements Revisioned, InRealm {
public RevisionedCachedRealmRole(Long revision, RoleModel model, RealmModel realm) { public RevisionedCachedRealmRole(Long revision, RoleModel model, RealmModel realm) {
super(model, realm); super(model, realm);

View file

@ -1,15 +1,14 @@
package org.keycloak.models.cache.infinispan.locking.entities; package org.keycloak.models.cache.infinispan.stream.entities;
import org.keycloak.models.RealmModel; import org.keycloak.models.RealmModel;
import org.keycloak.models.UserModel; import org.keycloak.models.UserModel;
import org.keycloak.models.cache.entities.CachedUser; import org.keycloak.models.cache.entities.CachedUser;
import org.keycloak.models.cache.infinispan.locking.Revisioned;
/** /**
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a> * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
* @version $Revision: 1 $ * @version $Revision: 1 $
*/ */
public class RevisionedCachedUser extends CachedUser implements Revisioned { public class RevisionedCachedUser extends CachedUser implements Revisioned, InRealm {
public RevisionedCachedUser(Long revision, RealmModel realm, UserModel user) { public RevisionedCachedUser(Long revision, RealmModel realm, UserModel user) {
super(realm, user); super(realm, user);
this.revision = revision; this.revision = revision;

View file

@ -16,4 +16,3 @@
# #
org.keycloak.connections.infinispan.DefaultInfinispanConnectionProviderFactory org.keycloak.connections.infinispan.DefaultInfinispanConnectionProviderFactory
org.keycloak.models.cache.infinispan.locking.LockingConnectionProviderFactory

View file

@ -16,4 +16,3 @@
# #
org.keycloak.models.cache.infinispan.InfinispanCacheRealmProviderFactory org.keycloak.models.cache.infinispan.InfinispanCacheRealmProviderFactory
org.keycloak.models.cache.infinispan.locking.LockingCacheRealmProviderFactory

View file

@ -0,0 +1,117 @@
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.global.GlobalConfigurationBuilder;
import org.infinispan.manager.DefaultCacheManager;
import org.infinispan.manager.EmbeddedCacheManager;
import org.infinispan.notifications.Listener;
import org.infinispan.notifications.cachelistener.annotation.CacheEntriesEvicted;
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.CacheEntryInvalidatedEvent;
import org.infinispan.notifications.cachelistener.event.CacheEntryRemovedEvent;
import org.junit.Ignore;
import org.junit.Test;
import org.keycloak.connections.infinispan.InfinispanConnectionProvider;
/**
* Just a simple test to see how distributed caches work
*
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
* @version $Revision: 1 $
*/
@Ignore
public class ClusteredCacheBehaviorTest {
public EmbeddedCacheManager createManager() {
GlobalConfigurationBuilder gcb = new GlobalConfigurationBuilder();
boolean clustered = true;
boolean async = false;
boolean allowDuplicateJMXDomains = true;
if (clustered) {
gcb.transport().defaultTransport();
}
gcb.globalJmxStatistics().allowDuplicateDomains(allowDuplicateJMXDomains);
EmbeddedCacheManager cacheManager = new DefaultCacheManager(gcb.build());
ConfigurationBuilder invalidationConfigBuilder = new ConfigurationBuilder();
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;
}
@Listener
public static class CacheListener {
String name;
public CacheListener(String name) {
this.name = name;
}
@CacheEntryRemoved
public void removed(CacheEntryRemovedEvent<String, Object> event) {
if (event.isPre()) {
System.out.println("Listener '" + name + "' entry removed");
}
}
@CacheEntryInvalidated
public void removed(CacheEntryInvalidatedEvent<String, Object> event) {
if (event.isPre()) {
System.out.println("Listener '" + name + "' entry invalidated");
}
}
@CacheEntriesEvicted
public void evicted(CacheEntriesEvictedEvent<String, Object> event) {
System.out.println("Listener '" + name + "' entry evicted");
}
}
@Test
public void testListener() throws Exception {
EmbeddedCacheManager node1 = createManager();
EmbeddedCacheManager node2 = createManager();
Cache<String, Object> node1Cache = node1.getCache(InfinispanConnectionProvider.REALM_CACHE_NAME);
node1Cache.addListener(new CacheListener("node1"));
Cache<String, Object> node2Cache = node2.getCache(InfinispanConnectionProvider.REALM_CACHE_NAME);
node2Cache.addListener(new CacheListener("node2"));
System.out.println("node1 create entry");
node1Cache.put("key", "node1");
System.out.println("node2 create entry");
node2Cache.put("key", "node2");
System.out.println("node1 remove entry");
node1Cache.remove("key");
System.out.println("node2 remove entry");
node2Cache.remove("key");
System.out.println("node2 put entry");
node2Cache.put("key", "node2");
System.out.println("node2 evict entry");
node2Cache.evict("key");
System.out.println("node1/node2 putExternal entry");
node1Cache.putForExternalRead("key", "common");
node2Cache.putForExternalRead("key", "common");
System.out.println("node2 remove entry");
node2Cache.remove("key");
}
}

View file

@ -612,12 +612,12 @@ public class ClientAdapter implements ClientModel {
@Override @Override
public RoleModel getRole(String name) { public RoleModel getRole(String name) {
TypedQuery<RoleEntity> query = em.createNamedQuery("getClientRoleByName", RoleEntity.class); TypedQuery<String> query = em.createNamedQuery("getClientRoleIdByName", String.class);
query.setParameter("name", name); query.setParameter("name", name);
query.setParameter("client", entity); query.setParameter("client", entity.getId());
List<RoleEntity> roles = query.getResultList(); List<String> roles = query.getResultList();
if (roles.size() == 0) return null; if (roles.size() == 0) return null;
return new RoleAdapter(realm, em, roles.get(0)); return session.realms().getRoleById(roles.get(0), realm);
} }
@Override @Override
@ -636,7 +636,7 @@ public class ClientAdapter implements ClientModel {
entity.getRoles().add(roleEntity); entity.getRoles().add(roleEntity);
em.persist(roleEntity); em.persist(roleEntity);
em.flush(); em.flush();
return new RoleAdapter(realm, em, roleEntity); return new RoleAdapter(session, realm, em, roleEntity);
} }
@Override @Override
@ -667,19 +667,21 @@ public class ClientAdapter implements ClientModel {
@Override @Override
public Set<RoleModel> getRoles() { public Set<RoleModel> getRoles() {
Set<RoleModel> list = new HashSet<RoleModel>(); Set<RoleModel> list = new HashSet<RoleModel>();
/*
Collection<RoleEntity> roles = entity.getRoles();
if (roles == null) return list;
for (RoleEntity entity : roles) {
list.add(new RoleAdapter(realm, em, entity));
}
*/
TypedQuery<RoleEntity> query = em.createNamedQuery("getClientRoles", RoleEntity.class); TypedQuery<RoleEntity> query = em.createNamedQuery("getClientRoles", RoleEntity.class);
query.setParameter("client", entity); query.setParameter("client", entity);
List<RoleEntity> roles = query.getResultList(); List<RoleEntity> roles = query.getResultList();
for (RoleEntity roleEntity : roles) { for (RoleEntity roleEntity : roles) {
list.add(new RoleAdapter(realm, em, roleEntity)); list.add(new RoleAdapter(session, realm, em, roleEntity));
} }
/*
TypedQuery<String> query = em.createNamedQuery("getClientRoleIds", String.class);
query.setParameter("client", entity.getId());
List<String> roles = query.getResultList();
for (String id : roles) {
list.add(session.realms().getRoleById(id, realm));
}
*/
return list; return list;
} }

View file

@ -36,6 +36,7 @@ import org.keycloak.models.utils.KeycloakModelUtils;
import javax.persistence.EntityManager; import javax.persistence.EntityManager;
import javax.persistence.TypedQuery; import javax.persistence.TypedQuery;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections;
import java.util.LinkedList; import java.util.LinkedList;
import java.util.List; import java.util.List;
@ -90,25 +91,27 @@ public class JpaRealmProvider implements RealmProvider {
@Override @Override
public List<RealmModel> getRealms() { public List<RealmModel> getRealms() {
TypedQuery<RealmEntity> query = em.createNamedQuery("getAllRealms", RealmEntity.class); TypedQuery<String> query = em.createNamedQuery("getAllRealmIds", String.class);
List<RealmEntity> entities = query.getResultList(); List<String> entities = query.getResultList();
List<RealmModel> realms = new ArrayList<RealmModel>(); List<RealmModel> realms = new ArrayList<RealmModel>();
for (RealmEntity entity : entities) { for (String id : entities) {
realms.add(new RealmAdapter(session, em, entity)); RealmModel realm = session.realms().getRealm(id);
if (realm != null) realms.add(realm);
} }
return realms; return realms;
} }
@Override @Override
public RealmModel getRealmByName(String name) { public RealmModel getRealmByName(String name) {
TypedQuery<RealmEntity> query = em.createNamedQuery("getRealmByName", RealmEntity.class); TypedQuery<String> query = em.createNamedQuery("getRealmIdByName", String.class);
query.setParameter("name", name); query.setParameter("name", name);
List<RealmEntity> entities = query.getResultList(); List<String> entities = query.getResultList();
if (entities.size() == 0) return null; if (entities.size() == 0) return null;
if (entities.size() > 1) throw new IllegalStateException("Should not be more than one realm with same name"); if (entities.size() > 1) throw new IllegalStateException("Should not be more than one realm with same name");
RealmEntity realm = query.getResultList().get(0); String id = query.getResultList().get(0);
if (realm == null) return null;
return new RealmAdapter(session, em, realm); return session.realms().getRealm(id);
} }
@Override @Override
@ -126,11 +129,11 @@ public class JpaRealmProvider implements RealmProvider {
num = em.createNamedQuery("deleteGroupsByRealm") num = em.createNamedQuery("deleteGroupsByRealm")
.setParameter("realm", realm).executeUpdate(); .setParameter("realm", realm).executeUpdate();
TypedQuery<ClientEntity> query = em.createNamedQuery("getClientsByRealm", ClientEntity.class); TypedQuery<String> query = em.createNamedQuery("getClientIdsByRealm", String.class);
query.setParameter("realm", realm); query.setParameter("realm", realm.getId());
List<ClientEntity> clients = query.getResultList(); List<String> clients = query.getResultList();
for (ClientEntity a : clients) { for (String client : clients) {
adapter.removeClient(a.getId()); session.realms().removeClient(client, adapter);
} }
/* /*
for (ClientEntity a : new LinkedList<>(realm.getClients())) { for (ClientEntity a : new LinkedList<>(realm.getClients())) {
@ -145,11 +148,6 @@ public class JpaRealmProvider implements RealmProvider {
em.flush(); em.flush();
em.clear(); em.clear();
realm = em.find(RealmEntity.class, id);
if (realm != null) {
logger.error("WTF is the realm still there after a removal????????");
}
return true; return true;
} }
@ -162,7 +160,7 @@ public class JpaRealmProvider implements RealmProvider {
RoleEntity entity = em.find(RoleEntity.class, id); RoleEntity entity = em.find(RoleEntity.class, id);
if (entity == null) return null; if (entity == null) return null;
if (!realm.getId().equals(entity.getRealmId())) return null; if (!realm.getId().equals(entity.getRealmId())) return null;
return new RoleAdapter(realm, em, entity); return new RoleAdapter(session, realm, em, entity);
} }
@Override @Override
@ -173,6 +171,52 @@ public class JpaRealmProvider implements RealmProvider {
return new GroupAdapter(realm, em, groupEntity); return new GroupAdapter(realm, em, groupEntity);
} }
@Override
public ClientModel addClient(RealmModel realm, String clientId) {
return addClient(realm, KeycloakModelUtils.generateId(), clientId);
}
@Override
public ClientModel addClient(RealmModel realm, String id, String clientId) {
if (clientId == null) {
clientId = id;
}
ClientEntity entity = new ClientEntity();
entity.setId(id);
entity.setClientId(clientId);
entity.setEnabled(true);
entity.setStandardFlowEnabled(true);
RealmEntity realmRef = em.getReference(RealmEntity.class, realm.getId());
entity.setRealm(realmRef);
em.persist(entity);
em.flush();
final ClientModel resource = new ClientAdapter(realm, em, session, entity);
em.flush();
session.getKeycloakSessionFactory().publish(new RealmModel.ClientCreationEvent() {
@Override
public ClientModel getCreatedClient() {
return resource;
}
});
return resource;
}
@Override
public List<ClientModel> getClients(RealmModel realm) {
TypedQuery<String> query = em.createNamedQuery("getClientIdsByRealm", String.class);
query.setParameter("realm", realm.getId());
List<String> clients = query.getResultList();
if (clients.isEmpty()) return Collections.EMPTY_LIST;
List<ClientModel> list = new LinkedList<>();
for (String id : clients) {
ClientModel client = session.realms().getClientById(id, realm);
if (client != null) list.add(client);
}
return Collections.unmodifiableList(list);
}
@Override @Override
public ClientModel getClientById(String id, RealmModel realm) { public ClientModel getClientById(String id, RealmModel realm) {
ClientEntity app = em.find(ClientEntity.class, id); ClientEntity app = em.find(ClientEntity.class, id);
@ -184,13 +228,13 @@ public class JpaRealmProvider implements RealmProvider {
@Override @Override
public ClientModel getClientByClientId(String clientId, RealmModel realm) { public ClientModel getClientByClientId(String clientId, RealmModel realm) {
TypedQuery<ClientEntity> query = em.createNamedQuery("findClientByClientId", ClientEntity.class); TypedQuery<String> query = em.createNamedQuery("findClientIdByClientId", String.class);
query.setParameter("clientId", clientId); query.setParameter("clientId", clientId);
query.setParameter("realm", realm.getId()); query.setParameter("realm", realm.getId());
List<ClientEntity> results = query.getResultList(); List<String> results = query.getResultList();
if (results.isEmpty()) return null; if (results.isEmpty()) return null;
ClientEntity entity = results.get(0); String id = results.get(0);
return new ClientAdapter(realm, em, session, entity); return session.realms().getClientById(id, realm);
} }
@Override @Override

View file

@ -723,45 +723,17 @@ public class RealmAdapter implements RealmModel {
@Override @Override
public List<ClientModel> getClients() { public List<ClientModel> getClients() {
TypedQuery<ClientEntity> query = em.createNamedQuery("getClientsByRealm", ClientEntity.class); return session.realms().getClients(this);
query.setParameter("realm", realm);
List<ClientEntity> clients = query.getResultList();
if (clients.isEmpty()) return Collections.EMPTY_LIST;
List<ClientModel> list = new LinkedList<>();
for (ClientEntity entity : clients) {
list.add(new ClientAdapter(this, em, session, entity));
}
return Collections.unmodifiableList(list);
} }
@Override @Override
public ClientModel addClient(String name) { public ClientModel addClient(String name) {
return this.addClient(KeycloakModelUtils.generateId(), name); return session.realms().addClient(this, name);
} }
@Override @Override
public ClientModel addClient(String id, String clientId) { public ClientModel addClient(String id, String clientId) {
if (clientId == null) { return session.realms().addClient(this, id, clientId);
clientId = id;
}
ClientEntity entity = new ClientEntity();
entity.setId(id);
entity.setClientId(clientId);
entity.setEnabled(true);
entity.setStandardFlowEnabled(true);
entity.setRealm(realm);
realm.getClients().add(entity);
em.persist(entity);
em.flush();
final ClientModel resource = new ClientAdapter(this, em, session, entity);
em.flush();
session.getKeycloakSessionFactory().publish(new ClientCreationEvent() {
@Override
public ClientModel getCreatedClient() {
return resource;
}
});
return resource;
} }
@Override @Override
@ -956,7 +928,7 @@ public class RealmAdapter implements RealmModel {
em.remove(entity); em.remove(entity);
} }
List<UserFederationProviderModel> add = new LinkedList<UserFederationProviderModel>(); List<UserFederationProviderModel> add = new LinkedList<>();
for (UserFederationProviderModel model : providers) { for (UserFederationProviderModel model : providers) {
boolean found = false; boolean found = false;
for (UserFederationProviderEntity entity : realm.getUserFederationProviders()) { for (UserFederationProviderEntity entity : realm.getUserFederationProviders()) {
@ -1008,12 +980,12 @@ public class RealmAdapter implements RealmModel {
@Override @Override
public RoleModel getRole(String name) { public RoleModel getRole(String name) {
TypedQuery<RoleEntity> query = em.createNamedQuery("getRealmRoleByName", RoleEntity.class); TypedQuery<String> query = em.createNamedQuery("getRealmRoleIdByName", String.class);
query.setParameter("name", name); query.setParameter("name", name);
query.setParameter("realm", realm); query.setParameter("realm", realm.getId());
List<RoleEntity> roles = query.getResultList(); List<String> roles = query.getResultList();
if (roles.size() == 0) return null; if (roles.size() == 0) return null;
return new RoleAdapter(this, em, roles.get(0)); return session.realms().getRoleById(roles.get(0), this);
} }
@Override @Override
@ -1031,7 +1003,7 @@ public class RealmAdapter implements RealmModel {
realm.getRoles().add(entity); realm.getRoles().add(entity);
em.persist(entity); em.persist(entity);
em.flush(); em.flush();
return new RoleAdapter(this, em, entity); return new RoleAdapter(session, this, em, entity);
} }
@Override @Override
@ -1063,7 +1035,9 @@ public class RealmAdapter implements RealmModel {
if (roles == null) return Collections.EMPTY_SET; if (roles == null) return Collections.EMPTY_SET;
Set<RoleModel> list = new HashSet<RoleModel>(); Set<RoleModel> list = new HashSet<RoleModel>();
for (RoleEntity entity : roles) { for (RoleEntity entity : roles) {
list.add(new RoleAdapter(this, em, entity)); list.add(new RoleAdapter(session, this, em, entity));
// can't get it from cache cuz of stack overflow
// list.add(session.realms().getRoleById(entity.getId(), this));
} }
return Collections.unmodifiableSet(list); return Collections.unmodifiableSet(list);
} }
@ -1259,9 +1233,14 @@ public class RealmAdapter implements RealmModel {
if (masterAdminClient == null) { if (masterAdminClient == null) {
return null; return null;
} }
RealmModel masterRealm = null;
RealmAdapter masterRealm = new RealmAdapter(session, em, masterAdminClient.getRealm()); String masterAdminClientRealmId = masterAdminClient.getRealm().getId();
return new ClientAdapter(masterRealm, em, session, masterAdminClient); if (masterAdminClientRealmId.equals(getId())) {
masterRealm = this;
} else {
masterRealm = session.realms().getRealm(masterAdminClientRealmId);
}
return session.realms().getClientById(masterAdminClient.getId(), masterRealm);
} }
@Override @Override
@ -2052,11 +2031,12 @@ public class RealmAdapter implements RealmModel {
@Override @Override
public List<GroupModel> getGroups() { public List<GroupModel> getGroups() {
List<GroupEntity> groups = em.createNamedQuery("getAllGroupsByRealm").setParameter("realm", realm).getResultList(); List<String> groups = em.createNamedQuery("getAllGroupIdsByRealm", String.class)
.setParameter("realm", realm.getId()).getResultList();
if (groups == null) return Collections.EMPTY_LIST; if (groups == null) return Collections.EMPTY_LIST;
List<GroupModel> list = new LinkedList<>(); List<GroupModel> list = new LinkedList<>();
for (GroupEntity entity : groups) { for (String id : groups) {
list.add(new GroupAdapter(this, em, entity)); list.add(session.realms().getGroupById(id, this));
} }
return Collections.unmodifiableList(list); return Collections.unmodifiableList(list);
} }
@ -2128,7 +2108,7 @@ public class RealmAdapter implements RealmModel {
if (entities == null || entities.isEmpty()) return Collections.EMPTY_LIST; if (entities == null || entities.isEmpty()) return Collections.EMPTY_LIST;
List<ClientTemplateModel> list = new LinkedList<>(); List<ClientTemplateModel> list = new LinkedList<>();
for (ClientTemplateEntity entity : entities) { for (ClientTemplateEntity entity : entities) {
list.add(new ClientTemplateAdapter(this, em, session, entity)); list.add(session.realms().getClientTemplateById(entity.getId(), this));
} }
return Collections.unmodifiableList(list); return Collections.unmodifiableList(list);
} }

View file

@ -17,6 +17,7 @@
package org.keycloak.models.jpa; package org.keycloak.models.jpa;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel; import org.keycloak.models.RealmModel;
import org.keycloak.models.RoleContainerModel; import org.keycloak.models.RoleContainerModel;
import org.keycloak.models.RoleModel; import org.keycloak.models.RoleModel;
@ -36,11 +37,13 @@ public class RoleAdapter implements RoleModel {
protected RoleEntity role; protected RoleEntity role;
protected EntityManager em; protected EntityManager em;
protected RealmModel realm; protected RealmModel realm;
protected KeycloakSession session;
public RoleAdapter(RealmModel realm, EntityManager em, RoleEntity role) { public RoleAdapter(KeycloakSession session, RealmModel realm, EntityManager em, RoleEntity role) {
this.em = em; this.em = em;
this.realm = realm; this.realm = realm;
this.role = role; this.role = role;
this.session = session;
} }
public RoleEntity getRole() { public RoleEntity getRole() {
@ -115,7 +118,10 @@ public class RoleAdapter implements RoleModel {
Set<RoleModel> set = new HashSet<RoleModel>(); Set<RoleModel> set = new HashSet<RoleModel>();
for (RoleEntity composite : getRole().getCompositeRoles()) { for (RoleEntity composite : getRole().getCompositeRoles()) {
set.add(new RoleAdapter(realm, em, composite)); set.add(new RoleAdapter(session, realm, em, composite));
// todo I want to do this, but can't as you get stack overflow
// set.add(session.realms().getRoleById(composite.getId(), realm));
} }
return set; return set;
} }

View file

@ -50,6 +50,7 @@ import java.util.Set;
@Table(name="CLIENT", uniqueConstraints = {@UniqueConstraint(columnNames = {"REALM_ID", "CLIENT_ID"})}) @Table(name="CLIENT", uniqueConstraints = {@UniqueConstraint(columnNames = {"REALM_ID", "CLIENT_ID"})})
@NamedQueries({ @NamedQueries({
@NamedQuery(name="getClientsByRealm", query="select client from ClientEntity client where client.realm = :realm"), @NamedQuery(name="getClientsByRealm", query="select client from ClientEntity client where client.realm = :realm"),
@NamedQuery(name="getClientIdsByRealm", query="select client.id from ClientEntity client where client.realm.id = :realm"),
@NamedQuery(name="findClientIdByClientId", query="select client.id from ClientEntity client where client.clientId = :clientId and client.realm.id = :realm"), @NamedQuery(name="findClientIdByClientId", query="select client.id from ClientEntity client where client.clientId = :clientId and client.realm.id = :realm"),
@NamedQuery(name="findClientByClientId", query="select client from ClientEntity client where client.clientId = :clientId and client.realm.id = :realm"), @NamedQuery(name="findClientByClientId", query="select client from ClientEntity client where client.clientId = :clientId and client.realm.id = :realm"),
}) })

View file

@ -39,6 +39,7 @@ import java.util.Collection;
*/ */
@NamedQueries({ @NamedQueries({
@NamedQuery(name="getAllGroupsByRealm", query="select u from GroupEntity u where u.realm = :realm order by u.name"), @NamedQuery(name="getAllGroupsByRealm", query="select u from GroupEntity u where u.realm = :realm order by u.name"),
@NamedQuery(name="getAllGroupIdsByRealm", query="select u.id from GroupEntity u where u.realm.id = :realm order by u.name"),
@NamedQuery(name="getGroupById", query="select u from GroupEntity u where u.id = :id and u.realm = :realm"), @NamedQuery(name="getGroupById", query="select u from GroupEntity u where u.id = :id and u.realm = :realm"),
@NamedQuery(name="getGroupIdsByParent", query="select u.id from GroupEntity u where u.parent = :parent"), @NamedQuery(name="getGroupIdsByParent", query="select u.id from GroupEntity u where u.parent = :parent"),
@NamedQuery(name="getGroupCount", query="select count(u) from GroupEntity u where u.realm = :realm"), @NamedQuery(name="getGroupCount", query="select count(u) from GroupEntity u where u.realm = :realm"),

View file

@ -49,8 +49,8 @@ import java.util.Set;
@Table(name="REALM") @Table(name="REALM")
@Entity @Entity
@NamedQueries({ @NamedQueries({
@NamedQuery(name="getAllRealms", query="select realm from RealmEntity realm"), @NamedQuery(name="getAllRealmIds", query="select realm.id from RealmEntity realm"),
@NamedQuery(name="getRealmByName", query="select realm from RealmEntity realm where realm.name = :name"), @NamedQuery(name="getRealmIdByName", query="select realm.id from RealmEntity realm where realm.name = :name"),
}) })
public class RealmEntity { public class RealmEntity {
@Id @Id
@ -147,9 +147,6 @@ public class RealmEntity {
@OneToMany(cascade ={CascadeType.REMOVE}, orphanRemoval = true, mappedBy = "realm") @OneToMany(cascade ={CascadeType.REMOVE}, orphanRemoval = true, mappedBy = "realm")
Collection<UserFederationMapperEntity> userFederationMappers = new ArrayList<UserFederationMapperEntity>(); Collection<UserFederationMapperEntity> userFederationMappers = new ArrayList<UserFederationMapperEntity>();
@OneToMany(fetch = FetchType.LAZY, cascade ={CascadeType.REMOVE}, orphanRemoval = true, mappedBy="realm")
Collection<ClientEntity> clients = new ArrayList<>();
@OneToMany(fetch = FetchType.LAZY, cascade ={CascadeType.REMOVE}, orphanRemoval = true, mappedBy = "realm") @OneToMany(fetch = FetchType.LAZY, cascade ={CascadeType.REMOVE}, orphanRemoval = true, mappedBy = "realm")
Collection<ClientTemplateEntity> clientTemplates = new ArrayList<>(); Collection<ClientTemplateEntity> clientTemplates = new ArrayList<>();
@ -421,14 +418,6 @@ public class RealmEntity {
public void setRequiredCredentials(Collection<RequiredCredentialEntity> requiredCredentials) { public void setRequiredCredentials(Collection<RequiredCredentialEntity> requiredCredentials) {
this.requiredCredentials = requiredCredentials; this.requiredCredentials = requiredCredentials;
} }
public Collection<ClientEntity> getClients() {
return clients;
}
public void setClients(Collection<ClientEntity> clients) {
this.clients = clients;
}
public Collection<RoleEntity> getRoles() { public Collection<RoleEntity> getRoles() {
return roles; return roles;
} }

View file

@ -49,8 +49,11 @@ import java.util.Collection;
}) })
@NamedQueries({ @NamedQueries({
@NamedQuery(name="getClientRoles", query="select role from RoleEntity role where role.client = :client"), @NamedQuery(name="getClientRoles", query="select role from RoleEntity role where role.client = :client"),
@NamedQuery(name="getClientRoleIds", query="select role.id from RoleEntity role where role.client.id = :client"),
@NamedQuery(name="getClientRoleByName", query="select role from RoleEntity role where role.name = :name and role.client = :client"), @NamedQuery(name="getClientRoleByName", query="select role from RoleEntity role where role.name = :name and role.client = :client"),
@NamedQuery(name="getRealmRoleByName", query="select role from RoleEntity role where role.clientRole = false and role.name = :name and role.realm = :realm") @NamedQuery(name="getClientRoleIdByName", query="select role.id from RoleEntity role where role.name = :name and role.client.id = :client"),
@NamedQuery(name="getRealmRoleByName", query="select role from RoleEntity role where role.clientRole = false and role.name = :name and role.realm = :realm"),
@NamedQuery(name="getRealmRoleIdByName", query="select role.id from RoleEntity role where role.clientRole = false and role.name = :name and role.realm.id = :realm")
}) })
public class RoleEntity { public class RoleEntity {

View file

@ -39,6 +39,7 @@ import org.keycloak.models.mongo.keycloak.entities.MongoRoleEntity;
import org.keycloak.models.utils.KeycloakModelUtils; import org.keycloak.models.utils.KeycloakModelUtils;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections;
import java.util.List; import java.util.List;
/** /**
@ -105,7 +106,8 @@ public class MongoRealmProvider implements RealmProvider {
List<RealmModel> results = new ArrayList<RealmModel>(); List<RealmModel> results = new ArrayList<RealmModel>();
for (MongoRealmEntity realmEntity : realms) { for (MongoRealmEntity realmEntity : realms) {
results.add(new RealmAdapter(session, realmEntity, invocationContext)); RealmModel realm = session.realms().getRealm(realmEntity.getId());
if (realm != null) results.add(realm);
} }
return results; return results;
} }
@ -118,7 +120,7 @@ public class MongoRealmProvider implements RealmProvider {
MongoRealmEntity realm = getMongoStore().loadSingleEntity(MongoRealmEntity.class, query, invocationContext); MongoRealmEntity realm = getMongoStore().loadSingleEntity(MongoRealmEntity.class, query, invocationContext);
if (realm == null) return null; if (realm == null) return null;
return new RealmAdapter(session, realm, invocationContext); return session.realms().getRealm(realm.getId());
} }
@Override @Override
@ -162,6 +164,51 @@ public class MongoRealmProvider implements RealmProvider {
return new ClientAdapter(session, realm, appData, invocationContext); return new ClientAdapter(session, realm, appData, invocationContext);
} }
@Override
public ClientModel addClient(RealmModel realm, String clientId) {
return addClient(realm, KeycloakModelUtils.generateId(), clientId);
}
@Override
public ClientModel addClient(RealmModel realm, String id, String clientId) {
MongoClientEntity clientEntity = new MongoClientEntity();
clientEntity.setId(id);
clientEntity.setClientId(clientId);
clientEntity.setRealmId(realm.getId());
clientEntity.setEnabled(true);
clientEntity.setStandardFlowEnabled(true);
getMongoStore().insertEntity(clientEntity, invocationContext);
if (clientId == null) {
clientEntity.setClientId(clientEntity.getId());
getMongoStore().updateEntity(clientEntity, invocationContext);
}
final ClientModel model = new ClientAdapter(session, realm, clientEntity, invocationContext);
session.getKeycloakSessionFactory().publish(new RealmModel.ClientCreationEvent() {
@Override
public ClientModel getCreatedClient() {
return model;
}
});
return model;
}
@Override
public List<ClientModel> getClients(RealmModel realm) {
DBObject query = new QueryBuilder()
.and("realmId").is(realm.getId())
.get();
List<MongoClientEntity> clientEntities = getMongoStore().loadEntities(MongoClientEntity.class, query, invocationContext);
if (clientEntities.isEmpty()) return Collections.EMPTY_LIST;
List<ClientModel> result = new ArrayList<ClientModel>();
for (MongoClientEntity clientEntity : clientEntities) {
result.add(session.realms().getClientById(clientEntity.getId(), realm));
}
return Collections.unmodifiableList(result);
}
@Override @Override
public boolean removeClient(String id, RealmModel realm) { public boolean removeClient(String id, RealmModel realm) {
if (id == null) return false; if (id == null) return false;
@ -180,7 +227,8 @@ public class MongoRealmProvider implements RealmProvider {
.and("clientId").is(clientId) .and("clientId").is(clientId)
.get(); .get();
MongoClientEntity appEntity = getMongoStore().loadSingleEntity(MongoClientEntity.class, query, invocationContext); MongoClientEntity appEntity = getMongoStore().loadSingleEntity(MongoClientEntity.class, query, invocationContext);
return appEntity == null ? null : new ClientAdapter(session, realm, appEntity, invocationContext); if (appEntity == null) return null;
return session.realms().getClientById(appEntity.getId(), realm);
} }

View file

@ -815,47 +815,18 @@ public class RealmAdapter extends AbstractMongoAdapter<MongoRealmEntity> impleme
@Override @Override
public List<ClientModel> getClients() { public List<ClientModel> getClients() {
DBObject query = new QueryBuilder() return session.realms().getClients(this);
.and("realmId").is(getId())
.get();
List<MongoClientEntity> clientEntities = getMongoStore().loadEntities(MongoClientEntity.class, query, invocationContext);
if (clientEntities.isEmpty()) return Collections.EMPTY_LIST;
List<ClientModel> result = new ArrayList<ClientModel>();
for (MongoClientEntity clientEntity : clientEntities) {
result.add(new ClientAdapter(session, this, clientEntity, invocationContext));
}
return Collections.unmodifiableList(result);
} }
@Override @Override
public ClientModel addClient(String name) { public ClientModel addClient(String name) {
return this.addClient(null, name); return session.realms().addClient(this, name);
} }
@Override @Override
public ClientModel addClient(String id, String clientId) { public ClientModel addClient(String id, String clientId) {
MongoClientEntity clientEntity = new MongoClientEntity(); return session.realms().addClient(this, id, clientId);
clientEntity.setId(id);
clientEntity.setClientId(clientId);
clientEntity.setRealmId(getId());
clientEntity.setEnabled(true);
clientEntity.setStandardFlowEnabled(true);
getMongoStore().insertEntity(clientEntity, invocationContext);
if (clientId == null) {
clientEntity.setClientId(clientEntity.getId());
getMongoStore().updateEntity(clientEntity, invocationContext);
}
final ClientModel model = new ClientAdapter(session, this, clientEntity, invocationContext);
session.getKeycloakSessionFactory().publish(new ClientCreationEvent() {
@Override
public ClientModel getCreatedClient() {
return model;
}
});
return model;
} }
@Override @Override

View file

@ -35,6 +35,12 @@ public interface RealmProvider extends Provider {
RealmModel getRealm(String id); RealmModel getRealm(String id);
RealmModel getRealmByName(String name); RealmModel getRealmByName(String name);
ClientModel addClient(RealmModel realm, String clientId);
ClientModel addClient(RealmModel realm, String id, String clientId);
List<ClientModel> getClients(RealmModel realm);
ClientModel getClientById(String id, RealmModel realm); ClientModel getClientById(String id, RealmModel realm);
ClientModel getClientByClientId(String clientId, RealmModel realm); ClientModel getClientByClientId(String clientId, RealmModel realm);

View file

@ -29,7 +29,7 @@ public interface CacheRealmProvider extends RealmProvider {
void registerRealmInvalidation(String id); void registerRealmInvalidation(String id);
void registerApplicationInvalidation(String id); void registerClientInvalidation(String id);
void registerClientTemplateInvalidation(String id); void registerClientTemplateInvalidation(String id);
void registerRoleInvalidation(String id); void registerRoleInvalidation(String id);

View file

@ -26,15 +26,15 @@ import org.keycloak.models.RoleModel;
*/ */
public class CachedClientRole extends CachedRole { public class CachedClientRole extends CachedRole {
private final String idClient; private final String clientId;
public CachedClientRole(String idClient, RoleModel model, RealmModel realm) { public CachedClientRole(String clientId, RoleModel model, RealmModel realm) {
super(model, realm); super(model, realm);
this.idClient = idClient; this.clientId = clientId;
} }
public String getIdClient() { public String getClientId() {
return idClient; return clientId;
} }
} }

View file

@ -27,7 +27,10 @@
}, },
"userCache": { "userCache": {
"provider": "${keycloak.user.cache.provider:infinispan}", "provider": "${keycloak.user.cache.provider:default}",
"default" : {
"enabled": true
},
"mem": { "mem": {
"maxSize": 20000 "maxSize": 20000
} }
@ -95,15 +98,14 @@
}, },
"realmCache": { "realmCache": {
"provider": "infinispan-locking", "provider": "${keycloak.realm.cache.provider:default}",
"infinispan-locking" : { "default" : {
"enabled": true "enabled": true
} }
}, },
"connectionsInfinispan": { "connectionsInfinispan": {
"provider": "locking", "default": {
"locking": {
"clustered": "${keycloak.connectionsInfinispan.clustered:false}", "clustered": "${keycloak.connectionsInfinispan.clustered:false}",
"async": "${keycloak.connectionsInfinispan.async:true}", "async": "${keycloak.connectionsInfinispan.async:true}",
"sessionsOwners": "${keycloak.connectionsInfinispan.sessionsOwners:2}" "sessionsOwners": "${keycloak.connectionsInfinispan.sessionsOwners:2}"

View file

@ -27,6 +27,7 @@ import org.keycloak.admin.client.resource.RealmResource;
import org.keycloak.representations.idm.ClientRepresentation; import org.keycloak.representations.idm.ClientRepresentation;
import org.keycloak.representations.idm.RoleRepresentation; import org.keycloak.representations.idm.RoleRepresentation;
import javax.ws.rs.NotFoundException;
import javax.ws.rs.core.Response; import javax.ws.rs.core.Response;
import java.util.LinkedList; import java.util.LinkedList;
import java.util.List; import java.util.List;
@ -90,6 +91,7 @@ public class ConcurrencyTest extends AbstractClientTest {
@Test @Test
public void createClient() throws Throwable { public void createClient() throws Throwable {
System.out.println("***************************");
long start = System.currentTimeMillis(); long start = System.currentTimeMillis();
run(new KeycloakRunnable() { run(new KeycloakRunnable() {
@Override @Override
@ -101,7 +103,8 @@ public class ConcurrencyTest extends AbstractClientTest {
String id = ApiUtil.getCreatedId(response); String id = ApiUtil.getCreatedId(response);
response.close(); response.close();
assertNotNull(realm.clients().get(id)); c = realm.clients().get(id).toRepresentation();
assertNotNull(c);
boolean found = false; boolean found = false;
for (ClientRepresentation r : realm.clients().findAll()) { for (ClientRepresentation r : realm.clients().findAll()) {
if (r.getClientId().equals(name)) { if (r.getClientId().equals(name)) {
@ -119,6 +122,58 @@ public class ConcurrencyTest extends AbstractClientTest {
} }
@Test
@Ignore
public void createRemoveClient() throws Throwable {
// FYI< this will fail as HSQL seems to be trying to perform table locks.
System.out.println("***************************");
long start = System.currentTimeMillis();
run(new KeycloakRunnable() {
@Override
public void run(Keycloak keycloak, RealmResource realm, int threadNum, int iterationNum) {
String name = "c-" + threadNum + "-" + iterationNum;
ClientRepresentation c = new ClientRepresentation();
c.setClientId(name);
Response response = realm.clients().create(c);
String id = ApiUtil.getCreatedId(response);
response.close();
c = realm.clients().get(id).toRepresentation();
assertNotNull(c);
boolean found = false;
for (ClientRepresentation r : realm.clients().findAll()) {
if (r.getClientId().equals(name)) {
found = true;
break;
}
}
if (!found) {
fail("Client " + name + " not found in client list");
}
realm.clients().get(id).remove();
try {
c = realm.clients().get(id).toRepresentation();
fail("Client " + name + " should not be found. Should throw a 404");
} catch (NotFoundException e) {
}
found = false;
for (ClientRepresentation r : realm.clients().findAll()) {
if (r.getClientId().equals(name)) {
found = true;
break;
}
}
Assert.assertFalse("Client " + name + " should not be in client list", found);
}
});
long end = System.currentTimeMillis() - start;
System.out.println("createClient took " + end);
}
@Test @Test
public void createRole() throws Throwable { public void createRole() throws Throwable {
long start = System.currentTimeMillis(); long start = System.currentTimeMillis();

View file

@ -27,7 +27,7 @@
}, },
"userCache": { "userCache": {
"infinispan" : { "default" : {
"enabled": true "enabled": true
} }
}, },
@ -78,15 +78,13 @@
}, },
"realmCache": { "realmCache": {
"provider": "infinispan-locking", "default" : {
"infinispan-locking" : {
"enabled": true "enabled": true
} }
}, },
"connectionsInfinispan": { "connectionsInfinispan": {
"provider": "locking", "default": {
"locking": {
"clustered": "${keycloak.connectionsInfinispan.clustered:false}", "clustered": "${keycloak.connectionsInfinispan.clustered:false}",
"async": "${keycloak.connectionsInfinispan.async:true}", "async": "${keycloak.connectionsInfinispan.async:true}",
"sessionsOwners": "${keycloak.connectionsInfinispan.sessionsOwners:2}" "sessionsOwners": "${keycloak.connectionsInfinispan.sessionsOwners:2}"

View file

@ -19,7 +19,7 @@ log4j.rootLogger=info, stdout
log4j.appender.stdout=org.apache.log4j.ConsoleAppender log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%d{HH:mm:ss,SSS} %-5p [%c] %m%n log4j.appender.stdout.layout.ConversionPattern=%d{HH:mm:ss,SSS} %-5p %t [%c] %m%n
log4j.logger.org.keycloak=info log4j.logger.org.keycloak=info