mirror of
https://github.com/keycloak/keycloak.git
synced 2026-01-30 11:29:27 -06:00
Calling getTopLevelGroups is slow inside GroupLDAPStorageMapper#getLDAPGroupMappingsConverted (#8430)
Closes #14820 --------- Co-authored-by: Michal Hajas <mhajas@redhat.com>
This commit is contained in:
@@ -26,6 +26,7 @@ import org.keycloak.models.cache.infinispan.events.RealmCacheInvalidationEvent;
|
||||
import org.keycloak.models.cache.infinispan.stream.GroupListPredicate;
|
||||
import org.keycloak.models.cache.infinispan.stream.HasRolePredicate;
|
||||
import org.keycloak.models.cache.infinispan.stream.InClientPredicate;
|
||||
import org.keycloak.models.cache.infinispan.stream.InGroupPredicate;
|
||||
import org.keycloak.models.cache.infinispan.stream.InRealmPredicate;
|
||||
|
||||
import java.util.Set;
|
||||
@@ -96,6 +97,10 @@ public class RealmCacheManager extends CacheManager {
|
||||
addInvalidations(GroupListPredicate.create().realm(realmId), invalidations);
|
||||
}
|
||||
|
||||
public void groupNameInvalidations(String groupId, Set<String> invalidations) {
|
||||
addInvalidations(InGroupPredicate.create().group(groupId), invalidations);
|
||||
}
|
||||
|
||||
public void clientAdded(String realmId, String clientUUID, String clientId, Set<String> invalidations) {
|
||||
invalidations.add(RealmCacheSession.getRealmClientsQueryCacheKey(realmId));
|
||||
}
|
||||
|
||||
@@ -277,6 +277,7 @@ public class RealmCacheSession implements CacheRealmProvider {
|
||||
|
||||
private void invalidateGroup(String id, String realmId, boolean invalidateQueries) {
|
||||
invalidateGroup(id);
|
||||
cache.groupNameInvalidations(id, invalidations);
|
||||
if (invalidateQueries) {
|
||||
cache.groupQueriesInvalidations(realmId, invalidations);
|
||||
}
|
||||
@@ -564,6 +565,14 @@ public class RealmCacheSession implements CacheRealmProvider {
|
||||
return realm + ".top.groups";
|
||||
}
|
||||
|
||||
static String getGroupByNameCacheKey(String realm, String parentId, String name) {
|
||||
if (parentId != null) {
|
||||
return realm + ".group." + parentId + "." + name;
|
||||
} else {
|
||||
return realm + ".group.top." + name;
|
||||
}
|
||||
}
|
||||
|
||||
static String getRolesCacheKey(String container) {
|
||||
return container + ROLES_QUERY_SUFFIX;
|
||||
}
|
||||
@@ -886,6 +895,33 @@ public class RealmCacheSession implements CacheRealmProvider {
|
||||
return adapter;
|
||||
}
|
||||
|
||||
@Override
|
||||
public GroupModel getGroupByName(RealmModel realm, GroupModel parent, String name) {
|
||||
String cacheKey = getGroupByNameCacheKey(realm.getId(), parent != null? parent.getId(): null, name);
|
||||
GroupNameQuery query = cache.get(cacheKey, GroupNameQuery.class);
|
||||
if (query != null) {
|
||||
logger.tracev("Group by name cache hit: {0}", name);
|
||||
}
|
||||
if (query == null) {
|
||||
Long loaded = cache.getCurrentRevision(cacheKey);
|
||||
GroupModel model = getGroupDelegate().getGroupByName(realm, parent, name);
|
||||
if (model == null) return null;
|
||||
if (invalidations.contains(model.getId())) return model;
|
||||
query = new GroupNameQuery(loaded, cacheKey, model.getId(), realm);
|
||||
cache.addRevisioned(query, startupRevision);
|
||||
return model;
|
||||
} else if (invalidations.contains(cacheKey)) {
|
||||
return getGroupDelegate().getGroupByName(realm, parent, name);
|
||||
} else {
|
||||
String groupId = query.getGroupId();
|
||||
if (invalidations.contains(groupId)) {
|
||||
return getGroupDelegate().getGroupByName(realm, parent, name);
|
||||
}
|
||||
return getGroupById(realm, groupId);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void moveGroup(RealmModel realm, GroupModel group, GroupModel toParent) {
|
||||
invalidateGroup(group.getId(), realm.getId(), true);
|
||||
|
||||
49
model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/entities/GroupNameQuery.java
vendored
Normal file
49
model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/entities/GroupNameQuery.java
vendored
Normal file
@@ -0,0 +1,49 @@
|
||||
/*
|
||||
* Copyright 2022 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.entities;
|
||||
|
||||
import org.keycloak.models.RealmModel;
|
||||
|
||||
public class GroupNameQuery extends AbstractRevisioned implements InRealm {
|
||||
private final String realm;
|
||||
private final String realmName;
|
||||
private final String groupId;
|
||||
|
||||
public GroupNameQuery(Long revisioned, String id, String groupId, RealmModel realm) {
|
||||
super(revisioned, id);
|
||||
this.realm = realm.getId();
|
||||
this.realmName = realm.getName();
|
||||
this.groupId = groupId;
|
||||
}
|
||||
|
||||
public String getGroupId() {
|
||||
return groupId;
|
||||
}
|
||||
|
||||
public String getRealm() {
|
||||
return realm;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "GroupNameQuery{" +
|
||||
"id='" + getId() + "'" +
|
||||
"realmName='" + realmName + '\'' +
|
||||
'}';
|
||||
}
|
||||
}
|
||||
@@ -62,6 +62,7 @@ public class GroupMovedEvent extends InvalidationEvent implements RealmCacheInva
|
||||
@Override
|
||||
public void addInvalidations(RealmCacheManager realmCache, Set<String> invalidations) {
|
||||
realmCache.groupQueriesInvalidations(realmId, invalidations);
|
||||
realmCache.groupNameInvalidations(groupId, invalidations);
|
||||
if (newParentId != null) {
|
||||
invalidations.add(newParentId);
|
||||
}
|
||||
|
||||
@@ -60,6 +60,7 @@ public class GroupRemovedEvent extends InvalidationEvent implements RealmCacheIn
|
||||
@Override
|
||||
public void addInvalidations(RealmCacheManager realmCache, Set<String> invalidations) {
|
||||
realmCache.groupQueriesInvalidations(realmId, invalidations);
|
||||
realmCache.groupNameInvalidations(groupId, invalidations);
|
||||
if (parentId != null) {
|
||||
invalidations.add(parentId);
|
||||
}
|
||||
|
||||
@@ -54,7 +54,7 @@ public class GroupUpdatedEvent extends InvalidationEvent implements RealmCacheIn
|
||||
|
||||
@Override
|
||||
public void addInvalidations(RealmCacheManager realmCache, Set<String> invalidations) {
|
||||
// Nothing. ID already invalidated
|
||||
realmCache.groupNameInvalidations(groupId, invalidations);
|
||||
}
|
||||
|
||||
public static class ExternalizerImpl implements Externalizer<GroupUpdatedEvent> {
|
||||
|
||||
83
model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/stream/InGroupPredicate.java
vendored
Normal file
83
model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/stream/InGroupPredicate.java
vendored
Normal file
@@ -0,0 +1,83 @@
|
||||
/*
|
||||
* Copyright 2022 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.keycloak.models.cache.infinispan.entities.GroupNameQuery;
|
||||
import org.keycloak.models.cache.infinispan.entities.Revisioned;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.ObjectInput;
|
||||
import java.io.ObjectOutput;
|
||||
import java.io.Serializable;
|
||||
import java.util.Map;
|
||||
import java.util.function.Predicate;
|
||||
import org.infinispan.commons.marshall.Externalizer;
|
||||
import org.infinispan.commons.marshall.MarshallUtil;
|
||||
import org.infinispan.commons.marshall.SerializeWith;
|
||||
|
||||
@SerializeWith(InGroupPredicate.ExternalizerImpl.class)
|
||||
public class InGroupPredicate implements Predicate<Map.Entry<String, Revisioned>>, Serializable {
|
||||
private String group;
|
||||
|
||||
public static InGroupPredicate create() {
|
||||
return new InGroupPredicate();
|
||||
}
|
||||
|
||||
public InGroupPredicate group(String id) {
|
||||
group = id;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean test(Map.Entry<String, Revisioned> entry) {
|
||||
Object value = entry.getValue();
|
||||
if (value == null) return false;
|
||||
if (!(value instanceof GroupNameQuery)) return false;
|
||||
|
||||
return group.equals(((GroupNameQuery)value).getGroupId());
|
||||
}
|
||||
|
||||
public static class ExternalizerImpl implements Externalizer<InGroupPredicate> {
|
||||
|
||||
private static final int VERSION_1 = 1;
|
||||
|
||||
@Override
|
||||
public void writeObject(ObjectOutput output, InGroupPredicate obj) throws IOException {
|
||||
output.writeByte(VERSION_1);
|
||||
|
||||
MarshallUtil.marshallString(obj.group, output);
|
||||
}
|
||||
|
||||
@Override
|
||||
public InGroupPredicate readObject(ObjectInput input) throws IOException, ClassNotFoundException {
|
||||
switch (input.readByte()) {
|
||||
case VERSION_1:
|
||||
return readObjectVersion1(input);
|
||||
default:
|
||||
throw new IOException("Unknown version");
|
||||
}
|
||||
}
|
||||
|
||||
public InGroupPredicate readObjectVersion1(ObjectInput input) throws IOException, ClassNotFoundException {
|
||||
InGroupPredicate res = new InGroupPredicate();
|
||||
res.group = MarshallUtil.unmarshallString(input);
|
||||
|
||||
return res;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
/*
|
||||
* Copyright 2022 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.sessions.infinispan.entities.wildfly;
|
||||
|
||||
import org.keycloak.models.cache.infinispan.stream.InGroupPredicate;
|
||||
|
||||
public class InGroupPredicateWFExternalizer extends InfinispanExternalizerAdapter<InGroupPredicate> {
|
||||
|
||||
public InGroupPredicateWFExternalizer() {
|
||||
super(InGroupPredicate.class, new InGroupPredicate.ExternalizerImpl());
|
||||
}
|
||||
}
|
||||
@@ -55,6 +55,7 @@ org.keycloak.models.sessions.infinispan.entities.wildfly.LockEntryWFExternalizer
|
||||
org.keycloak.models.sessions.infinispan.entities.wildfly.ActionTokenValueEntityWFExternalizer
|
||||
org.keycloak.models.sessions.infinispan.entities.wildfly.RoleAddedEventWFExternalizer
|
||||
org.keycloak.models.sessions.infinispan.entities.wildfly.InClientPredicateWFExternalizer
|
||||
org.keycloak.models.sessions.infinispan.entities.wildfly.InGroupPredicateWFExternalizer
|
||||
org.keycloak.models.sessions.infinispan.entities.wildfly.UserFullInvalidationEventWFExternalizer
|
||||
org.keycloak.models.sessions.infinispan.entities.wildfly.ClientRemovedSessionEventWFExternalizer
|
||||
org.keycloak.models.sessions.infinispan.entities.wildfly.SessionPredicateWFExternalizer
|
||||
|
||||
@@ -435,6 +435,20 @@ public class JpaRealmProvider implements RealmProvider, ClientProvider, ClientSc
|
||||
return adapter;
|
||||
}
|
||||
|
||||
@Override
|
||||
public GroupModel getGroupByName(RealmModel realm, GroupModel parent, String name) {
|
||||
TypedQuery<String> query = em.createNamedQuery("getGroupIdByNameAndParent", String.class);
|
||||
query.setParameter("name", name);
|
||||
query.setParameter("realm", realm.getId());
|
||||
query.setParameter("parent", parent != null ? parent.getId() : GroupEntity.TOP_PARENT_ID);
|
||||
List<String> entities = query.getResultList();
|
||||
if (entities.isEmpty()) return null;
|
||||
if (entities.size() > 1) throw new IllegalStateException("Should not be more than one Group with same name");
|
||||
String id = query.getResultList().get(0);
|
||||
|
||||
return session.groups().getGroupById(realm, id);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void moveGroup(RealmModel realm, GroupModel group, GroupModel toParent) {
|
||||
if (toParent != null && group.getId().equals(toParent.getId())) {
|
||||
|
||||
@@ -37,7 +37,8 @@ import java.util.LinkedList;
|
||||
@NamedQuery(name="getGroupCountByNameContainingFromIdList", query="select count(u) from GroupEntity u where u.realm = :realm and lower(u.name) like lower(concat('%',:search,'%')) and u.id in :ids"),
|
||||
@NamedQuery(name="getTopLevelGroupIds", query="select u.id from GroupEntity u where u.parentId = :parent and u.realm = :realm order by u.name ASC"),
|
||||
@NamedQuery(name="getGroupCount", query="select count(u) from GroupEntity u where u.realm = :realm"),
|
||||
@NamedQuery(name="getTopLevelGroupCount", query="select count(u) from GroupEntity u where u.realm = :realm and u.parentId = :parent")
|
||||
@NamedQuery(name="getTopLevelGroupCount", query="select count(u) from GroupEntity u where u.realm = :realm and u.parentId = :parent"),
|
||||
@NamedQuery(name="getGroupIdByNameAndParent", query="select u.id from GroupEntity u where u.realm = :realm and u.parentId = :parent and u.name = :name")
|
||||
})
|
||||
@Entity
|
||||
@Table(name="KEYCLOAK_GROUP",
|
||||
|
||||
@@ -55,6 +55,11 @@ public class GroupStorageManager extends AbstractStorageManager<GroupStorageProv
|
||||
return provider.getGroupById(realm, id);
|
||||
}
|
||||
|
||||
@Override
|
||||
public GroupModel getGroupByName(RealmModel realm, GroupModel parent, String name) {
|
||||
return localStorage().getGroupByName(realm, parent, name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Stream<GroupModel> searchGroupsByAttributes(RealmModel realm, Map<String, String> attributes, Integer firstResult, Integer maxResults) {
|
||||
Stream<GroupModel> local = localStorage().searchGroupsByAttributes(realm, attributes, firstResult, maxResults);
|
||||
|
||||
@@ -93,6 +93,28 @@ public class MapGroupProvider implements GroupProvider {
|
||||
: entityToAdapterFunc(realm).apply(entity);
|
||||
}
|
||||
|
||||
@Override
|
||||
public GroupModel getGroupByName(RealmModel realm, GroupModel parent, String name) {
|
||||
if (name == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
LOG.tracef("getGroupByName(%s, %s)%s", realm, name, getShortStackTrace());
|
||||
|
||||
DefaultModelCriteria<GroupModel> mcb = criteria();
|
||||
mcb = mcb.compare(SearchableFields.NAME, Operator.EQ, name)
|
||||
.compare(SearchableFields.REALM_ID, Operator.EQ, realm.getId());
|
||||
if (parent != null) {
|
||||
mcb = mcb.compare(SearchableFields.PARENT_ID, Operator.EQ, parent.getId());
|
||||
} else {
|
||||
mcb = mcb.compare(SearchableFields.PARENT_ID, Operator.NOT_EXISTS);
|
||||
}
|
||||
QueryParameters<GroupModel> queryParameters = withCriteria(mcb);
|
||||
String groupId = storeWithRealm(realm).read(queryParameters).findFirst().map(MapGroupEntity::getId)
|
||||
.orElse(null);
|
||||
return groupId == null ? null : session.groups().getGroupById(realm, groupId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Stream<GroupModel> getGroupsStream(RealmModel realm) {
|
||||
return getGroupsStreamInternal(realm, null, null);
|
||||
|
||||
Reference in New Issue
Block a user