group list caching

This commit is contained in:
Bill Burke 2016-02-24 17:21:11 -05:00
parent 9488787986
commit f10f00ba71
11 changed files with 422 additions and 171 deletions

View file

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

View file

@ -1303,67 +1303,45 @@ public class RealmAdapter implements RealmModel {
return cached.getRequiredActionProvidersByAlias().get(alias);
}
@Override
public GroupModel getGroupById(String id) {
if (updated != null) return updated.getGroupById(id);
return cacheSession.getGroupById(id, this);
}
@Override
public List<GroupModel> getGroups() {
if (updated != null) return updated.getGroups();
if (cached.getGroups().isEmpty()) return Collections.EMPTY_LIST;
List<GroupModel> list = new LinkedList<>();
for (String id : cached.getGroups()) {
GroupModel group = cacheSession.getGroupById(id, this);
if (group == null) continue;
list.add(group);
}
return Collections.unmodifiableList(list);
}
@Override
public List<GroupModel> getTopLevelGroups() {
List<GroupModel> base = getGroups();
if (base.isEmpty()) return base;
List<GroupModel> copy = new LinkedList<>();
for (GroupModel group : base) {
if (group.getParent() == null) {
copy.add(group);
}
}
return Collections.unmodifiableList(copy);
}
@Override
public boolean removeGroup(GroupModel group) {
getDelegateForUpdate();
return updated.removeGroup(group);
}
@Override
public GroupModel createGroup(String name) {
getDelegateForUpdate();
return updated.createGroup(name);
return cacheSession.createGroup(this, name);
}
@Override
public GroupModel createGroup(String id, String name) {
getDelegateForUpdate();
return updated.createGroup(id, name);
return cacheSession.createGroup(this, id, name);
}
@Override
public void addTopLevelGroup(GroupModel subGroup) {
getDelegateForUpdate();
updated.addTopLevelGroup(subGroup);
cacheSession.addTopLevelGroup(this, subGroup);
}
@Override
public void moveGroup(GroupModel group, GroupModel toParent) {
getDelegateForUpdate();
updated.moveGroup(group, toParent);
cacheSession.moveGroup(this, group, toParent);
}
@Override
public GroupModel getGroupById(String id) {
return cacheSession.getGroupById(id, this);
}
@Override
public List<GroupModel> getGroups() {
return cacheSession.getGroups(this);
}
@Override
public List<GroupModel> getTopLevelGroups() {
return cacheSession.getTopLevelGroups(this);
}
@Override
public boolean removeGroup(GroupModel group) {
return cacheSession.removeGroup(this, group);
}
@Override

View file

@ -397,6 +397,14 @@ public class StreamCacheRealmProvider implements CacheRealmProvider {
return realm + REALM_CLIENTS_QUERY_SUFFIX;
}
private String getGroupsQueryCacheKey(String realm) {
return realm + ".groups";
}
private String getTopGroupsQueryCacheKey(String realm) {
return realm + ".top.groups";
}
private String getRolesCacheKey(String container) {
return container + ROLES_QUERY_SUFFIX;
}
@ -687,6 +695,129 @@ public class StreamCacheRealmProvider implements CacheRealmProvider {
return adapter;
}
@Override
public void moveGroup(RealmModel realm, GroupModel group, GroupModel toParent) {
registerGroupInvalidation(group.getId());
if (toParent != null) registerGroupInvalidation(toParent.getId());
getDelegate().moveGroup(realm, group, toParent);
}
@Override
public List<GroupModel> getGroups(RealmModel realm) {
String cacheKey = getGroupsQueryCacheKey(realm.getId());
boolean queryDB = invalidations.contains(cacheKey) || listInvalidations.contains(realm.getId());
if (queryDB) {
return getDelegate().getGroups(realm);
}
GroupListQuery query = cache.get(cacheKey, GroupListQuery.class);
if (query != null) {
logger.tracev("getGroups cache hit: {0}", realm.getName());
}
if (query == null) {
Long loaded = cache.getCurrentRevision(cacheKey);
List<GroupModel> model = getDelegate().getGroups(realm);
if (model == null) return null;
Set<String> ids = new HashSet<>();
for (GroupModel client : model) ids.add(client.getId());
query = new GroupListQuery(loaded, cacheKey, realm, ids);
logger.tracev("adding realm getGroups cache miss: realm {0} key {1}", realm.getName(), cacheKey);
cache.addRevisioned(query);
return model;
}
List<GroupModel> list = new LinkedList<>();
for (String id : query.getGroups()) {
GroupModel group = session.realms().getGroupById(id, realm);
if (group == null) {
invalidations.add(cacheKey);
return getDelegate().getGroups(realm);
}
list.add(group);
}
return list;
}
@Override
public List<GroupModel> getTopLevelGroups(RealmModel realm) {
String cacheKey = getTopGroupsQueryCacheKey(realm.getId());
boolean queryDB = invalidations.contains(cacheKey) || listInvalidations.contains(realm.getId());
if (queryDB) {
return getDelegate().getTopLevelGroups(realm);
}
GroupListQuery query = cache.get(cacheKey, GroupListQuery.class);
if (query != null) {
logger.tracev("getTopLevelGroups cache hit: {0}", realm.getName());
}
if (query == null) {
Long loaded = cache.getCurrentRevision(cacheKey);
List<GroupModel> model = getDelegate().getTopLevelGroups(realm);
if (model == null) return null;
Set<String> ids = new HashSet<>();
for (GroupModel client : model) ids.add(client.getId());
query = new GroupListQuery(loaded, cacheKey, realm, ids);
logger.tracev("adding realm getTopLevelGroups cache miss: realm {0} key {1}", realm.getName(), cacheKey);
cache.addRevisioned(query);
return model;
}
List<GroupModel> list = new LinkedList<>();
for (String id : query.getGroups()) {
GroupModel group = session.realms().getGroupById(id, realm);
if (group == null) {
invalidations.add(cacheKey);
return getDelegate().getTopLevelGroups(realm);
}
list.add(group);
}
return list;
}
@Override
public boolean removeGroup(RealmModel realm, GroupModel group) {
registerGroupInvalidation(group.getId());
listInvalidations.add(realm.getId());
invalidations.add(getGroupsQueryCacheKey(realm.getId()));
if (group.getParentId() == null) {
invalidations.add(getTopGroupsQueryCacheKey(realm.getId()));
} else {
registerGroupInvalidation(group.getParentId());
}
return getDelegate().removeGroup(realm, group);
}
@Override
public GroupModel createGroup(RealmModel realm, String name) {
GroupModel group = getDelegate().createGroup(realm, name);
return groupAdded(realm, group);
}
public GroupModel groupAdded(RealmModel realm, GroupModel group) {
listInvalidations.add(realm.getId());
invalidations.add(getGroupsQueryCacheKey(realm.getId()));
invalidations.add(getTopGroupsQueryCacheKey(realm.getId()));
invalidations.add(group.getId());
return group;
}
@Override
public GroupModel createGroup(RealmModel realm, String id, String name) {
GroupModel group = getDelegate().createGroup(realm, id, name);
return groupAdded(realm, group);
}
@Override
public void addTopLevelGroup(RealmModel realm, GroupModel subGroup) {
invalidations.add(getTopGroupsQueryCacheKey(realm.getId()));
invalidations.add(subGroup.getId());
if (subGroup.getParentId() != null) {
registerGroupInvalidation(subGroup.getParentId());
}
getDelegate().addTopLevelGroup(realm, subGroup);
}
@Override
public ClientModel getClientById(String id, RealmModel realm) {
CachedClient cached = cache.get(id, CachedClient.class);

View file

@ -281,6 +281,96 @@ public class JpaRealmProvider implements RealmProvider {
return new GroupAdapter(realm, em, groupEntity);
}
@Override
public void moveGroup(RealmModel realm, GroupModel group, GroupModel toParent) {
if (toParent != null && group.getId().equals(toParent.getId())) {
return;
}
if (group.getParentId() != null) {
group.getParent().removeChild(group);
}
group.setParent(toParent);
if (toParent != null) toParent.addChild(group);
else session.realms().addTopLevelGroup(realm, group);
}
@Override
public List<GroupModel> getGroups(RealmModel realm) {
List<String> groups = em.createNamedQuery("getAllGroupIdsByRealm", String.class)
.setParameter("realm", realm.getId()).getResultList();
if (groups == null) return Collections.EMPTY_LIST;
List<GroupModel> list = new LinkedList<>();
for (String id : groups) {
list.add(session.realms().getGroupById(id, realm));
}
return Collections.unmodifiableList(list);
}
@Override
public List<GroupModel> getTopLevelGroups(RealmModel realm) {
List<String> groups = em.createNamedQuery("getTopLevelGroupIds", String.class)
.setParameter("realm", realm.getId())
.getResultList();
if (groups == null) return Collections.EMPTY_LIST;
List<GroupModel> list = new LinkedList<>();
for (String id : groups) {
list.add(session.realms().getGroupById(id, realm));
}
return Collections.unmodifiableList(list);
}
@Override
public boolean removeGroup(RealmModel realm, GroupModel group) {
if (group == null) {
return false;
}
session.users().preRemove(realm, group);
realm.removeDefaultGroup(group);
for (GroupModel subGroup : group.getSubGroups()) {
session.realms().removeGroup(realm, subGroup);
}
moveGroup(realm, group, null);
GroupEntity groupEntity = em.find(GroupEntity.class, group.getId());
if (!groupEntity.getRealm().getId().equals(realm.getId())) {
return false;
}
// I don't think we need this as GroupEntity has cascade removal. It causes batch errors if you turn this on.
// em.createNamedQuery("deleteGroupAttributesByGroup").setParameter("group", groupEntity).executeUpdate();
em.createNamedQuery("deleteGroupRoleMappingsByGroup").setParameter("group", groupEntity).executeUpdate();
em.remove(groupEntity);
return true;
}
@Override
public GroupModel createGroup(RealmModel realm, String name) {
String id = KeycloakModelUtils.generateId();
return createGroup(realm, id, name);
}
@Override
public GroupModel createGroup(RealmModel realm, String id, String name) {
if (id == null) id = KeycloakModelUtils.generateId();
GroupEntity groupEntity = new GroupEntity();
groupEntity.setId(id);
groupEntity.setName(name);
RealmEntity realmEntity = em.getReference(RealmEntity.class, realm.getId());
groupEntity.setRealm(realmEntity);
em.persist(groupEntity);
return new GroupAdapter(realm, em, groupEntity);
}
@Override
public void addTopLevelGroup(RealmModel realm, GroupModel subGroup) {
subGroup.setParent(null);
}
@Override
public ClientModel addClient(RealmModel realm, String clientId) {
return addClient(realm, KeycloakModelUtils.generateId(), clientId);

View file

@ -1987,17 +1987,25 @@ public class RealmAdapter implements RealmModel {
return null;
}
@Override
public GroupModel createGroup(String name) {
return session.realms().createGroup(this, name);
}
@Override
public GroupModel createGroup(String id, String name) {
return session.realms().createGroup(this, id, name);
}
@Override
public void addTopLevelGroup(GroupModel subGroup) {
session.realms().addTopLevelGroup(this, subGroup);
}
@Override
public void moveGroup(GroupModel group, GroupModel toParent) {
if (toParent != null && group.getId().equals(toParent.getId())) {
return;
}
if (group.getParentId() != null) {
group.getParent().removeChild(group);
}
group.setParent(toParent);
if (toParent != null) toParent.addChild(group);
else addTopLevelGroup(group);
session.realms().moveGroup(this, group, toParent);
}
@Override
@ -2007,75 +2015,17 @@ public class RealmAdapter implements RealmModel {
@Override
public List<GroupModel> getGroups() {
List<String> groups = em.createNamedQuery("getAllGroupIdsByRealm", String.class)
.setParameter("realm", realm.getId()).getResultList();
if (groups == null) return Collections.EMPTY_LIST;
List<GroupModel> list = new LinkedList<>();
for (String id : groups) {
list.add(session.realms().getGroupById(id, this));
}
return Collections.unmodifiableList(list);
return session.realms().getGroups(this);
}
@Override
public List<GroupModel> getTopLevelGroups() {
List<GroupModel> base = getGroups();
if (base.isEmpty()) return base;
List<GroupModel> copy = new LinkedList<>();
for (GroupModel group : base) {
if (group.getParent() == null) {
copy.add(group);
}
}
return Collections.unmodifiableList(copy);
return session.realms().getTopLevelGroups(this);
}
@Override
public boolean removeGroup(GroupModel group) {
if (group == null) {
return false;
}
GroupEntity groupEntity = GroupAdapter.toEntity(group, em);
if (!groupEntity.getRealm().getId().equals(getId())) {
return false;
}
realm.getDefaultGroups().remove(groupEntity);
for (GroupModel subGroup : group.getSubGroups()) {
removeGroup(subGroup);
}
session.users().preRemove(this, group);
moveGroup(group, null);
em.createNamedQuery("deleteGroupAttributesByGroup").setParameter("group", groupEntity).executeUpdate();
em.createNamedQuery("deleteGroupRoleMappingsByGroup").setParameter("group", groupEntity).executeUpdate();
em.remove(groupEntity);
return true;
}
@Override
public GroupModel createGroup(String name) {
String id = KeycloakModelUtils.generateId();
return createGroup(id, name);
}
@Override
public GroupModel createGroup(String id, String name) {
if (id == null) id = KeycloakModelUtils.generateId();
GroupEntity groupEntity = new GroupEntity();
groupEntity.setId(id);
groupEntity.setName(name);
groupEntity.setRealm(realm);
em.persist(groupEntity);
return new GroupAdapter(this, em, groupEntity);
}
@Override
public void addTopLevelGroup(GroupModel subGroup) {
subGroup.setParent(null);
return session.realms().removeGroup(this, group);
}
@Override

View file

@ -42,6 +42,7 @@ import java.util.Collection;
@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="getGroupIdsByParent", query="select u.id from GroupEntity u where u.parent = :parent"),
@NamedQuery(name="getTopLevelGroupIds", query="select u.id from GroupEntity u where u.parent is null and u.realm.id = :realm"),
@NamedQuery(name="getGroupCount", query="select count(u) from GroupEntity u where u.realm = :realm"),
@NamedQuery(name="deleteGroupsByRealm", query="delete from GroupEntity u where u.realm = :realm")
})
@ -64,7 +65,9 @@ public class GroupEntity {
@JoinColumn(name = "REALM_ID")
private RealmEntity realm;
@OneToMany(cascade = CascadeType.REMOVE, orphanRemoval = true, mappedBy="group")
@OneToMany(
cascade = CascadeType.REMOVE,
orphanRemoval = true, mappedBy="group")
protected Collection<GroupAttributeEntity> attributes = new ArrayList<GroupAttributeEntity>();
public String getId() {

View file

@ -41,6 +41,7 @@ import org.keycloak.models.utils.KeycloakModelUtils;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;
@ -154,6 +155,92 @@ public class MongoRealmProvider implements RealmProvider {
return new GroupAdapter(session, realm, group, invocationContext);
}
@Override
public void moveGroup(RealmModel realm, GroupModel group, GroupModel toParent) {
if (toParent != null && group.getId().equals(toParent.getId())) {
return;
}
if (group.getParentId() != null) {
group.getParent().removeChild(group);
}
group.setParent(toParent);
if (toParent != null) toParent.addChild(group);
else session.realms().addTopLevelGroup(realm, group);
}
@Override
public List<GroupModel> getGroups(RealmModel realm) {
DBObject query = new QueryBuilder()
.and("realmId").is(realm.getId())
.get();
List<MongoGroupEntity> groups = getMongoStore().loadEntities(MongoGroupEntity.class, query, invocationContext);
if (groups == null) return Collections.EMPTY_LIST;
List<GroupModel> result = new LinkedList<>();
if (groups == null) return result;
for (MongoGroupEntity group : groups) {
result.add(getGroupById(group.getId(), realm));
}
return Collections.unmodifiableList(result);
}
@Override
public List<GroupModel> getTopLevelGroups(RealmModel realm) {
DBObject query = new QueryBuilder()
.and("realmId").is(realm.getId())
.and("parentId").is(null)
.get();
List<MongoGroupEntity> groups = getMongoStore().loadEntities(MongoGroupEntity.class, query, invocationContext);
if (groups == null) return Collections.EMPTY_LIST;
List<GroupModel> result = new LinkedList<>();
if (groups == null) return result;
for (MongoGroupEntity group : groups) {
result.add(getGroupById(group.getId(), realm));
}
return Collections.unmodifiableList(result);
}
@Override
public boolean removeGroup(RealmModel realm, GroupModel group) {
session.users().preRemove(realm, group);
realm.removeDefaultGroup(group);
for (GroupModel subGroup : group.getSubGroups()) {
removeGroup(realm, subGroup);
}
moveGroup(realm, group, null);
return getMongoStore().removeEntity(MongoGroupEntity.class, group.getId(), invocationContext);
}
@Override
public GroupModel createGroup(RealmModel realm, String name) {
String id = KeycloakModelUtils.generateId();
return createGroup(realm, id, name);
}
@Override
public GroupModel createGroup(RealmModel realm, String id, String name) {
if (id == null) id = KeycloakModelUtils.generateId();
MongoGroupEntity group = new MongoGroupEntity();
group.setId(id);
group.setName(name);
group.setRealmId(realm.getId());
getMongoStore().insertEntity(group, invocationContext);
return new GroupAdapter(session, realm, group, invocationContext);
}
@Override
public void addTopLevelGroup(RealmModel realm, GroupModel subGroup) {
subGroup.setParent(null);
}
@Override
public ClientModel getClientById(String id, RealmModel realm) {
MongoClientEntity appData = getMongoStore().loadEntity(MongoClientEntity.class, id, invocationContext);

View file

@ -645,40 +645,23 @@ public class RealmAdapter extends AbstractMongoAdapter<MongoRealmEntity> impleme
@Override
public GroupModel createGroup(String name) {
String id = KeycloakModelUtils.generateId();
return createGroup(id, name);
return session.realms().createGroup(this, name);
}
@Override
public GroupModel createGroup(String id, String name) {
if (id == null) id = KeycloakModelUtils.generateId();
MongoGroupEntity group = new MongoGroupEntity();
group.setId(id);
group.setName(name);
group.setRealmId(getId());
getMongoStore().insertEntity(group, invocationContext);
return new GroupAdapter(session, this, group, invocationContext);
return session.realms().createGroup(this, id, name);
}
@Override
public void addTopLevelGroup(GroupModel subGroup) {
subGroup.setParent(null);
session.realms().addTopLevelGroup(this, subGroup);
}
@Override
public void moveGroup(GroupModel group, GroupModel toParent) {
if (toParent != null && group.getId().equals(toParent.getId())) {
return;
}
if (group.getParentId() != null) {
group.getParent().removeChild(group);
}
group.setParent(toParent);
if (toParent != null) toParent.addChild(group);
else addTopLevelGroup(group);
session.realms().moveGroup(this, group, toParent);
}
@Override
@ -688,46 +671,17 @@ public class RealmAdapter extends AbstractMongoAdapter<MongoRealmEntity> impleme
@Override
public List<GroupModel> getGroups() {
DBObject query = new QueryBuilder()
.and("realmId").is(getId())
.get();
List<MongoGroupEntity> groups = getMongoStore().loadEntities(MongoGroupEntity.class, query, invocationContext);
if (groups == null) return Collections.EMPTY_LIST;
List<GroupModel> result = new LinkedList<>();
if (groups == null) return result;
for (MongoGroupEntity group : groups) {
result.add(model.getGroupById(group.getId(), this));
}
return Collections.unmodifiableList(result);
return session.realms().getGroups(this);
}
@Override
public List<GroupModel> getTopLevelGroups() {
List<GroupModel> base = getGroups();
if (base.isEmpty()) return base;
List<GroupModel> copy = new LinkedList<>();
for (GroupModel group : base) {
if (group.getParent() == null) {
copy.add(group);
}
}
return Collections.unmodifiableList(copy);
return session.realms().getTopLevelGroups(this);
}
@Override
public boolean removeGroup(GroupModel group) {
if (realm.getDefaultGroups() != null) {
getMongoStore().pullItemFromList(realm, "defaultGroups", group.getId(), invocationContext);
}
for (GroupModel subGroup : group.getSubGroups()) {
removeGroup(subGroup);
}
session.users().preRemove(this, group);
moveGroup(group, null);
return getMongoStore().removeEntity(MongoGroupEntity.class, group.getId(), invocationContext);
return session.realms().removeGroup(this, group);
}

View file

@ -36,6 +36,20 @@ public interface RealmProvider extends Provider {
RealmModel getRealm(String id);
RealmModel getRealmByName(String name);
void moveGroup(RealmModel realm, GroupModel group, GroupModel toParent);
List<GroupModel> getGroups(RealmModel realm);
List<GroupModel> getTopLevelGroups(RealmModel realm);
boolean removeGroup(RealmModel realm, GroupModel group);
GroupModel createGroup(RealmModel realm, String name);
GroupModel createGroup(RealmModel realm, String id, String name);
void addTopLevelGroup(RealmModel realm, GroupModel subGroup);
ClientModel addClient(RealmModel realm, String clientId);
ClientModel addClient(RealmModel realm, String id, String clientId);

View file

@ -62,7 +62,7 @@
"user": "${keycloak.connectionsJpa.user:sa}",
"password": "${keycloak.connectionsJpa.password:}",
"databaseSchema": "${keycloak.connectionsJpa.databaseSchema:update}",
"showSql": "${keycloak.connectionsJpa.showSql:false}",
"showSql": "${keycloak.connectionsJpa.showSql:true}",
"formatSql": "${keycloak.connectionsJpa.formatSql:true}"
}
},