From dc1c9b944f5d5ebf9a6ed281a2e95f549e25c600 Mon Sep 17 00:00:00 2001 From: mhajas Date: Wed, 30 Jun 2021 16:14:16 +0200 Subject: [PATCH] KEYCLOAK-18370 Introduce QueryParameters --- .../MapRootAuthenticationSessionProvider.java | 5 +- .../MapPermissionTicketStore.java | 55 ++++--- .../map/authorization/MapPolicyStore.java | 50 +++---- .../map/authorization/MapResourceStore.java | 51 +++---- .../map/authorization/MapScopeStore.java | 19 +-- .../entity/MapPermissionTicketEntity.java | 2 - .../authorization/entity/MapPolicyEntity.java | 2 - .../entity/MapResourceEntity.java | 2 - .../models/map/client/MapClientProvider.java | 40 +++-- .../clientscope/MapClientScopeProvider.java | 10 +- .../models/map/group/MapGroupProvider.java | 85 ++++++----- .../MapUserLoginFailureProvider.java | 9 +- .../models/map/realm/MapRealmProvider.java | 11 +- .../models/map/role/MapRoleProvider.java | 54 +++---- .../map/storage/MapKeycloakTransaction.java | 14 +- .../models/map/storage/MapStorage.java | 25 ++-- .../models/map/storage/QueryParameters.java | 139 ++++++++++++++++++ .../ConcurrentHashMapKeycloakTransaction.java | 43 +++--- .../storage/chm/ConcurrentHashMapStorage.java | 46 ++++-- ...ncurrentHashMapStorageProviderFactory.java | 4 +- .../map/storage/chm/MapFieldPredicates.java | 50 ++++++- .../UserSessionConcurrentHashMapStorage.java | 15 +- .../models/map/user/MapUserEntity.java | 2 - .../models/map/user/MapUserProvider.java | 55 ++++--- .../userSession/MapUserSessionProvider.java | 59 ++++---- .../storage/SearchableModelField.java | 25 ---- 26 files changed, 520 insertions(+), 352 deletions(-) create mode 100644 model/map/src/main/java/org/keycloak/models/map/storage/QueryParameters.java diff --git a/model/map/src/main/java/org/keycloak/models/map/authSession/MapRootAuthenticationSessionProvider.java b/model/map/src/main/java/org/keycloak/models/map/authSession/MapRootAuthenticationSessionProvider.java index 3c7f15f411..0d44cf4400 100644 --- a/model/map/src/main/java/org/keycloak/models/map/authSession/MapRootAuthenticationSessionProvider.java +++ b/model/map/src/main/java/org/keycloak/models/map/authSession/MapRootAuthenticationSessionProvider.java @@ -40,6 +40,7 @@ import java.util.function.Predicate; import static org.keycloak.common.util.StackUtil.getShortStackTrace; import static org.keycloak.models.map.common.MapStorageUtils.registerEntityForChanges; +import static org.keycloak.models.map.storage.QueryParameters.withCriteria; /** * @author Martin Kanis @@ -144,7 +145,7 @@ public class MapRootAuthenticationSessionProvider implements AuthenticationSe .compare(SearchableFields.REALM_ID, Operator.EQ, realm.getId()) .compare(SearchableFields.TIMESTAMP, Operator.LT, expired); - long deletedCount = tx.delete(sessionStore.getKeyConvertor().yieldNewUniqueKey(), mcb); + long deletedCount = tx.delete(sessionStore.getKeyConvertor().yieldNewUniqueKey(), withCriteria(mcb)); LOG.debugf("Removed %d expired authentication sessions for realm '%s'", deletedCount, realm.getName()); } @@ -155,7 +156,7 @@ public class MapRootAuthenticationSessionProvider implements AuthenticationSe ModelCriteriaBuilder mcb = sessionStore.createCriteriaBuilder() .compare(SearchableFields.REALM_ID, Operator.EQ, realm.getId()); - sessionStore.delete(mcb); + sessionStore.delete(withCriteria(mcb)); } @Override diff --git a/model/map/src/main/java/org/keycloak/models/map/authorization/MapPermissionTicketStore.java b/model/map/src/main/java/org/keycloak/models/map/authorization/MapPermissionTicketStore.java index ef02c1872b..3b3a5701d5 100644 --- a/model/map/src/main/java/org/keycloak/models/map/authorization/MapPermissionTicketStore.java +++ b/model/map/src/main/java/org/keycloak/models/map/authorization/MapPermissionTicketStore.java @@ -35,7 +35,6 @@ import org.keycloak.models.map.storage.ModelCriteriaBuilder; import org.keycloak.models.map.storage.ModelCriteriaBuilder.Operator; import java.util.Collections; -import java.util.Comparator; import java.util.EnumMap; import java.util.List; import java.util.Map; @@ -45,6 +44,8 @@ import java.util.stream.Collectors; import static org.keycloak.common.util.StackUtil.getShortStackTrace; import static org.keycloak.models.map.common.MapStorageUtils.registerEntityForChanges; +import static org.keycloak.models.map.storage.QueryParameters.Order.ASCENDING; +import static org.keycloak.models.map.storage.QueryParameters.withCriteria; import static org.keycloak.utils.StreamsUtil.distinctByKey; import static org.keycloak.utils.StreamsUtil.paginatedStream; @@ -90,7 +91,7 @@ public class MapPermissionTicketStore> implements Permis .toArray(ModelCriteriaBuilder[]::new) ); - return tx.getCount(mcb); + return tx.getCount(withCriteria(mcb)); } @Override @@ -108,8 +109,8 @@ public class MapPermissionTicketStore> implements Permis if (scopeId != null) { mcb = mcb.compare(SearchableFields.SCOPE_ID, Operator.EQ, scopeId); } - - if (tx.getCount(mcb) > 0) { + + if (tx.getCount(withCriteria(mcb)) > 0) { throw new ModelDuplicateException("Permission ticket for resource server: '" + resourceServer.getId() + ", Resource: " + resourceId + ", owner: " + owner + ", scopeId: " + scopeId + " already exists."); } @@ -142,8 +143,8 @@ public class MapPermissionTicketStore> implements Permis public PermissionTicket findById(String id, String resourceServerId) { LOG.tracef("findById(%s, %s)%s", id, resourceServerId, getShortStackTrace()); - return tx.read(forResourceServer(resourceServerId) - .compare(PermissionTicket.SearchableFields.ID, Operator.EQ, id)) + return tx.read(withCriteria(forResourceServer(resourceServerId) + .compare(SearchableFields.ID, Operator.EQ, id))) .findFirst() .map(this::entityToAdapter) .orElse(null); @@ -153,7 +154,7 @@ public class MapPermissionTicketStore> implements Permis public List findByResourceServer(String resourceServerId) { LOG.tracef("findByResourceServer(%s)%s", resourceServerId, getShortStackTrace()); - return tx.read(forResourceServer(resourceServerId)) + return tx.read(withCriteria(forResourceServer(resourceServerId))) .map(this::entityToAdapter) .collect(Collectors.toList()); } @@ -162,8 +163,8 @@ public class MapPermissionTicketStore> implements Permis public List findByOwner(String owner, String resourceServerId) { LOG.tracef("findByOwner(%s, %s)%s", owner, resourceServerId, getShortStackTrace()); - return tx.read(forResourceServer(resourceServerId) - .compare(SearchableFields.OWNER, Operator.EQ, owner)) + return tx.read(withCriteria(forResourceServer(resourceServerId) + .compare(SearchableFields.OWNER, Operator.EQ, owner))) .map(this::entityToAdapter) .collect(Collectors.toList()); } @@ -172,8 +173,8 @@ public class MapPermissionTicketStore> implements Permis public List findByResource(String resourceId, String resourceServerId) { LOG.tracef("findByResource(%s, %s)%s", resourceId, resourceServerId, getShortStackTrace()); - return tx.read(forResourceServer(resourceServerId) - .compare(SearchableFields.RESOURCE_ID, Operator.EQ, resourceId)) + return tx.read(withCriteria(forResourceServer(resourceServerId) + .compare(SearchableFields.RESOURCE_ID, Operator.EQ, resourceId))) .map(this::entityToAdapter) .collect(Collectors.toList()); } @@ -182,8 +183,8 @@ public class MapPermissionTicketStore> implements Permis public List findByScope(String scopeId, String resourceServerId) { LOG.tracef("findByScope(%s, %s)%s", scopeId, resourceServerId, getShortStackTrace()); - return tx.read(forResourceServer(resourceServerId) - .compare(SearchableFields.SCOPE_ID, Operator.EQ, scopeId)) + return tx.read(withCriteria(forResourceServer(resourceServerId) + .compare(SearchableFields.SCOPE_ID, Operator.EQ, scopeId))) .map(this::entityToAdapter) .collect(Collectors.toList()); } @@ -212,11 +213,9 @@ public class MapPermissionTicketStore> implements Permis .toArray(ModelCriteriaBuilder[]::new) ); - Comparator> c = Comparator.comparing(MapPermissionTicketEntity::getId); - return paginatedStream(tx.read(mcb) - .sorted(c), firstResult, maxResult) + return tx.read(withCriteria(mcb).pagination(firstResult, maxResult, SearchableFields.ID)) .map(this::entityToAdapter) - .collect(Collectors.toList()); + .collect(Collectors.toList()); } private ModelCriteriaBuilder filterEntryToModelCriteriaBuilder(Map.Entry entry) { @@ -297,12 +296,11 @@ public class MapPermissionTicketStore> implements Permis .findById(ticket.getResourceId(), ticket.getResourceServerId()); } - return paginatedStream(tx.read(mcb) - .filter(distinctByKey(MapPermissionTicketEntity::getResourceId)) - .sorted(MapPermissionTicketEntity.COMPARE_BY_RESOURCE_ID) - .map(ticketResourceMapper) - .filter(Objects::nonNull), first, max) - .collect(Collectors.toList()); + return paginatedStream(tx.read(withCriteria(mcb).orderBy(SearchableFields.RESOURCE_ID, ASCENDING)) + .filter(distinctByKey(MapPermissionTicketEntity::getResourceId)) + .map(ticketResourceMapper) + .filter(Objects::nonNull), first, max) + .collect(Collectors.toList()); } @Override @@ -310,11 +308,10 @@ public class MapPermissionTicketStore> implements Permis ModelCriteriaBuilder mcb = permissionTicketStore.createCriteriaBuilder() .compare(SearchableFields.OWNER, Operator.EQ, owner); - return paginatedStream(tx.read(mcb) - .filter(distinctByKey(MapPermissionTicketEntity::getResourceId)) - .sorted(MapPermissionTicketEntity.COMPARE_BY_RESOURCE_ID), first, max) - .map(ticket -> authorizationProvider.getStoreFactory().getResourceStore() - .findById(ticket.getResourceId(), ticket.getResourceServerId())) - .collect(Collectors.toList()); + return paginatedStream(tx.read(withCriteria(mcb).orderBy(SearchableFields.RESOURCE_ID, ASCENDING)) + .filter(distinctByKey(MapPermissionTicketEntity::getResourceId)), first, max) + .map(ticket -> authorizationProvider.getStoreFactory().getResourceStore() + .findById(ticket.getResourceId(), ticket.getResourceServerId())) + .collect(Collectors.toList()); } } diff --git a/model/map/src/main/java/org/keycloak/models/map/authorization/MapPolicyStore.java b/model/map/src/main/java/org/keycloak/models/map/authorization/MapPolicyStore.java index 4457a1eed0..8b987c6b61 100644 --- a/model/map/src/main/java/org/keycloak/models/map/authorization/MapPolicyStore.java +++ b/model/map/src/main/java/org/keycloak/models/map/authorization/MapPolicyStore.java @@ -42,7 +42,7 @@ import java.util.stream.Collectors; import static org.keycloak.common.util.StackUtil.getShortStackTrace; import static org.keycloak.models.map.common.MapStorageUtils.registerEntityForChanges; -import static org.keycloak.utils.StreamsUtil.paginatedStream; +import static org.keycloak.models.map.storage.QueryParameters.withCriteria; public class MapPolicyStore implements PolicyStore { @@ -86,7 +86,7 @@ public class MapPolicyStore implements PolicyStore { ModelCriteriaBuilder mcb = forResourceServer(resourceServer.getId()) .compare(SearchableFields.NAME, Operator.EQ, representation.getName()); - if (tx.getCount(mcb) > 0) { + if (tx.getCount(withCriteria(mcb)) > 0) { throw new ModelDuplicateException("Policy with name '" + representation.getName() + "' for " + resourceServer.getId() + " already exists"); } @@ -111,8 +111,8 @@ public class MapPolicyStore implements PolicyStore { public Policy findById(String id, String resourceServerId) { LOG.tracef("findById(%s, %s)%s", id, resourceServerId, getShortStackTrace()); - return tx.read(forResourceServer(resourceServerId) - .compare(SearchableFields.ID, Operator.EQ, id)) + return tx.read(withCriteria(forResourceServer(resourceServerId) + .compare(SearchableFields.ID, Operator.EQ, id))) .findFirst() .map(this::entityToAdapter) .orElse(null); @@ -122,8 +122,8 @@ public class MapPolicyStore implements PolicyStore { public Policy findByName(String name, String resourceServerId) { LOG.tracef("findByName(%s, %s)%s", name, resourceServerId, getShortStackTrace()); - return tx.read(forResourceServer(resourceServerId) - .compare(SearchableFields.NAME, Operator.EQ, name)) + return tx.read(withCriteria(forResourceServer(resourceServerId) + .compare(SearchableFields.NAME, Operator.EQ, name))) .findFirst() .map(this::entityToAdapter) .orElse(null); @@ -133,7 +133,7 @@ public class MapPolicyStore implements PolicyStore { public List findByResourceServer(String id) { LOG.tracef("findByResourceServer(%s)%s", id, getShortStackTrace()); - return tx.read(forResourceServer(id)) + return tx.read(withCriteria(forResourceServer(id))) .map(this::entityToAdapter) .collect(Collectors.toList()); } @@ -153,12 +153,12 @@ public class MapPolicyStore implements PolicyStore { mcb = mcb.compare(SearchableFields.OWNER, Operator.NOT_EXISTS); } - return paginatedStream(tx.read(mcb) - .sorted(MapPolicyEntity.COMPARE_BY_NAME), firstResult, maxResult) - .map(MapPolicyEntity::getId) - .map(K::toString) - .map(id -> authorizationProvider.getStoreFactory().getPolicyStore().findById(id, resourceServerId)) // We need to go through cache - .collect(Collectors.toList()); + return tx.read(withCriteria(mcb).pagination(firstResult, maxResult, SearchableFields.NAME)) + .map(MapPolicyEntity::getId) + .map(policyStore.getKeyConvertor()::keyToString) + // We need to go through cache + .map(id -> authorizationProvider.getStoreFactory().getPolicyStore().findById(id, resourceServerId)) + .collect(Collectors.toList()); } private ModelCriteriaBuilder filterEntryToModelCriteriaBuilder(Map.Entry entry) { @@ -205,24 +205,24 @@ public class MapPolicyStore implements PolicyStore { public void findByResource(String resourceId, String resourceServerId, Consumer consumer) { LOG.tracef("findByResource(%s, %s, %s)%s", resourceId, resourceServerId, consumer, getShortStackTrace()); - tx.read(forResourceServer(resourceServerId) - .compare(Policy.SearchableFields.RESOURCE_ID, Operator.EQ, resourceId)) + tx.read(withCriteria(forResourceServer(resourceServerId) + .compare(SearchableFields.RESOURCE_ID, Operator.EQ, resourceId))) .map(this::entityToAdapter) .forEach(consumer); } @Override public void findByResourceType(String type, String resourceServerId, Consumer policyConsumer) { - tx.read(forResourceServer(resourceServerId) - .compare(SearchableFields.CONFIG, Operator.LIKE, (Object[]) new String[] {"defaultResourceType", type})) + tx.read(withCriteria(forResourceServer(resourceServerId) + .compare(SearchableFields.CONFIG, Operator.LIKE, (Object[]) new String[]{"defaultResourceType", type}))) .map(this::entityToAdapter) .forEach(policyConsumer); } @Override public List findByScopeIds(List scopeIds, String resourceServerId) { - return tx.read(forResourceServer(resourceServerId) - .compare(SearchableFields.SCOPE_ID, Operator.IN, scopeIds)) + return tx.read(withCriteria(forResourceServer(resourceServerId) + .compare(SearchableFields.SCOPE_ID, Operator.IN, scopeIds))) .map(this::entityToAdapter) .collect(Collectors.toList()); } @@ -240,22 +240,22 @@ public class MapPolicyStore implements PolicyStore { mcb = mcb.compare(SearchableFields.RESOURCE_ID, Operator.NOT_EXISTS) .compare(SearchableFields.CONFIG, Operator.NOT_EXISTS, (Object[]) new String[] {"defaultResourceType"}); } - - tx.read(mcb).map(this::entityToAdapter).forEach(consumer); + + tx.read(withCriteria(mcb)).map(this::entityToAdapter).forEach(consumer); } @Override public List findByType(String type, String resourceServerId) { - return tx.read(forResourceServer(resourceServerId) - .compare(SearchableFields.TYPE, Operator.EQ, type)) + return tx.read(withCriteria(forResourceServer(resourceServerId) + .compare(SearchableFields.TYPE, Operator.EQ, type))) .map(this::entityToAdapter) .collect(Collectors.toList()); } @Override public List findDependentPolicies(String id, String resourceServerId) { - return tx.read(forResourceServer(resourceServerId) - .compare(SearchableFields.ASSOCIATED_POLICY_ID, Operator.EQ, id)) + return tx.read(withCriteria(forResourceServer(resourceServerId) + .compare(SearchableFields.ASSOCIATED_POLICY_ID, Operator.EQ, id))) .map(this::entityToAdapter) .collect(Collectors.toList()); } diff --git a/model/map/src/main/java/org/keycloak/models/map/authorization/MapResourceStore.java b/model/map/src/main/java/org/keycloak/models/map/authorization/MapResourceStore.java index ffdf567513..492309736b 100644 --- a/model/map/src/main/java/org/keycloak/models/map/authorization/MapResourceStore.java +++ b/model/map/src/main/java/org/keycloak/models/map/authorization/MapResourceStore.java @@ -33,7 +33,6 @@ import org.keycloak.models.map.storage.ModelCriteriaBuilder; import org.keycloak.models.map.storage.ModelCriteriaBuilder.Operator; import java.util.Arrays; -import java.util.Comparator; import java.util.LinkedList; import java.util.List; import java.util.Map; @@ -42,7 +41,7 @@ import java.util.stream.Collectors; import static org.keycloak.common.util.StackUtil.getShortStackTrace; import static org.keycloak.models.map.common.MapStorageUtils.registerEntityForChanges; -import static org.keycloak.utils.StreamsUtil.paginatedStream; +import static org.keycloak.models.map.storage.QueryParameters.withCriteria; public class MapResourceStore> implements ResourceStore { @@ -86,7 +85,7 @@ public class MapResourceStore> implements ResourceStore .compare(SearchableFields.NAME, Operator.EQ, name) .compare(SearchableFields.OWNER, Operator.EQ, owner); - if (tx.getCount(mcb) > 0) { + if (tx.getCount(withCriteria(mcb)) > 0) { throw new ModelDuplicateException("Resource with name '" + name + "' for " + resourceServer.getId() + " already exists for request owner " + owner); } @@ -113,8 +112,8 @@ public class MapResourceStore> implements ResourceStore public Resource findById(String id, String resourceServerId) { LOG.tracef("findById(%s, %s)%s", id, resourceServerId, getShortStackTrace()); - return tx.read(forResourceServer(resourceServerId) - .compare(SearchableFields.ID, Operator.EQ, id)) + return tx.read(withCriteria(forResourceServer(resourceServerId) + .compare(SearchableFields.ID, Operator.EQ, id))) .findFirst() .map(this::entityToAdapter) .orElse(null); @@ -127,12 +126,11 @@ public class MapResourceStore> implements ResourceStore private void findByOwnerFilter(String ownerId, String resourceServerId, Consumer consumer, int firstResult, int maxResult) { LOG.tracef("findByOwnerFilter(%s, %s, %s, %d, %d)%s", ownerId, resourceServerId, consumer, firstResult, maxResult, getShortStackTrace()); - Comparator> c = Comparator.comparing(MapResourceEntity::getId); - paginatedStream(tx.read(forResourceServer(resourceServerId) - .compare(SearchableFields.OWNER, Operator.EQ, ownerId)) - .sorted(c), firstResult, maxResult) - .map(this::entityToAdapter) - .forEach(consumer); + + tx.read(withCriteria(forResourceServer(resourceServerId).compare(SearchableFields.OWNER, Operator.EQ, ownerId)) + .pagination(firstResult, maxResult, SearchableFields.ID) + ).map(this::entityToAdapter) + .forEach(consumer); } @Override @@ -147,9 +145,9 @@ public class MapResourceStore> implements ResourceStore @Override public List findByUri(String uri, String resourceServerId) { LOG.tracef("findByUri(%s, %s)%s", uri, resourceServerId, getShortStackTrace()); - - return tx.read(forResourceServer(resourceServerId) - .compare(SearchableFields.URI, Operator.EQ, uri)) + + return tx.read(withCriteria(forResourceServer(resourceServerId) + .compare(SearchableFields.URI, Operator.EQ, uri))) .map(this::entityToAdapter) .collect(Collectors.toList()); } @@ -158,7 +156,7 @@ public class MapResourceStore> implements ResourceStore public List findByResourceServer(String resourceServerId) { LOG.tracef("findByResourceServer(%s)%s", resourceServerId, getShortStackTrace()); - return tx.read(forResourceServer(resourceServerId)) + return tx.read(withCriteria(forResourceServer(resourceServerId))) .map(this::entityToAdapter) .collect(Collectors.toList()); } @@ -172,8 +170,7 @@ public class MapResourceStore> implements ResourceStore .toArray(ModelCriteriaBuilder[]::new) ); - return paginatedStream(tx.read(mcb) - .sorted(MapResourceEntity.COMPARE_BY_NAME), firstResult, maxResult) + return tx.read(withCriteria(mcb).pagination(firstResult, maxResult, SearchableFields.NAME)) .map(this::entityToAdapter) .collect(Collectors.toList()); } @@ -210,8 +207,8 @@ public class MapResourceStore> implements ResourceStore public void findByScope(List scopes, String resourceServerId, Consumer consumer) { LOG.tracef("findByScope(%s, %s, %s)%s", scopes, resourceServerId, consumer, getShortStackTrace()); - tx.read(forResourceServer(resourceServerId) - .compare(SearchableFields.SCOPE_ID, Operator.IN, scopes)) + tx.read(withCriteria(forResourceServer(resourceServerId) + .compare(SearchableFields.SCOPE_ID, Operator.IN, scopes))) .map(this::entityToAdapter) .forEach(consumer); } @@ -224,9 +221,9 @@ public class MapResourceStore> implements ResourceStore @Override public Resource findByName(String name, String ownerId, String resourceServerId) { LOG.tracef("findByName(%s, %s, %s)%s", name, ownerId, resourceServerId, getShortStackTrace()); - return tx.read(forResourceServer(resourceServerId) - .compare(SearchableFields.OWNER, Operator.EQ, ownerId) - .compare(SearchableFields.NAME, Operator.EQ, name)) + return tx.read(withCriteria(forResourceServer(resourceServerId) + .compare(SearchableFields.OWNER, Operator.EQ, ownerId) + .compare(SearchableFields.NAME, Operator.EQ, name))) .findFirst() .map(this::entityToAdapter) .orElse(null); @@ -235,8 +232,8 @@ public class MapResourceStore> implements ResourceStore @Override public void findByType(String type, String resourceServerId, Consumer consumer) { LOG.tracef("findByType(%s, %s, %s)%s", type, resourceServerId, consumer, getShortStackTrace()); - tx.read(forResourceServer(resourceServerId) - .compare(SearchableFields.TYPE, Operator.EQ, type)) + tx.read(withCriteria(forResourceServer(resourceServerId) + .compare(SearchableFields.TYPE, Operator.EQ, type))) .map(this::entityToAdapter) .forEach(consumer); } @@ -252,7 +249,7 @@ public class MapResourceStore> implements ResourceStore mcb = mcb.compare(SearchableFields.OWNER, Operator.EQ, owner); } - tx.read(mcb) + tx.read(withCriteria(mcb)) .map(this::entityToAdapter) .forEach(consumer); } @@ -260,9 +257,9 @@ public class MapResourceStore> implements ResourceStore @Override public void findByTypeInstance(String type, String resourceServerId, Consumer consumer) { LOG.tracef("findByTypeInstance(%s, %s, %s)%s", type, resourceServerId, consumer, getShortStackTrace()); - tx.read(forResourceServer(resourceServerId) + tx.read(withCriteria(forResourceServer(resourceServerId) .compare(SearchableFields.OWNER, Operator.NE, resourceServerId) - .compare(SearchableFields.TYPE, Operator.EQ, type)) + .compare(SearchableFields.TYPE, Operator.EQ, type))) .map(this::entityToAdapter) .forEach(consumer); } diff --git a/model/map/src/main/java/org/keycloak/models/map/authorization/MapScopeStore.java b/model/map/src/main/java/org/keycloak/models/map/authorization/MapScopeStore.java index 76edf3660e..36e4b33b17 100644 --- a/model/map/src/main/java/org/keycloak/models/map/authorization/MapScopeStore.java +++ b/model/map/src/main/java/org/keycloak/models/map/authorization/MapScopeStore.java @@ -39,7 +39,7 @@ import java.util.stream.Collectors; import static org.keycloak.common.util.StackUtil.getShortStackTrace; import static org.keycloak.models.map.common.MapStorageUtils.registerEntityForChanges; -import static org.keycloak.utils.StreamsUtil.paginatedStream; +import static org.keycloak.models.map.storage.QueryParameters.withCriteria; public class MapScopeStore implements ScopeStore { @@ -84,7 +84,7 @@ public class MapScopeStore implements ScopeStore { ModelCriteriaBuilder mcb = forResourceServer(resourceServer.getId()) .compare(SearchableFields.NAME, Operator.EQ, name); - if (tx.getCount(mcb) > 0) { + if (tx.getCount(withCriteria(mcb)) > 0) { throw new ModelDuplicateException("Scope with name '" + name + "' for " + resourceServer.getId() + " already exists"); } @@ -109,8 +109,8 @@ public class MapScopeStore implements ScopeStore { public Scope findById(String id, String resourceServerId) { LOG.tracef("findById(%s, %s)%s", id, resourceServerId, getShortStackTrace()); - return tx.read(forResourceServer(resourceServerId) - .compare(Scope.SearchableFields.ID, Operator.EQ, id)) + return tx.read(withCriteria(forResourceServer(resourceServerId) + .compare(SearchableFields.ID, Operator.EQ, id))) .findFirst() .map(this::entityToAdapter) .orElse(null); @@ -120,8 +120,8 @@ public class MapScopeStore implements ScopeStore { public Scope findByName(String name, String resourceServerId) { LOG.tracef("findByName(%s, %s)%s", name, resourceServerId, getShortStackTrace()); - return tx.read(forResourceServer(resourceServerId).compare(Scope.SearchableFields.NAME, - Operator.EQ, name)) + return tx.read(withCriteria(forResourceServer(resourceServerId).compare(SearchableFields.NAME, + Operator.EQ, name))) .findFirst() .map(this::entityToAdapter) .orElse(null); @@ -131,7 +131,7 @@ public class MapScopeStore implements ScopeStore { public List findByResourceServer(String id) { LOG.tracef("findByResourceServer(%s)%s", id, getShortStackTrace()); - return tx.read(forResourceServer(id)) + return tx.read(withCriteria(forResourceServer(id))) .map(this::entityToAdapter) .collect(Collectors.toList()); } @@ -155,7 +155,8 @@ public class MapScopeStore implements ScopeStore { } } - return paginatedStream(tx.read(mcb).map(this::entityToAdapter), firstResult, maxResult) - .collect(Collectors.toList()); + return tx.read(withCriteria(mcb).pagination(firstResult, maxResult, SearchableFields.NAME)) + .map(this::entityToAdapter) + .collect(Collectors.toList()); } } diff --git a/model/map/src/main/java/org/keycloak/models/map/authorization/entity/MapPermissionTicketEntity.java b/model/map/src/main/java/org/keycloak/models/map/authorization/entity/MapPermissionTicketEntity.java index f6e383b07e..351b5cfa83 100644 --- a/model/map/src/main/java/org/keycloak/models/map/authorization/entity/MapPermissionTicketEntity.java +++ b/model/map/src/main/java/org/keycloak/models/map/authorization/entity/MapPermissionTicketEntity.java @@ -24,8 +24,6 @@ import java.util.Objects; public class MapPermissionTicketEntity implements AbstractEntity { - public static final Comparator> COMPARE_BY_RESOURCE_ID = Comparator.comparing(MapPermissionTicketEntity::getResourceId); - private final K id; private String owner; private String requester; diff --git a/model/map/src/main/java/org/keycloak/models/map/authorization/entity/MapPolicyEntity.java b/model/map/src/main/java/org/keycloak/models/map/authorization/entity/MapPolicyEntity.java index 73a9c21a4b..35edf85372 100644 --- a/model/map/src/main/java/org/keycloak/models/map/authorization/entity/MapPolicyEntity.java +++ b/model/map/src/main/java/org/keycloak/models/map/authorization/entity/MapPolicyEntity.java @@ -30,8 +30,6 @@ import java.util.Objects; public class MapPolicyEntity implements AbstractEntity { - public static final Comparator> COMPARE_BY_NAME = Comparator.comparing(MapPolicyEntity::getName); - private final K id; private String name; private String description; diff --git a/model/map/src/main/java/org/keycloak/models/map/authorization/entity/MapResourceEntity.java b/model/map/src/main/java/org/keycloak/models/map/authorization/entity/MapResourceEntity.java index 41e2c32922..c1875365b7 100644 --- a/model/map/src/main/java/org/keycloak/models/map/authorization/entity/MapResourceEntity.java +++ b/model/map/src/main/java/org/keycloak/models/map/authorization/entity/MapResourceEntity.java @@ -29,8 +29,6 @@ import java.util.Set; public class MapResourceEntity implements AbstractEntity { - public static final Comparator> COMPARE_BY_NAME = Comparator.comparing(MapResourceEntity::getName); - private final K id; private String name; private String displayName; diff --git a/model/map/src/main/java/org/keycloak/models/map/client/MapClientProvider.java b/model/map/src/main/java/org/keycloak/models/map/client/MapClientProvider.java index 4a60179e01..7a24c240c5 100644 --- a/model/map/src/main/java/org/keycloak/models/map/client/MapClientProvider.java +++ b/model/map/src/main/java/org/keycloak/models/map/client/MapClientProvider.java @@ -28,7 +28,7 @@ import org.keycloak.models.RealmModel; import org.keycloak.models.RoleModel; import org.keycloak.models.map.storage.MapKeycloakTransaction; -import java.util.Comparator; + import java.util.Map; import java.util.Objects; import java.util.Set; @@ -45,9 +45,11 @@ import org.keycloak.models.map.storage.ModelCriteriaBuilder.Operator; import static org.keycloak.common.util.StackUtil.getShortStackTrace; import org.keycloak.models.ClientScopeModel; import static org.keycloak.models.map.common.MapStorageUtils.registerEntityForChanges; +import static org.keycloak.models.map.storage.QueryParameters.Order.ASCENDING; +import static org.keycloak.models.map.storage.QueryParameters.withCriteria; + import org.keycloak.protocol.oidc.OIDCLoginProtocol; import java.util.HashSet; -import static org.keycloak.utils.StreamsUtil.paginatedStream; public class MapClientProvider implements ClientProvider { @@ -57,8 +59,6 @@ public class MapClientProvider implements ClientProvider { private final MapStorage, ClientModel> clientStore; private final ConcurrentMap> clientRegisteredNodesStore; - private static final Comparator COMPARE_BY_CLIENT_ID = Comparator.comparing(MapClientEntity::getClientId); - public MapClientProvider(KeycloakSession session, MapStorage, ClientModel> clientStore, ConcurrentMap> clientRegisteredNodesStore) { this.session = session; this.clientStore = clientStore; @@ -126,7 +126,11 @@ public class MapClientProvider implements ClientProvider { @Override public Stream getClientsStream(RealmModel realm, Integer firstResult, Integer maxResults) { - return paginatedStream(getClientsStream(realm), firstResult, maxResults); + ModelCriteriaBuilder mcb = clientStore.createCriteriaBuilder() + .compare(SearchableFields.REALM_ID, Operator.EQ, realm.getId()); + + return tx.read(withCriteria(mcb).pagination(firstResult, maxResults, SearchableFields.CLIENT_ID)) + .map(entityToAdapterFunc(realm)); } @Override @@ -134,10 +138,8 @@ public class MapClientProvider implements ClientProvider { ModelCriteriaBuilder mcb = clientStore.createCriteriaBuilder() .compare(SearchableFields.REALM_ID, Operator.EQ, realm.getId()); - return tx.read(mcb) - .sorted(COMPARE_BY_CLIENT_ID) - .map(entityToAdapterFunc(realm)) - ; + return tx.read(withCriteria(mcb).orderBy(SearchableFields.CLIENT_ID, ASCENDING)) + .map(entityToAdapterFunc(realm)); } @Override @@ -220,7 +222,7 @@ public class MapClientProvider implements ClientProvider { ModelCriteriaBuilder mcb = clientStore.createCriteriaBuilder() .compare(SearchableFields.REALM_ID, Operator.EQ, realm.getId()); - return this.clientStore.getCount(mcb); + return this.clientStore.getCount(withCriteria(mcb)); } @Override @@ -248,7 +250,7 @@ public class MapClientProvider implements ClientProvider { .compare(SearchableFields.REALM_ID, Operator.EQ, realm.getId()) .compare(SearchableFields.CLIENT_ID, Operator.ILIKE, clientId); - return tx.read(mcb) + return tx.read(withCriteria(mcb)) .map(entityToAdapterFunc(realm)) .findFirst() .orElse(null) @@ -265,10 +267,8 @@ public class MapClientProvider implements ClientProvider { .compare(SearchableFields.REALM_ID, Operator.EQ, realm.getId()) .compare(SearchableFields.CLIENT_ID, Operator.ILIKE, "%" + clientId + "%"); - Stream> s = tx.read(mcb) - .sorted(COMPARE_BY_CLIENT_ID); - - return paginatedStream(s, firstResult, maxResults).map(entityToAdapterFunc(realm)); + return tx.read(withCriteria(mcb).pagination(firstResult, maxResults, SearchableFields.CLIENT_ID)) + .map(entityToAdapterFunc(realm)); } @Override @@ -280,10 +280,8 @@ public class MapClientProvider implements ClientProvider { mcb = mcb.compare(SearchableFields.ATTRIBUTE, Operator.EQ, entry.getKey(), entry.getValue()); } - Stream> s = tx.read(mcb) - .sorted(COMPARE_BY_CLIENT_ID); - - return paginatedStream(s, firstResult, maxResults).map(entityToAdapterFunc(realm)); + return tx.read(withCriteria(mcb).pagination(firstResult, maxResults, SearchableFields.CLIENT_ID)) + .map(entityToAdapterFunc(realm)); } @Override @@ -344,7 +342,7 @@ public class MapClientProvider implements ClientProvider { .compare(SearchableFields.REALM_ID, Operator.EQ, realm.getId()) .compare(SearchableFields.ENABLED, Operator.EQ, Boolean.TRUE); - try (Stream> st = tx.read(mcb)) { + try (Stream> st = tx.read(withCriteria(mcb))) { return st .filter(mce -> mce.getRedirectUris() != null && ! mce.getRedirectUris().isEmpty()) .collect(Collectors.toMap( @@ -358,7 +356,7 @@ public class MapClientProvider implements ClientProvider { ModelCriteriaBuilder mcb = clientStore.createCriteriaBuilder() .compare(SearchableFields.REALM_ID, Operator.EQ, realm.getId()) .compare(SearchableFields.SCOPE_MAPPING_ROLE, Operator.EQ, role.getId()); - try (Stream> toRemove = tx.read(mcb)) { + try (Stream> toRemove = tx.read(withCriteria(mcb))) { toRemove .map(clientEntity -> session.clients().getClientById(realm, clientEntity.getId().toString())) .filter(Objects::nonNull) diff --git a/model/map/src/main/java/org/keycloak/models/map/clientscope/MapClientScopeProvider.java b/model/map/src/main/java/org/keycloak/models/map/clientscope/MapClientScopeProvider.java index b3ed1db6ed..b31963fa17 100644 --- a/model/map/src/main/java/org/keycloak/models/map/clientscope/MapClientScopeProvider.java +++ b/model/map/src/main/java/org/keycloak/models/map/clientscope/MapClientScopeProvider.java @@ -17,7 +17,6 @@ package org.keycloak.models.map.clientscope; -import java.util.Comparator; import java.util.Objects; import java.util.function.Function; import java.util.function.Predicate; @@ -38,6 +37,8 @@ import org.keycloak.models.map.storage.ModelCriteriaBuilder.Operator; import org.keycloak.models.utils.KeycloakModelUtils; import static org.keycloak.models.map.common.MapStorageUtils.registerEntityForChanges; +import static org.keycloak.models.map.storage.QueryParameters.Order.ASCENDING; +import static org.keycloak.models.map.storage.QueryParameters.withCriteria; public class MapClientScopeProvider implements ClientScopeProvider { @@ -46,8 +47,6 @@ public class MapClientScopeProvider implements ClientScopeProvider { private final MapKeycloakTransaction, ClientScopeModel> tx; private final MapStorage, ClientScopeModel> clientScopeStore; - private static final Comparator COMPARE_BY_NAME = Comparator.comparing(MapClientScopeEntity::getName); - public MapClientScopeProvider(KeycloakSession session, MapStorage, ClientScopeModel> clientScopeStore) { this.session = session; this.clientScopeStore = clientScopeStore; @@ -79,8 +78,7 @@ public class MapClientScopeProvider implements ClientScopeProvider { ModelCriteriaBuilder mcb = clientScopeStore.createCriteriaBuilder() .compare(SearchableFields.REALM_ID, Operator.EQ, realm.getId()); - return tx.read(mcb) - .sorted(COMPARE_BY_NAME) + return tx.read(withCriteria(mcb).orderBy(SearchableFields.NAME, ASCENDING)) .map(entityToAdapterFunc(realm)); } @@ -91,7 +89,7 @@ public class MapClientScopeProvider implements ClientScopeProvider { .compare(SearchableFields.REALM_ID, Operator.EQ, realm.getId()) .compare(SearchableFields.NAME, Operator.EQ, name); - if (tx.getCount(mcb) > 0) { + if (tx.getCount(withCriteria(mcb)) > 0) { throw new ModelDuplicateException("Client scope with name '" + name + "' in realm " + realm.getName()); } diff --git a/model/map/src/main/java/org/keycloak/models/map/group/MapGroupProvider.java b/model/map/src/main/java/org/keycloak/models/map/group/MapGroupProvider.java index 55e0a9955b..94c7ae06db 100644 --- a/model/map/src/main/java/org/keycloak/models/map/group/MapGroupProvider.java +++ b/model/map/src/main/java/org/keycloak/models/map/group/MapGroupProvider.java @@ -30,15 +30,18 @@ import org.keycloak.models.map.storage.MapStorage; import org.keycloak.models.map.storage.ModelCriteriaBuilder; import org.keycloak.models.map.storage.ModelCriteriaBuilder.Operator; -import java.util.Comparator; +import org.keycloak.models.map.storage.QueryParameters; + import java.util.Objects; import java.util.function.Function; import java.util.function.UnaryOperator; +import java.util.stream.Collectors; import java.util.stream.Stream; import static org.keycloak.common.util.StackUtil.getShortStackTrace; import static org.keycloak.models.map.common.MapStorageUtils.registerEntityForChanges; -import static org.keycloak.utils.StreamsUtil.paginatedStream; +import static org.keycloak.models.map.storage.QueryParameters.Order.ASCENDING; +import static org.keycloak.models.map.storage.QueryParameters.withCriteria; public class MapGroupProvider implements GroupProvider { @@ -88,10 +91,10 @@ public class MapGroupProvider implements GroupProvider { @Override public Stream getGroupsStream(RealmModel realm) { - return getGroupsStreamInternal(realm, null); + return getGroupsStreamInternal(realm, null, null); } - private Stream getGroupsStreamInternal(RealmModel realm, UnaryOperator> modifier) { + private Stream getGroupsStreamInternal(RealmModel realm, UnaryOperator> modifier, UnaryOperator> queryParametersModifier) { LOG.tracef("getGroupsStream(%s)%s", realm, getShortStackTrace()); ModelCriteriaBuilder mcb = groupStore.createCriteriaBuilder() .compare(SearchableFields.REALM_ID, Operator.EQ, realm.getId()); @@ -100,9 +103,13 @@ public class MapGroupProvider implements GroupProvider { mcb = modifier.apply(mcb); } - return tx.read(mcb) + QueryParameters queryParameters = withCriteria(mcb).orderBy(SearchableFields.NAME, ASCENDING); + if (queryParametersModifier != null) { + queryParameters = queryParametersModifier.apply(queryParameters); + } + + return tx.read(queryParameters) .map(entityToAdapterFunc(realm)) - .sorted(GroupModel.COMPARE_BY_NAME) ; } @@ -116,11 +123,8 @@ public class MapGroupProvider implements GroupProvider { mcb = mcb.compare(SearchableFields.NAME, Operator.ILIKE, "%" + search + "%"); } - Stream groupModelStream = tx.read(mcb) - .map(entityToAdapterFunc(realm)) - .sorted(Comparator.comparing(GroupModel::getName)); - - return paginatedStream(groupModelStream, first, max); + return tx.read(withCriteria(mcb).pagination(first, max, SearchableFields.NAME)) + .map(entityToAdapterFunc(realm)); } @Override @@ -133,7 +137,7 @@ public class MapGroupProvider implements GroupProvider { mcb = mcb.compare(SearchableFields.PARENT_ID, Operator.EQ, (Object) null); } - return tx.getCount(mcb); + return tx.getCount(withCriteria(mcb)); } @Override @@ -142,51 +146,56 @@ public class MapGroupProvider implements GroupProvider { .compare(SearchableFields.REALM_ID, Operator.EQ, realm.getId()) .compare(SearchableFields.NAME, Operator.ILIKE, "%" + search + "%"); - return tx.getCount(mcb); + return tx.getCount(withCriteria(mcb)); } @Override public Stream getGroupsByRoleStream(RealmModel realm, RoleModel role, Integer firstResult, Integer maxResults) { LOG.tracef("getGroupsByRole(%s, %s, %d, %d)%s", realm, role, firstResult, maxResults, getShortStackTrace()); - Stream groupModelStream = getGroupsStreamInternal(realm, - (ModelCriteriaBuilder mcb) -> mcb.compare(SearchableFields.ASSIGNED_ROLE, Operator.EQ, role.getId()) + return getGroupsStreamInternal(realm, + (ModelCriteriaBuilder mcb) -> mcb.compare(SearchableFields.ASSIGNED_ROLE, Operator.EQ, role.getId()), + qp -> qp.offset(firstResult).limit(maxResults) ); - - return paginatedStream(groupModelStream, firstResult, maxResults); } @Override public Stream getTopLevelGroupsStream(RealmModel realm) { LOG.tracef("getTopLevelGroupsStream(%s)%s", realm, getShortStackTrace()); return getGroupsStreamInternal(realm, - (ModelCriteriaBuilder mcb) -> mcb.compare(SearchableFields.PARENT_ID, Operator.EQ, (Object) null) + (ModelCriteriaBuilder mcb) -> mcb.compare(SearchableFields.PARENT_ID, Operator.NOT_EXISTS), + null ); } @Override public Stream getTopLevelGroupsStream(RealmModel realm, Integer firstResult, Integer maxResults) { - Stream groupModelStream = getTopLevelGroupsStream(realm); - - return paginatedStream(groupModelStream, firstResult, maxResults); - + LOG.tracef("getTopLevelGroupsStream(%s, %s, %s)%s", realm, firstResult, maxResults, getShortStackTrace()); + return getGroupsStreamInternal(realm, + (ModelCriteriaBuilder mcb) -> mcb.compare(SearchableFields.PARENT_ID, Operator.NOT_EXISTS), + qp -> qp.offset(firstResult).limit(maxResults) + ); } @Override public Stream searchForGroupByNameStream(RealmModel realm, String search, Integer firstResult, Integer maxResults) { LOG.tracef("searchForGroupByNameStream(%s, %s, %d, %d)%s", realm, search, firstResult, maxResults, getShortStackTrace()); - Stream groupModelStream = getGroupsStreamInternal(realm, - (ModelCriteriaBuilder mcb) -> mcb.compare(SearchableFields.NAME, Operator.ILIKE, "%" + search + "%") - ); - final Stream groups = paginatedStream(groupModelStream.map(GroupModel::getId), firstResult, maxResults); - return groups.map(id -> { - GroupModel groupById = session.groups().getGroupById(realm,id); - while (Objects.nonNull(groupById.getParentId())) { - groupById = session.groups().getGroupById(realm, groupById.getParentId()); - } - return groupById; - }).sorted(GroupModel.COMPARE_BY_NAME).distinct(); + ModelCriteriaBuilder mcb = groupStore.createCriteriaBuilder() + .compare(SearchableFields.REALM_ID, Operator.EQ, realm.getId()) + .compare(SearchableFields.NAME, Operator.ILIKE, "%" + search + "%"); + + + return tx.read(withCriteria(mcb).pagination(firstResult, maxResults, SearchableFields.NAME)) + .map(MapGroupEntity::getId) + .map(groupStore.getKeyConvertor()::keyToString) + .map(id -> { + GroupModel groupById = session.groups().getGroupById(realm, id); + while (Objects.nonNull(groupById.getParentId())) { + groupById = session.groups().getGroupById(realm, groupById.getParentId()); + } + return groupById; + }).sorted(GroupModel.COMPARE_BY_NAME).distinct(); } @Override @@ -201,7 +210,7 @@ public class MapGroupProvider implements GroupProvider { .compare(SearchableFields.PARENT_ID, Operator.EQ, parentId) .compare(SearchableFields.NAME, Operator.EQ, name); - if (tx.getCount(mcb) > 0) { + if (tx.getCount(withCriteria(mcb)) > 0) { throw new ModelDuplicateException("Group with name '" + name + "' in realm " + realm.getName() + " already exists for requested parent" ); } @@ -243,7 +252,7 @@ public class MapGroupProvider implements GroupProvider { session.users().preRemove(realm, group); realm.removeDefaultGroup(group); - group.getSubGroupsStream().forEach(subGroup -> session.groups().removeGroup(realm, subGroup)); + group.getSubGroupsStream().collect(Collectors.toSet()).forEach(subGroup -> session.groups().removeGroup(realm, subGroup)); // TODO: ^^^^^^^ Up to here @@ -268,7 +277,7 @@ public class MapGroupProvider implements GroupProvider { .compare(SearchableFields.PARENT_ID, Operator.EQ, parentId) .compare(SearchableFields.NAME, Operator.EQ, group.getName()); - try (Stream> possibleSiblings = tx.read(mcb)) { + try (Stream> possibleSiblings = tx.read(withCriteria(mcb))) { if (possibleSiblings.findAny().isPresent()) { throw new ModelDuplicateException("Parent already contains subgroup named '" + group.getName() + "'"); } @@ -290,7 +299,7 @@ public class MapGroupProvider implements GroupProvider { .compare(SearchableFields.PARENT_ID, Operator.EQ, (Object) null) .compare(SearchableFields.NAME, Operator.EQ, subGroup.getName()); - try (Stream> possibleSiblings = tx.read(mcb)) { + try (Stream> possibleSiblings = tx.read(withCriteria(mcb))) { if (possibleSiblings.findAny().isPresent()) { throw new ModelDuplicateException("There is already a top level group named '" + subGroup.getName() + "'"); } @@ -304,7 +313,7 @@ public class MapGroupProvider implements GroupProvider { ModelCriteriaBuilder mcb = groupStore.createCriteriaBuilder() .compare(SearchableFields.REALM_ID, Operator.EQ, realm.getId()) .compare(SearchableFields.ASSIGNED_ROLE, Operator.EQ, role.getId()); - try (Stream> toRemove = tx.read(mcb)) { + try (Stream> toRemove = tx.read(withCriteria(mcb))) { toRemove .map(groupEntity -> session.groups().getGroupById(realm, groupEntity.getId().toString())) .forEach(groupModel -> groupModel.deleteRoleMapping(role)); diff --git a/model/map/src/main/java/org/keycloak/models/map/loginFailure/MapUserLoginFailureProvider.java b/model/map/src/main/java/org/keycloak/models/map/loginFailure/MapUserLoginFailureProvider.java index 34516a6161..4a31a4a3ad 100644 --- a/model/map/src/main/java/org/keycloak/models/map/loginFailure/MapUserLoginFailureProvider.java +++ b/model/map/src/main/java/org/keycloak/models/map/loginFailure/MapUserLoginFailureProvider.java @@ -29,6 +29,7 @@ import java.util.function.Function; import static org.keycloak.common.util.StackUtil.getShortStackTrace; import static org.keycloak.models.map.common.MapStorageUtils.registerEntityForChanges; +import static org.keycloak.models.map.storage.QueryParameters.withCriteria; /** * @author Martin Kanis @@ -66,7 +67,7 @@ public class MapUserLoginFailureProvider implements UserLoginFailureProvider LOG.tracef("getUserLoginFailure(%s, %s)%s", realm, userId, getShortStackTrace()); - return userLoginFailureTx.read(mcb) + return userLoginFailureTx.read(withCriteria(mcb)) .findFirst() .map(userLoginFailureEntityToAdapterFunc(realm)) .orElse(null); @@ -80,7 +81,7 @@ public class MapUserLoginFailureProvider implements UserLoginFailureProvider LOG.tracef("addUserLoginFailure(%s, %s)%s", realm, userId, getShortStackTrace()); - MapUserLoginFailureEntity userLoginFailureEntity = userLoginFailureTx.read(mcb).findFirst().orElse(null); + MapUserLoginFailureEntity userLoginFailureEntity = userLoginFailureTx.read(withCriteria(mcb)).findFirst().orElse(null); if (userLoginFailureEntity == null) { userLoginFailureEntity = new MapUserLoginFailureEntity<>(userLoginFailureStore.getKeyConvertor().yieldNewUniqueKey(), realm.getId(), userId); @@ -99,7 +100,7 @@ public class MapUserLoginFailureProvider implements UserLoginFailureProvider LOG.tracef("removeUserLoginFailure(%s, %s)%s", realm, userId, getShortStackTrace()); - userLoginFailureTx.delete(userLoginFailureStore.getKeyConvertor().yieldNewUniqueKey(), mcb); + userLoginFailureTx.delete(userLoginFailureStore.getKeyConvertor().yieldNewUniqueKey(), withCriteria(mcb)); } @Override @@ -109,7 +110,7 @@ public class MapUserLoginFailureProvider implements UserLoginFailureProvider LOG.tracef("removeAllUserLoginFailures(%s)%s", realm, getShortStackTrace()); - userLoginFailureTx.delete(userLoginFailureStore.getKeyConvertor().yieldNewUniqueKey(), mcb); + userLoginFailureTx.delete(userLoginFailureStore.getKeyConvertor().yieldNewUniqueKey(), withCriteria(mcb)); } @Override diff --git a/model/map/src/main/java/org/keycloak/models/map/realm/MapRealmProvider.java b/model/map/src/main/java/org/keycloak/models/map/realm/MapRealmProvider.java index 5245fc8b13..775c8220e6 100644 --- a/model/map/src/main/java/org/keycloak/models/map/realm/MapRealmProvider.java +++ b/model/map/src/main/java/org/keycloak/models/map/realm/MapRealmProvider.java @@ -38,6 +38,8 @@ import org.keycloak.models.map.storage.ModelCriteriaBuilder; import org.keycloak.models.map.storage.ModelCriteriaBuilder.Operator; import org.keycloak.models.utils.KeycloakModelUtils; import static org.keycloak.models.map.common.MapStorageUtils.registerEntityForChanges; +import static org.keycloak.models.map.storage.QueryParameters.Order.ASCENDING; +import static org.keycloak.models.map.storage.QueryParameters.withCriteria; public class MapRealmProvider implements RealmProvider { @@ -110,7 +112,7 @@ public class MapRealmProvider implements RealmProvider { ModelCriteriaBuilder mcb = realmStore.createCriteriaBuilder() .compare(SearchableFields.NAME, Operator.EQ, name); - K realmId = tx.read(mcb) + K realmId = tx.read(withCriteria(mcb)) .findFirst() .map(MapRealmEntity::getId) .orElse(null); @@ -132,9 +134,8 @@ public class MapRealmProvider implements RealmProvider { } private Stream getRealmsStream(ModelCriteriaBuilder mcb) { - return tx.read(mcb) - .map(this::entityToAdapter) - .sorted(RealmModel.COMPARE_BY_NAME); + return tx.read(withCriteria(mcb).orderBy(SearchableFields.NAME, ASCENDING)) + .map(this::entityToAdapter); } @Override @@ -174,7 +175,7 @@ public class MapRealmProvider implements RealmProvider { ModelCriteriaBuilder mcb = realmStore.createCriteriaBuilder() .compare(SearchableFields.CLIENT_INITIAL_ACCESS, Operator.EXISTS); - tx.read(mcb) + tx.read(withCriteria(mcb)) .map(e -> registerEntityForChanges(tx, e)) .forEach(MapRealmEntity::removeExpiredClientInitialAccesses); } diff --git a/model/map/src/main/java/org/keycloak/models/map/role/MapRoleProvider.java b/model/map/src/main/java/org/keycloak/models/map/role/MapRoleProvider.java index e6aab0bcc7..c2b1bdc997 100644 --- a/model/map/src/main/java/org/keycloak/models/map/role/MapRoleProvider.java +++ b/model/map/src/main/java/org/keycloak/models/map/role/MapRoleProvider.java @@ -25,14 +25,15 @@ import org.keycloak.models.RealmModel; import org.keycloak.models.RoleModel; import org.keycloak.models.map.storage.MapKeycloakTransaction; -import java.util.Comparator; + import java.util.Objects; import java.util.function.Function; import java.util.stream.Stream; import org.keycloak.models.map.storage.MapStorage; import static org.keycloak.common.util.StackUtil.getShortStackTrace; import static org.keycloak.models.map.common.MapStorageUtils.registerEntityForChanges; -import static org.keycloak.utils.StreamsUtil.paginatedStream; +import static org.keycloak.models.map.storage.QueryParameters.Order.ASCENDING; +import static org.keycloak.models.map.storage.QueryParameters.withCriteria; import org.keycloak.models.RoleContainerModel; import org.keycloak.models.RoleModel.SearchableFields; @@ -47,19 +48,6 @@ public class MapRoleProvider implements RoleProvider { final MapKeycloakTransaction, RoleModel> tx; private final MapStorage, RoleModel> roleStore; - private static final Comparator> COMPARE_BY_NAME = new Comparator>() { - @Override - public int compare(MapRoleEntity o1, MapRoleEntity o2) { - String r1 = o1 == null ? null : o1.getName(); - String r2 = o2 == null ? null : o2.getName(); - return r1 == r2 ? 0 - : r1 == null ? -1 - : r2 == null ? 1 - : r1.compareTo(r2); - - } - }; - public MapRoleProvider(KeycloakSession session, MapStorage, RoleModel> roleStore) { this.session = session; this.roleStore = roleStore; @@ -99,7 +87,12 @@ public class MapRoleProvider implements RoleProvider { @Override public Stream getRealmRolesStream(RealmModel realm, Integer first, Integer max) { - return paginatedStream(getRealmRolesStream(realm), first, max); + ModelCriteriaBuilder mcb = roleStore.createCriteriaBuilder() + .compare(SearchableFields.REALM_ID, Operator.EQ, realm.getId()) + .compare(SearchableFields.IS_CLIENT_ROLE, Operator.NE, true); + + return tx.read(withCriteria(mcb).pagination(first, max, SearchableFields.NAME)) + .map(entityToAdapterFunc(realm)); } @Override @@ -108,8 +101,7 @@ public class MapRoleProvider implements RoleProvider { .compare(SearchableFields.REALM_ID, Operator.EQ, realm.getId()) .compare(SearchableFields.IS_CLIENT_ROLE, Operator.NE, true); - return tx.read(mcb) - .sorted(COMPARE_BY_NAME) + return tx.read(withCriteria(mcb).orderBy(SearchableFields.NAME, ASCENDING)) .map(entityToAdapterFunc(realm)); } @@ -136,7 +128,12 @@ public class MapRoleProvider implements RoleProvider { @Override public Stream getClientRolesStream(ClientModel client, Integer first, Integer max) { - return paginatedStream(getClientRolesStream(client), first, max); + ModelCriteriaBuilder mcb = roleStore.createCriteriaBuilder() + .compare(SearchableFields.REALM_ID, Operator.EQ, client.getRealm().getId()) + .compare(SearchableFields.CLIENT_ID, Operator.EQ, client.getId()); + + return tx.read(withCriteria(mcb).pagination(first, max, SearchableFields.NAME)) + .map(entityToAdapterFunc(client.getRealm())); } @Override @@ -145,8 +142,7 @@ public class MapRoleProvider implements RoleProvider { .compare(SearchableFields.REALM_ID, Operator.EQ, client.getRealm().getId()) .compare(SearchableFields.CLIENT_ID, Operator.EQ, client.getId()); - return tx.read(mcb) - .sorted(COMPARE_BY_NAME) + return tx.read(withCriteria(mcb).orderBy(SearchableFields.NAME, ASCENDING)) .map(entityToAdapterFunc(client.getRealm())); } @Override @@ -197,7 +193,7 @@ public class MapRoleProvider implements RoleProvider { .compare(SearchableFields.REALM_ID, Operator.EQ, realm.getId()) .compare(SearchableFields.NAME, Operator.ILIKE, name); - String roleId = tx.read(mcb) + String roleId = tx.read(withCriteria(mcb)) .map(entityToAdapterFunc(realm)) .map(RoleModel::getId) .findFirst() @@ -218,7 +214,7 @@ public class MapRoleProvider implements RoleProvider { .compare(SearchableFields.CLIENT_ID, Operator.EQ, client.getId()) .compare(SearchableFields.NAME, Operator.ILIKE, name); - String roleId = tx.read(mcb) + String roleId = tx.read(withCriteria(mcb)) .map(entityToAdapterFunc(client.getRealm())) .map(RoleModel::getId) .findFirst() @@ -254,10 +250,8 @@ public class MapRoleProvider implements RoleProvider { roleStore.createCriteriaBuilder().compare(SearchableFields.DESCRIPTION, Operator.ILIKE, "%" + search + "%") ); - Stream> s = tx.read(mcb) - .sorted(COMPARE_BY_NAME); - - return paginatedStream(s.map(entityToAdapterFunc(realm)), first, max); + return tx.read(withCriteria(mcb).pagination(first, max, SearchableFields.NAME)) + .map(entityToAdapterFunc(realm)); } @Override @@ -272,10 +266,8 @@ public class MapRoleProvider implements RoleProvider { roleStore.createCriteriaBuilder().compare(SearchableFields.NAME, Operator.ILIKE, "%" + search + "%"), roleStore.createCriteriaBuilder().compare(SearchableFields.DESCRIPTION, Operator.ILIKE, "%" + search + "%") ); - Stream> s = tx.read(mcb) - .sorted(COMPARE_BY_NAME); - - return paginatedStream(s,first, max).map(entityToAdapterFunc(client.getRealm())); + return tx.read(withCriteria(mcb).pagination(first, max, SearchableFields.NAME)) + .map(entityToAdapterFunc(client.getRealm())); } @Override diff --git a/model/map/src/main/java/org/keycloak/models/map/storage/MapKeycloakTransaction.java b/model/map/src/main/java/org/keycloak/models/map/storage/MapKeycloakTransaction.java index 466fb97c32..d1a71bf591 100644 --- a/model/map/src/main/java/org/keycloak/models/map/storage/MapKeycloakTransaction.java +++ b/model/map/src/main/java/org/keycloak/models/map/storage/MapKeycloakTransaction.java @@ -58,20 +58,19 @@ public interface MapKeycloakTransaction, M> exten * transaction by methods {@link MapKeycloakTransaction#create}, {@link MapKeycloakTransaction#update}, * {@link MapKeycloakTransaction#delete}, etc. * - * @param mcb criteria to filter values + * @param queryParameters parameters for the query like firstResult, maxResult, requested ordering, etc. * @return values that fulfill the given criteria, that are updated based on changes in the current transaction */ - Stream read(ModelCriteriaBuilder mcb); - + Stream read(QueryParameters queryParameters); /** * Returns a number of values present in the underlying storage that fulfill the given criteria with respect to * changes done in the current transaction. * - * @param mcb criteria to filter values + * @param queryParameters parameters for the query like firstResult, maxResult, requested ordering, etc. * @return number of values present in the storage that fulfill the given criteria */ - long getCount(ModelCriteriaBuilder mcb); + long getCount(QueryParameters queryParameters); /** * Instructs this transaction to force-update the {@code value} associated with the identifier {@code value.getId()} in the @@ -116,8 +115,9 @@ public interface MapKeycloakTransaction, M> exten * * @param artificialKey key to record the transaction with, must be a key that does not exist in this transaction to * prevent collisions with other operations in this transaction - * @param mcb criteria to delete values + * @param queryParameters parameters for the query like firstResult, maxResult, requested ordering, etc. + * @return number of removed objects (might return {@code -1} if not supported) */ - long delete(K artificialKey, ModelCriteriaBuilder mcb); + long delete(K artificialKey, QueryParameters queryParameters); } diff --git a/model/map/src/main/java/org/keycloak/models/map/storage/MapStorage.java b/model/map/src/main/java/org/keycloak/models/map/storage/MapStorage.java index 45d7a01a75..ec09d754a5 100644 --- a/model/map/src/main/java/org/keycloak/models/map/storage/MapStorage.java +++ b/model/map/src/main/java/org/keycloak/models/map/storage/MapStorage.java @@ -20,12 +20,14 @@ import org.keycloak.models.KeycloakSession; import org.keycloak.models.map.common.AbstractEntity; import java.util.stream.Stream; +import static org.keycloak.models.map.storage.QueryParameters.withCriteria; + /** * Implementation of this interface interacts with a persistence storage storing various entities, e.g. users, realms. - * It contains basic object CRUD operations as well as bulk {@link #read(org.keycloak.models.map.storage.ModelCriteriaBuilder)} - * and bulk {@link #delete(org.keycloak.models.map.storage.ModelCriteriaBuilder)} operations, + * It contains basic object CRUD operations as well as bulk {@link #read(org.keycloak.models.map.storage.QueryParameters)} + * and bulk {@link #delete(org.keycloak.models.map.storage.QueryParameters)} operations, * and operation for determining the number of the objects satisfying given criteria - * ({@link #getCount(org.keycloak.models.map.storage.ModelCriteriaBuilder)}). + * ({@link #getCount(org.keycloak.models.map.storage.QueryParameters)}). * * @author hmlnarik * @param Type of the primary key. Various storages can @@ -62,29 +64,27 @@ public interface MapStorage, M> { * Returns stream of objects satisfying given {@code criteria} from the storage. * The criteria are specified in the given criteria builder based on model properties. * - * @param criteria Criteria filtering out the object, originally obtained - * from {@link #createCriteriaBuilder()} method of this object. - * If {@code null}, it returns an empty stream. + * @param queryParameters parameters for the query like firstResult, maxResult, requested ordering, etc. * @return Stream of objects. Never returns {@code null}. * @throws IllegalStateException If {@code criteria} is not compatible, i.e. has not been originally created * by the {@link #createCriteriaBuilder()} method of this object. */ - Stream read(ModelCriteriaBuilder criteria); + Stream read(QueryParameters queryParameters); /** * Returns the number of objects satisfying given {@code criteria} from the storage. * The criteria are specified in the given criteria builder based on model properties. * - * @param criteria + * @param queryParameters parameters for the query like firstResult, maxResult, requested ordering, etc. * @return Number of objects. Never returns {@code null}. * @throws IllegalStateException If {@code criteria} is not compatible, i.e. has not been originally created * by the {@link #createCriteriaBuilder()} method of this object. */ - long getCount(ModelCriteriaBuilder criteria); + long getCount(QueryParameters queryParameters); /** * Updates the object with the key of the {@code value}'s ID in the storage if it already exists. - * @param key Primary key of the object to update + * * @param value Updated value * @throws NullPointerException if the object or its {@code id} is {@code null} * @see AbstractEntity#getId() @@ -100,12 +100,12 @@ public interface MapStorage, M> { /** * Deletes objects that match the given criteria. - * @param criteria + * @param queryParameters parameters for the query like firstResult, maxResult, requested ordering, etc. * @return Number of removed objects (might return {@code -1} if not supported) * @throws IllegalStateException If {@code criteria} is not compatible, i.e. has not been originally created * by the {@link #createCriteriaBuilder()} method of this object. */ - long delete(ModelCriteriaBuilder criteria); + long delete(QueryParameters queryParameters); /** @@ -122,7 +122,6 @@ public interface MapStorage, M> { * @return See description. Never returns {@code null} */ ModelCriteriaBuilder createCriteriaBuilder(); - /** * Creates a {@code MapKeycloakTransaction} object that tracks a new transaction related to this storage. diff --git a/model/map/src/main/java/org/keycloak/models/map/storage/QueryParameters.java b/model/map/src/main/java/org/keycloak/models/map/storage/QueryParameters.java new file mode 100644 index 0000000000..e52b3b49b3 --- /dev/null +++ b/model/map/src/main/java/org/keycloak/models/map/storage/QueryParameters.java @@ -0,0 +1,139 @@ +package org.keycloak.models.map.storage; + +import org.keycloak.storage.SearchableModelField; + +import java.util.LinkedList; +import java.util.List; + +import static org.keycloak.models.map.storage.QueryParameters.Order.ASCENDING; + +/** + * Wraps together parameters for querying storage e.g. number of results to return, requested order or filtering criteria + * + * @param Provide entity specific type checking, for example, when we create {@code QueryParameters} + * instance for Users, M is equal to UserModel, hence we are not able, for example, to order result by a + * {@link SearchableModelField} defined for clients in {@link org.keycloak.models.ClientModel}. + */ +public class QueryParameters { + + private Integer offset; + private Integer limit; + private final List> orderBy = new LinkedList<>(); + private ModelCriteriaBuilder mcb; + + public QueryParameters() { + } + + public QueryParameters(ModelCriteriaBuilder mcb) { + this.mcb = mcb; + } + + /** + * Creates a new {@code QueryParameters} instance initialized with {@link ModelCriteriaBuilder} + * + * @param mcb filtering criteria + * @param model type + * @return a new {@code QueryParameters} instance + */ + public static QueryParameters withCriteria(ModelCriteriaBuilder mcb) { + return new QueryParameters<>(mcb); + } + + /** + * Sets pagination (offset, limit and orderBy) parameters to {@code QueryParameters} + * + * @param offset + * @param limit + * @param orderByAscField + * @return this object + */ + public QueryParameters pagination(Integer offset, Integer limit, SearchableModelField orderByAscField) { + this.offset = offset; + this.limit = limit; + this.orderBy.add(new OrderBy<>(orderByAscField, ASCENDING)); + + return this; + } + + /** + * Sets orderBy parameter; can be called repeatedly; fields are stored in a list where the first field has highest + * priority when determining order; e.g. the second field is compared only when values for the first field are equal + * + * @param searchableModelField + * @return this object + */ + public QueryParameters orderBy(SearchableModelField searchableModelField, Order order) { + orderBy.add(new OrderBy<>(searchableModelField, order)); + + return this; + } + + /** + * Sets offset parameter + * + * @param offset + * @return + */ + public QueryParameters offset(Integer offset) { + this.offset = offset; + return this; + } + + /** + * Sets limit parameter + * + * @param limit + * @return + */ + public QueryParameters limit(Integer limit) { + this.limit = limit; + return this; + } + + public Integer getOffset() { + return offset; + } + + public Integer getLimit() { + return limit; + } + + public ModelCriteriaBuilder getModelCriteriaBuilder() { + return mcb; + } + + public List> getOrderBy() { + return orderBy; + } + + /** + * Enum for ascending or descending ordering + */ + public enum Order { + ASCENDING, + DESCENDING + } + + /** + * Wrapper class for a field with its {@code Order}, ascending or descending + * + * @param + */ + public static class OrderBy { + private final SearchableModelField modelField; + private final Order order; + + public OrderBy(SearchableModelField modelField, Order order) { + this.modelField = modelField; + this.order = order; + } + + public SearchableModelField getModelField() { + return modelField; + } + + public Order getOrder() { + return order; + } + } +} diff --git a/model/map/src/main/java/org/keycloak/models/map/storage/chm/ConcurrentHashMapKeycloakTransaction.java b/model/map/src/main/java/org/keycloak/models/map/storage/chm/ConcurrentHashMapKeycloakTransaction.java index becf89ecb8..805b170dc8 100644 --- a/model/map/src/main/java/org/keycloak/models/map/storage/chm/ConcurrentHashMapKeycloakTransaction.java +++ b/model/map/src/main/java/org/keycloak/models/map/storage/chm/ConcurrentHashMapKeycloakTransaction.java @@ -30,6 +30,8 @@ import org.jboss.logging.Logger; import org.keycloak.models.map.storage.MapKeycloakTransaction; import org.keycloak.models.map.storage.MapStorage; import org.keycloak.models.map.storage.ModelCriteriaBuilder; +import org.keycloak.models.map.storage.QueryParameters; +import org.keycloak.utils.StreamsUtil; public class ConcurrentHashMapKeycloakTransaction, M> implements MapKeycloakTransaction { @@ -133,11 +135,11 @@ public class ConcurrentHashMapKeycloakTransaction * Returns the stream of records that match given criteria and includes changes made in this transaction, i.e. * the result contains updates and excludes records that have been deleted in this transaction. * - * @param mcb + * @param queryParameters * @return */ @Override - public Stream read(ModelCriteriaBuilder mcb) { + public Stream read(QueryParameters queryParameters) { Predicate filterOutAllBulkDeletedObjects = tasks.values().stream() .filter(BulkDeleteOperation.class::isInstance) .map(BulkDeleteOperation.class::cast) @@ -145,7 +147,9 @@ public class ConcurrentHashMapKeycloakTransaction .reduce(Predicate::and) .orElse(v -> true); - Stream updatedAndNotRemovedObjectsStream = this.map.read(mcb) + ModelCriteriaBuilder mcb = queryParameters.getModelCriteriaBuilder(); + + Stream updatedAndNotRemovedObjectsStream = this.map.read(queryParameters) .filter(filterOutAllBulkDeletedObjects) .map(this::getUpdated) // If the object has been removed, tx.get will return null, otherwise it will return me.getValue() .filter(Objects::nonNull); @@ -159,12 +163,17 @@ public class ConcurrentHashMapKeycloakTransaction updatedAndNotRemovedObjectsStream ); - return res; + if (!queryParameters.getOrderBy().isEmpty()) { + res = res.sorted(MapFieldPredicates.getComparator(queryParameters.getOrderBy().stream())); + } + + + return StreamsUtil.paginatedStream(res, queryParameters.getOffset(), queryParameters.getLimit()); } @Override - public long getCount(ModelCriteriaBuilder mcb) { - return read(mcb).count(); + public long getCount(QueryParameters queryParameters) { + return read(queryParameters).count(); } @Override @@ -210,11 +219,11 @@ public class ConcurrentHashMapKeycloakTransaction @Override - public long delete(K artificialKey, ModelCriteriaBuilder mcb) { + public long delete(K artificialKey, QueryParameters queryParameters) { log.tracef("Adding operation DELETE_BULK"); // Remove all tasks that create / update / delete objects deleted by the bulk removal. - final BulkDeleteOperation bdo = new BulkDeleteOperation(mcb); + final BulkDeleteOperation bdo = new BulkDeleteOperation(queryParameters); Predicate filterForNonDeletedObjects = bdo.getFilterForNonDeletedObjects(); long res = 0; for (Iterator> it = tasks.entrySet().iterator(); it.hasNext();) { @@ -355,29 +364,29 @@ public class ConcurrentHashMapKeycloakTransaction private class BulkDeleteOperation extends MapTaskWithValue { - private final ModelCriteriaBuilder mcb; + private final QueryParameters queryParameters; - public BulkDeleteOperation(ModelCriteriaBuilder mcb) { + public BulkDeleteOperation(QueryParameters queryParameters) { super(null); - this.mcb = mcb; + this.queryParameters = queryParameters; } @Override @SuppressWarnings("unchecked") public void execute() { - map.delete(mcb); + map.delete(queryParameters); } public Predicate getFilterForNonDeletedObjects() { - if (! (mcb instanceof MapModelCriteriaBuilder)) { + if (! (queryParameters.getModelCriteriaBuilder() instanceof MapModelCriteriaBuilder)) { return t -> true; } @SuppressWarnings("unchecked") - final MapModelCriteriaBuilder mmcb = (MapModelCriteriaBuilder) mcb; + final MapModelCriteriaBuilder mmcb = (MapModelCriteriaBuilder) queryParameters.getModelCriteriaBuilder(); Predicate entityFilter = mmcb.getEntityFilter(); - Predicate keyFilter = ((MapModelCriteriaBuilder) mcb).getKeyFilter(); + Predicate keyFilter = mmcb.getKeyFilter(); return v -> v == null || ! (keyFilter.test(v.getId()) && entityFilter.test(v)); } @@ -387,7 +396,7 @@ public class ConcurrentHashMapKeycloakTransaction } private long getCount() { - return map.getCount(mcb); + return map.getCount(queryParameters); } } -} \ No newline at end of file +} diff --git a/model/map/src/main/java/org/keycloak/models/map/storage/chm/ConcurrentHashMapStorage.java b/model/map/src/main/java/org/keycloak/models/map/storage/chm/ConcurrentHashMapStorage.java index 6162907d0a..9856cedf19 100644 --- a/model/map/src/main/java/org/keycloak/models/map/storage/chm/ConcurrentHashMapStorage.java +++ b/model/map/src/main/java/org/keycloak/models/map/storage/chm/ConcurrentHashMapStorage.java @@ -21,18 +21,23 @@ import org.keycloak.models.map.storage.MapKeycloakTransaction; import org.keycloak.models.map.common.AbstractEntity; import org.keycloak.models.map.storage.MapStorage; import org.keycloak.models.map.storage.ModelCriteriaBuilder; +import org.keycloak.models.map.storage.QueryParameters; import org.keycloak.storage.SearchableModelField; + +import java.util.Comparator; import java.util.Map; import java.util.Map.Entry; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; +import java.util.concurrent.atomic.AtomicLong; import java.util.stream.Stream; -import org.keycloak.models.map.storage.chm.MapModelCriteriaBuilder.UpdatePredicatesFunc; import org.keycloak.models.map.storage.StringKeyConvertor; -import java.util.Iterator; +import org.keycloak.models.map.storage.chm.MapModelCriteriaBuilder.UpdatePredicatesFunc; import java.util.Objects; import java.util.function.Predicate; +import static org.keycloak.utils.StreamsUtil.paginatedStream; + /** * * @author hmlnarik @@ -74,10 +79,11 @@ public class ConcurrentHashMapStorage, M> impleme } @Override - public long delete(ModelCriteriaBuilder criteria) { - long res; + public long delete(QueryParameters queryParameters) { + ModelCriteriaBuilder criteria = queryParameters.getModelCriteriaBuilder(); + if (criteria == null) { - res = store.size(); + long res = store.size(); store.clear(); return res; } @@ -88,15 +94,21 @@ public class ConcurrentHashMapStorage, M> impleme } Predicate keyFilter = b.getKeyFilter(); Predicate entityFilter = b.getEntityFilter(); - res = 0; - for (Iterator> iterator = store.entrySet().iterator(); iterator.hasNext();) { - Entry next = iterator.next(); - if (keyFilter.test(next.getKey()) && entityFilter.test(next.getValue())) { - res++; - iterator.remove(); - } + Stream> storeStream = store.entrySet().stream(); + final AtomicLong res = new AtomicLong(0); + + if (!queryParameters.getOrderBy().isEmpty()) { + Comparator comparator = MapFieldPredicates.getComparator(queryParameters.getOrderBy().stream()); + storeStream = storeStream.sorted((entry1, entry2) -> comparator.compare(entry1.getValue(), entry2.getValue())); } - return res; + + paginatedStream(storeStream.filter(next -> keyFilter.test(next.getKey()) && entityFilter.test(next.getValue())) + , queryParameters.getOffset(), queryParameters.getLimit()) + .peek(item -> {res.incrementAndGet();}) + .map(Entry::getKey) + .forEach(store::remove); + + return res.get(); } @Override @@ -117,7 +129,9 @@ public class ConcurrentHashMapStorage, M> impleme } @Override - public Stream read(ModelCriteriaBuilder criteria) { + public Stream read(QueryParameters queryParameters) { + ModelCriteriaBuilder criteria = queryParameters.getModelCriteriaBuilder(); + if (criteria == null) { return Stream.empty(); } @@ -135,8 +149,8 @@ public class ConcurrentHashMapStorage, M> impleme } @Override - public long getCount(ModelCriteriaBuilder criteria) { - return read(criteria).count(); + public long getCount(QueryParameters queryParameters) { + return read(queryParameters).count(); } } diff --git a/model/map/src/main/java/org/keycloak/models/map/storage/chm/ConcurrentHashMapStorageProviderFactory.java b/model/map/src/main/java/org/keycloak/models/map/storage/chm/ConcurrentHashMapStorageProviderFactory.java index b0125e8405..e57daa1dab 100644 --- a/model/map/src/main/java/org/keycloak/models/map/storage/chm/ConcurrentHashMapStorageProviderFactory.java +++ b/model/map/src/main/java/org/keycloak/models/map/storage/chm/ConcurrentHashMapStorageProviderFactory.java @@ -57,6 +57,8 @@ import java.util.Collections; import java.util.HashMap; import java.util.Map; +import static org.keycloak.models.map.storage.QueryParameters.withCriteria; + /** * * @author hmlnarik @@ -171,7 +173,7 @@ public class ConcurrentHashMapStorageProviderFactory implements AmphibianProvide LOG.debugf("Storing contents to %s", f.getCanonicalPath()); @SuppressWarnings("unchecked") final ModelCriteriaBuilder readAllCriteria = store.createCriteriaBuilder(); - Serialization.MAPPER.writeValue(f, store.read(readAllCriteria)); + Serialization.MAPPER.writeValue(f, store.read(withCriteria(readAllCriteria))); } else { LOG.debugf("Not storing contents of %s because directory not set", mapName); } diff --git a/model/map/src/main/java/org/keycloak/models/map/storage/chm/MapFieldPredicates.java b/model/map/src/main/java/org/keycloak/models/map/storage/chm/MapFieldPredicates.java index 9f790144f4..d6c48d8b15 100644 --- a/model/map/src/main/java/org/keycloak/models/map/storage/chm/MapFieldPredicates.java +++ b/model/map/src/main/java/org/keycloak/models/map/storage/chm/MapFieldPredicates.java @@ -43,7 +43,10 @@ import org.keycloak.models.map.group.MapGroupEntity; import org.keycloak.models.map.loginFailure.MapUserLoginFailureEntity; import org.keycloak.models.map.realm.MapRealmEntity; import org.keycloak.models.map.role.MapRoleEntity; +import org.keycloak.models.map.storage.QueryParameters; import org.keycloak.storage.SearchableModelField; + +import java.util.Comparator; import java.util.HashMap; import java.util.Map; import org.keycloak.models.map.storage.chm.MapModelCriteriaBuilder.UpdatePredicatesFunc; @@ -56,11 +59,11 @@ import org.keycloak.sessions.RootAuthenticationSessionModel; import org.keycloak.storage.StorageId; import java.util.Arrays; import java.util.Collection; -import java.util.Collections; import java.util.List; import java.util.Objects; import java.util.function.Function; import java.util.function.Predicate; +import java.util.stream.Stream; import org.keycloak.models.map.storage.CriterionNotSupportedException; import static org.keycloak.models.UserSessionModel.CORRESPONDING_SESSION_ID; @@ -89,10 +92,11 @@ public class MapFieldPredicates { @SuppressWarnings("unchecked") private static final Map, Map> PREDICATES = new HashMap<>(); + private static final Map, Comparator> COMPARATORS = new HashMap<>(); static { put(REALM_PREDICATES, RealmModel.SearchableFields.NAME, MapRealmEntity::getName); - put(REALM_PREDICATES, RealmModel.SearchableFields.CLIENT_INITIAL_ACCESS, MapRealmEntity::getClientInitialAccesses); + putIncomparable(REALM_PREDICATES, RealmModel.SearchableFields.CLIENT_INITIAL_ACCESS, MapRealmEntity::getClientInitialAccesses); put(REALM_PREDICATES, RealmModel.SearchableFields.COMPONENT_PROVIDER_TYPE, MapFieldPredicates::checkRealmsWithComponentType); put(CLIENT_PREDICATES, ClientModel.SearchableFields.REALM_ID, MapClientEntity::getRealmId); @@ -136,7 +140,7 @@ public class MapFieldPredicates { put(AUTHENTICATION_SESSION_PREDICATES, RootAuthenticationSessionModel.SearchableFields.REALM_ID, MapRootAuthenticationSessionEntity::getRealmId); put(AUTHENTICATION_SESSION_PREDICATES, RootAuthenticationSessionModel.SearchableFields.TIMESTAMP, MapRootAuthenticationSessionEntity::getTimestamp); - put(AUTHZ_RESOURCE_SERVER_PREDICATES, ResourceServer.SearchableFields.ID, MapResourceServerEntity::getId); + put(AUTHZ_RESOURCE_SERVER_PREDICATES, ResourceServer.SearchableFields.ID, predicateForKeyField(MapResourceServerEntity::getId)); put(AUTHZ_RESOURCE_PREDICATES, Resource.SearchableFields.ID, predicateForKeyField(MapResourceEntity::getId)); put(AUTHZ_RESOURCE_PREDICATES, Resource.SearchableFields.NAME, MapResourceEntity::getName); @@ -207,9 +211,16 @@ public class MapFieldPredicates { PREDICATES.put(UserLoginFailureModel.class, USER_LOGIN_FAILURE_PREDICATES); } - private static , M> void put( + private static , M, L extends Comparable> void put( Map, UpdatePredicatesFunc> map, - SearchableModelField field, Function extractor) { + SearchableModelField field, Function extractor) { + COMPARATORS.put(field, Comparator.comparing(extractor)); + map.put(field, (mcb, op, values) -> mcb.fieldCompare(op, extractor, values)); + } + + private static , M> void putIncomparable( + Map, UpdatePredicatesFunc> map, + SearchableModelField field, Function extractor) { map.put(field, (mcb, op, values) -> mcb.fieldCompare(op, extractor, values)); } @@ -219,7 +230,7 @@ public class MapFieldPredicates { map.put(field, function); } - private static > Function predicateForKeyField(Function extractor) { + private static > Function predicateForKeyField(Function extractor) { return entity -> { Object o = extractor.apply(entity); return o == null ? null : o.toString(); @@ -483,10 +494,35 @@ public class MapFieldPredicates { protected static , M> Map, UpdatePredicatesFunc> basePredicates(SearchableModelField idField) { Map, UpdatePredicatesFunc> fieldPredicates = new HashMap<>(); - fieldPredicates.put(idField, (o, op, values) -> o.idCompare(op, values)); + fieldPredicates.put(idField, MapModelCriteriaBuilder::idCompare); return fieldPredicates; } + public static , M> Comparator getComparator(QueryParameters.OrderBy orderBy) { + SearchableModelField searchableModelField = orderBy.getModelField(); + QueryParameters.Order order = orderBy.getOrder(); + + @SuppressWarnings("unchecked") + Comparator comparator = (Comparator) COMPARATORS.get(searchableModelField); + + if (comparator == null) { + throw new IllegalArgumentException("Comparator for field " + searchableModelField.getName() + " is not configured."); + } + + if (order == QueryParameters.Order.DESCENDING) { + return comparator.reversed(); + } + + return comparator; + } + + @SuppressWarnings("unchecked") + public static , M> Comparator getComparator(Stream> ordering) { + return (Comparator) ordering.map(MapFieldPredicates::getComparator) + .reduce(Comparator::thenComparing) + .orElseThrow(() -> new IllegalArgumentException("Cannot create comparator for " + ordering)); + } + @SuppressWarnings("unchecked") public static , M> Map, UpdatePredicatesFunc> getPredicates(Class clazz) { return PREDICATES.get(clazz); diff --git a/model/map/src/main/java/org/keycloak/models/map/storage/chm/UserSessionConcurrentHashMapStorage.java b/model/map/src/main/java/org/keycloak/models/map/storage/chm/UserSessionConcurrentHashMapStorage.java index 952f3716af..73490a5477 100644 --- a/model/map/src/main/java/org/keycloak/models/map/storage/chm/UserSessionConcurrentHashMapStorage.java +++ b/model/map/src/main/java/org/keycloak/models/map/storage/chm/UserSessionConcurrentHashMapStorage.java @@ -23,12 +23,15 @@ import org.keycloak.models.map.common.AbstractEntity; import org.keycloak.models.map.storage.MapKeycloakTransaction; import org.keycloak.models.map.storage.ModelCriteriaBuilder; import org.keycloak.models.map.storage.ModelCriteriaBuilder.Operator; +import org.keycloak.models.map.storage.QueryParameters; import org.keycloak.models.map.storage.StringKeyConvertor; import org.keycloak.models.map.userSession.MapAuthenticatedClientSessionEntity; import org.keycloak.models.map.userSession.MapUserSessionEntity; import java.util.Set; import java.util.stream.Collectors; +import static org.keycloak.models.map.storage.QueryParameters.withCriteria; + /** * User session storage with a naive implementation of referential integrity in client to user session relation, restricted to * ON DELETE CASCADE functionality. @@ -49,17 +52,19 @@ public class UserSessionConcurrentHashMapStorage extends ConcurrentHashMapSto } @Override - public long delete(K artificialKey, ModelCriteriaBuilder mcb) { - Set ids = read(mcb).map(AbstractEntity::getId).collect(Collectors.toSet()); + public long delete(K artificialKey, QueryParameters queryParameters) { + ModelCriteriaBuilder mcb = queryParameters.getModelCriteriaBuilder(); + + Set ids = read(queryParameters).map(AbstractEntity::getId).collect(Collectors.toSet()); ModelCriteriaBuilder csMcb = clientSessionStore.createCriteriaBuilder().compare(AuthenticatedClientSessionModel.SearchableFields.USER_SESSION_ID, Operator.IN, ids); - clientSessionTr.delete(artificialKey, csMcb); - return super.delete(artificialKey, mcb); + clientSessionTr.delete(artificialKey, withCriteria(csMcb)); + return super.delete(artificialKey, queryParameters); } @Override public void delete(K key) { ModelCriteriaBuilder csMcb = clientSessionStore.createCriteriaBuilder().compare(AuthenticatedClientSessionModel.SearchableFields.USER_SESSION_ID, Operator.EQ, key); - clientSessionTr.delete(key, csMcb); + clientSessionTr.delete(key, withCriteria(csMcb)); super.delete(key); } diff --git a/model/map/src/main/java/org/keycloak/models/map/user/MapUserEntity.java b/model/map/src/main/java/org/keycloak/models/map/user/MapUserEntity.java index 495dd0053b..1c39d216c1 100644 --- a/model/map/src/main/java/org/keycloak/models/map/user/MapUserEntity.java +++ b/model/map/src/main/java/org/keycloak/models/map/user/MapUserEntity.java @@ -65,8 +65,6 @@ public class MapUserEntity implements AbstractEntity { private String serviceAccountClientLink; private int notBefore; - static Comparator> COMPARE_BY_USERNAME = Comparator.comparing(MapUserEntity::getUsername); - /** * Flag signalizing that any of the setters has been meaningfully used. */ diff --git a/model/map/src/main/java/org/keycloak/models/map/user/MapUserProvider.java b/model/map/src/main/java/org/keycloak/models/map/user/MapUserProvider.java index 4116fcdaca..f64fe9e680 100644 --- a/model/map/src/main/java/org/keycloak/models/map/user/MapUserProvider.java +++ b/model/map/src/main/java/org/keycloak/models/map/user/MapUserProvider.java @@ -70,7 +70,8 @@ import static org.keycloak.models.UserModel.FIRST_NAME; import static org.keycloak.models.UserModel.LAST_NAME; import static org.keycloak.models.UserModel.USERNAME; import static org.keycloak.models.map.common.MapStorageUtils.registerEntityForChanges; -import static org.keycloak.utils.StreamsUtil.paginatedStream; +import static org.keycloak.models.map.storage.QueryParameters.Order.ASCENDING; +import static org.keycloak.models.map.storage.QueryParameters.withCriteria; public class MapUserProvider implements UserProvider.Streams, UserCredentialStore.Streams { @@ -173,7 +174,7 @@ public class MapUserProvider implements UserProvider.Streams, UserCredentialS .compare(SearchableFields.REALM_ID, Operator.EQ, realm.getId()) .compare(SearchableFields.IDP_AND_USER, Operator.EQ, socialProvider); - tx.read(mcb) + tx.read(withCriteria(mcb)) .map(e -> registerEntityForChanges(tx, e)) .forEach(userEntity -> userEntity.removeFederatedIdentity(socialProvider)); } @@ -208,8 +209,8 @@ public class MapUserProvider implements UserProvider.Streams, UserCredentialS ModelCriteriaBuilder mcb = userStore.createCriteriaBuilder() .compare(SearchableFields.REALM_ID, Operator.EQ, realm.getId()) .compare(SearchableFields.IDP_AND_USER, Operator.EQ, socialLink.getIdentityProvider(), socialLink.getUserId()); - - return tx.read(mcb) + + return tx.read(withCriteria(mcb)) .collect(Collectors.collectingAndThen( Collectors.toList(), list -> { @@ -298,7 +299,7 @@ public class MapUserProvider implements UserProvider.Streams, UserCredentialS .compare(SearchableFields.REALM_ID, Operator.EQ, client.getRealm().getId()) .compare(SearchableFields.SERVICE_ACCOUNT_CLIENT, Operator.EQ, client.getId()); - return tx.read(mcb) + return tx.read(withCriteria(mcb)) .collect(Collectors.collectingAndThen( Collectors.toList(), list -> { @@ -321,7 +322,7 @@ public class MapUserProvider implements UserProvider.Streams, UserCredentialS .compare(SearchableFields.REALM_ID, Operator.EQ, realm.getId()) .compare(SearchableFields.USERNAME, Operator.EQ, username); - if (tx.getCount(mcb) > 0) { + if (tx.getCount(withCriteria(mcb)) > 0) { throw new ModelDuplicateException("User with username '" + username + "' in realm " + realm.getName() + " already exists" ); } @@ -362,7 +363,7 @@ public class MapUserProvider implements UserProvider.Streams, UserCredentialS ModelCriteriaBuilder mcb = userStore.createCriteriaBuilder() .compare(SearchableFields.REALM_ID, Operator.EQ, realm.getId()); - tx.delete(userStore.getKeyConvertor().yieldNewUniqueKey(), mcb); + tx.delete(userStore.getKeyConvertor().yieldNewUniqueKey(), withCriteria(mcb)); } @Override @@ -372,7 +373,7 @@ public class MapUserProvider implements UserProvider.Streams, UserCredentialS .compare(SearchableFields.REALM_ID, Operator.EQ, realm.getId()) .compare(SearchableFields.FEDERATION_LINK, Operator.EQ, storageProviderId); - tx.delete(userStore.getKeyConvertor().yieldNewUniqueKey(), mcb); + tx.delete(userStore.getKeyConvertor().yieldNewUniqueKey(), withCriteria(mcb)); } @Override @@ -382,7 +383,7 @@ public class MapUserProvider implements UserProvider.Streams, UserCredentialS .compare(SearchableFields.REALM_ID, Operator.EQ, realm.getId()) .compare(SearchableFields.FEDERATION_LINK, Operator.EQ, storageProviderId); - try (Stream> s = tx.read(mcb)) { + try (Stream> s = tx.read(withCriteria(mcb))) { s.map(e -> registerEntityForChanges(tx, e)) .forEach(userEntity -> userEntity.setFederationLink(null)); } @@ -396,7 +397,7 @@ public class MapUserProvider implements UserProvider.Streams, UserCredentialS .compare(SearchableFields.REALM_ID, Operator.EQ, realm.getId()) .compare(SearchableFields.ASSIGNED_ROLE, Operator.EQ, roleId); - try (Stream> s = tx.read(mcb)) { + try (Stream> s = tx.read(withCriteria(mcb))) { s.map(e -> registerEntityForChanges(tx, e)) .forEach(userEntity -> userEntity.removeRolesMembership(roleId)); } @@ -410,7 +411,7 @@ public class MapUserProvider implements UserProvider.Streams, UserCredentialS .compare(SearchableFields.REALM_ID, Operator.EQ, realm.getId()) .compare(SearchableFields.ASSIGNED_GROUP, Operator.EQ, groupId); - try (Stream> s = tx.read(mcb)) { + try (Stream> s = tx.read(withCriteria(mcb))) { s.map(e -> registerEntityForChanges(tx, e)) .forEach(userEntity -> userEntity.removeGroupsMembership(groupId)); } @@ -424,7 +425,7 @@ public class MapUserProvider implements UserProvider.Streams, UserCredentialS .compare(SearchableFields.REALM_ID, Operator.EQ, realm.getId()) .compare(SearchableFields.CONSENT_FOR_CLIENT, Operator.EQ, clientId); - try (Stream> s = tx.read(mcb)) { + try (Stream> s = tx.read(withCriteria(mcb))) { s.map(e -> registerEntityForChanges(tx, e)) .forEach(userEntity -> userEntity.removeUserConsent(clientId)); } @@ -444,7 +445,7 @@ public class MapUserProvider implements UserProvider.Streams, UserCredentialS .compare(SearchableFields.REALM_ID, Operator.EQ, clientScope.getRealm().getId()) .compare(SearchableFields.CONSENT_WITH_CLIENT_SCOPE, Operator.EQ, clientScopeId); - try (Stream> s = tx.read(mcb)) { + try (Stream> s = tx.read(withCriteria(mcb))) { s.flatMap(MapUserEntity::getUserConsents) .forEach(consent -> consent.removeGrantedClientScopesIds(clientScopeId)); } @@ -462,7 +463,7 @@ public class MapUserProvider implements UserProvider.Streams, UserCredentialS .compare(SearchableFields.REALM_ID, Operator.EQ, realm.getId()) .compare(SearchableFields.CONSENT_CLIENT_FEDERATION_LINK, Operator.EQ, componentId); - try (Stream> s = tx.read(mcb)) { + try (Stream> s = tx.read(withCriteria(mcb))) { String providerIdS = new StorageId(componentId, "").getId(); s.forEach(removeConsentsForExternalClient(providerIdS)); } @@ -490,7 +491,7 @@ public class MapUserProvider implements UserProvider.Streams, UserCredentialS ModelCriteriaBuilder mcb = userStore.createCriteriaBuilder() .compare(SearchableFields.REALM_ID, Operator.EQ, realm.getId()); - try (Stream> s = tx.read(mcb)) { + try (Stream> s = tx.read(withCriteria(mcb))) { s.map(e -> registerEntityForChanges(tx, e)) .forEach(entity -> entity.addRolesMembership(roleId)); } @@ -510,7 +511,7 @@ public class MapUserProvider implements UserProvider.Streams, UserCredentialS .compare(SearchableFields.REALM_ID, Operator.EQ, realm.getId()) .compare(SearchableFields.USERNAME, Operator.ILIKE, username); - try (Stream> s = tx.read(mcb)) { + try (Stream> s = tx.read(withCriteria(mcb))) { return s.findFirst() .map(entityToAdapterFunc(realm)).orElse(null); } @@ -523,7 +524,7 @@ public class MapUserProvider implements UserProvider.Streams, UserCredentialS .compare(SearchableFields.REALM_ID, Operator.EQ, realm.getId()) .compare(SearchableFields.EMAIL, Operator.EQ, email); - List> usersWithEmail = tx.read(mcb) + List> usersWithEmail = tx.read(withCriteria(mcb)) .filter(userEntity -> Objects.equals(userEntity.getEmail(), email)) .collect(Collectors.toList()); if (usersWithEmail.isEmpty()) return null; @@ -571,7 +572,7 @@ public class MapUserProvider implements UserProvider.Streams, UserCredentialS mcb = mcb.compare(SearchableFields.SERVICE_ACCOUNT_CLIENT, Operator.NOT_EXISTS); } - return (int) tx.getCount(mcb); + return (int) tx.getCount(withCriteria(mcb)); } @Override @@ -584,9 +585,8 @@ public class MapUserProvider implements UserProvider.Streams, UserCredentialS mcb = mcb.compare(SearchableFields.SERVICE_ACCOUNT_CLIENT, Operator.NOT_EXISTS); } - return paginatedStream(tx.read(mcb) - .sorted(MapUserEntity.COMPARE_BY_USERNAME), firstResult, maxResults) - .map(entityToAdapterFunc(realm)); + return tx.read(withCriteria(mcb).pagination(firstResult, maxResults, SearchableFields.USERNAME)) + .map(entityToAdapterFunc(realm)); } @Override @@ -701,10 +701,7 @@ public class MapUserProvider implements UserProvider.Streams, UserCredentialS mcb = mcb.compare(SearchableFields.ASSIGNED_GROUP, Operator.IN, authorizedGroups); } - Stream> usersStream = tx.read(mcb) - .sorted(MapUserEntity.COMPARE_BY_USERNAME); // Sort before paginating - - return paginatedStream(usersStream, firstResult, maxResults) // paginate if necessary + return tx.read(withCriteria(mcb).pagination(firstResult, maxResults, SearchableFields.USERNAME)) .map(entityToAdapterFunc(realm)) .filter(Objects::nonNull); } @@ -716,7 +713,7 @@ public class MapUserProvider implements UserProvider.Streams, UserCredentialS .compare(SearchableFields.REALM_ID, Operator.EQ, realm.getId()) .compare(SearchableFields.ASSIGNED_GROUP, Operator.EQ, group.getId()); - return paginatedStream(tx.read(mcb).sorted(MapUserEntity.COMPARE_BY_USERNAME), firstResult, maxResults) + return tx.read(withCriteria(mcb).pagination(firstResult, maxResults, SearchableFields.USERNAME)) .map(entityToAdapterFunc(realm)); } @@ -727,8 +724,7 @@ public class MapUserProvider implements UserProvider.Streams, UserCredentialS .compare(SearchableFields.REALM_ID, Operator.EQ, realm.getId()) .compare(SearchableFields.ATTRIBUTE, Operator.EQ, attrName, attrValue); - return tx.read(mcb) - .sorted(MapUserEntity.COMPARE_BY_USERNAME) + return tx.read(withCriteria(mcb).orderBy(SearchableFields.USERNAME, ASCENDING)) .map(entityToAdapterFunc(realm)); } @@ -756,8 +752,7 @@ public class MapUserProvider implements UserProvider.Streams, UserCredentialS .compare(SearchableFields.REALM_ID, Operator.EQ, realm.getId()) .compare(SearchableFields.ASSIGNED_ROLE, Operator.EQ, role.getId()); - return paginatedStream(tx.read(mcb) - .sorted(MapUserEntity.COMPARE_BY_USERNAME), firstResult, maxResults) + return tx.read(withCriteria(mcb).pagination(firstResult, maxResults, SearchableFields.USERNAME)) .map(entityToAdapterFunc(realm)); } diff --git a/model/map/src/main/java/org/keycloak/models/map/userSession/MapUserSessionProvider.java b/model/map/src/main/java/org/keycloak/models/map/userSession/MapUserSessionProvider.java index f0b2499988..28886943a0 100644 --- a/model/map/src/main/java/org/keycloak/models/map/userSession/MapUserSessionProvider.java +++ b/model/map/src/main/java/org/keycloak/models/map/userSession/MapUserSessionProvider.java @@ -33,7 +33,6 @@ import org.keycloak.models.map.storage.ModelCriteriaBuilder; import java.util.Arrays; import java.util.Collection; -import java.util.Comparator; import java.util.HashMap; import java.util.Map; import java.util.Objects; @@ -48,9 +47,9 @@ import static org.keycloak.common.util.StackUtil.getShortStackTrace; import static org.keycloak.models.UserSessionModel.CORRESPONDING_SESSION_ID; import static org.keycloak.models.UserSessionModel.SessionPersistenceState.TRANSIENT; import static org.keycloak.models.map.common.MapStorageUtils.registerEntityForChanges; +import static org.keycloak.models.map.storage.QueryParameters.withCriteria; import static org.keycloak.models.map.userSession.SessionExpiration.setClientSessionExpiration; import static org.keycloak.models.map.userSession.SessionExpiration.setUserSessionExpiration; -import static org.keycloak.utils.StreamsUtil.paginatedStream; /** * @author Martin Kanis @@ -195,7 +194,7 @@ public class MapUserSessionProvider implements UserSessionProvider { .compare(AuthenticatedClientSessionModel.SearchableFields.CLIENT_ID, ModelCriteriaBuilder.Operator.EQ, client.getId()) .compare(AuthenticatedClientSessionModel.SearchableFields.IS_OFFLINE, ModelCriteriaBuilder.Operator.EQ, offline); - return clientSessionTx.read(mcb) + return clientSessionTx.read(withCriteria(mcb)) .findFirst() .map(clientEntityToAdapterFunc(client.getRealm(), client, userSession)) .orElse(null); @@ -258,7 +257,7 @@ public class MapUserSessionProvider implements UserSessionProvider { ModelCriteriaBuilder mcb = realmAndOfflineCriteriaBuilder(realm, false) .compare(UserSessionModel.SearchableFields.ID, ModelCriteriaBuilder.Operator.EQ, uuid); - return userSessionTx.read(mcb) + return userSessionTx.read(withCriteria(mcb)) .findFirst() .map(userEntityToAdapterFunc(realm)) .orElse(null); @@ -271,7 +270,7 @@ public class MapUserSessionProvider implements UserSessionProvider { LOG.tracef("getUserSessionsStream(%s, %s)%s", realm, user, getShortStackTrace()); - return userSessionTx.read(mcb) + return userSessionTx.read(withCriteria(mcb)) .map(userEntityToAdapterFunc(realm)) .filter(Objects::nonNull); } @@ -283,7 +282,7 @@ public class MapUserSessionProvider implements UserSessionProvider { LOG.tracef("getUserSessionsStream(%s, %s)%s", realm, client, getShortStackTrace()); - return userSessionTx.read(mcb) + return userSessionTx.read(withCriteria(mcb)) .map(userEntityToAdapterFunc(realm)) .filter(Objects::nonNull); } @@ -291,8 +290,16 @@ public class MapUserSessionProvider implements UserSessionProvider { @Override public Stream getUserSessionsStream(RealmModel realm, ClientModel client, Integer firstResult, Integer maxResults) { - return paginatedStream(getUserSessionsStream(realm, client) - .sorted(Comparator.comparing(UserSessionModel::getLastSessionRefresh)), firstResult, maxResults); + LOG.tracef("getUserSessionsStream(%s, %s, %s, %s)%s", realm, client, firstResult, maxResults, getShortStackTrace()); + + ModelCriteriaBuilder mcb = realmAndOfflineCriteriaBuilder(realm, false) + .compare(UserSessionModel.SearchableFields.CLIENT_ID, ModelCriteriaBuilder.Operator.EQ, client.getId()); + + + return userSessionTx.read(withCriteria(mcb).pagination(firstResult, maxResults, + UserSessionModel.SearchableFields.LAST_SESSION_REFRESH)) + .map(userEntityToAdapterFunc(realm)) + .filter(Objects::nonNull); } @Override @@ -302,7 +309,7 @@ public class MapUserSessionProvider implements UserSessionProvider { LOG.tracef("getUserSessionByBrokerUserIdStream(%s, %s)%s", realm, brokerUserId, getShortStackTrace()); - return userSessionTx.read(mcb) + return userSessionTx.read(withCriteria(mcb)) .map(userEntityToAdapterFunc(realm)) .filter(Objects::nonNull); } @@ -314,7 +321,7 @@ public class MapUserSessionProvider implements UserSessionProvider { LOG.tracef("getUserSessionByBrokerSessionId(%s, %s)%s", realm, brokerSessionId, getShortStackTrace()); - return userSessionTx.read(mcb) + return userSessionTx.read(withCriteria(mcb)) .findFirst() .map(userEntityToAdapterFunc(realm)) .orElse(null); @@ -347,7 +354,7 @@ public class MapUserSessionProvider implements UserSessionProvider { LOG.tracef("getActiveUserSessions(%s, %s)%s", realm, client, getShortStackTrace()); - return userSessionTx.getCount(mcb); + return userSessionTx.getCount(withCriteria(mcb)); } @Override @@ -356,7 +363,7 @@ public class MapUserSessionProvider implements UserSessionProvider { LOG.tracef("getActiveClientSessionStats(%s, %s)%s", realm, offline, getShortStackTrace()); - return userSessionTx.read(mcb) + return userSessionTx.read(withCriteria(mcb)) .map(userEntityToAdapterFunc(realm)) .filter(Objects::nonNull) .map(UserSessionModel::getAuthenticatedClientSessions) @@ -375,7 +382,7 @@ public class MapUserSessionProvider implements UserSessionProvider { LOG.tracef("removeUserSession(%s, %s)%s", realm, session, getShortStackTrace()); - userSessionTx.delete(userSessionStore.getKeyConvertor().yieldNewUniqueKey(), mcb); + userSessionTx.delete(userSessionStore.getKeyConvertor().yieldNewUniqueKey(), withCriteria(mcb)); } @Override @@ -386,7 +393,7 @@ public class MapUserSessionProvider implements UserSessionProvider { LOG.tracef("removeUserSessions(%s, %s)%s", realm, user, getShortStackTrace()); - userSessionTx.delete(userSessionStore.getKeyConvertor().yieldNewUniqueKey(), mcb); + userSessionTx.delete(userSessionStore.getKeyConvertor().yieldNewUniqueKey(), withCriteria(mcb)); } @Override @@ -405,7 +412,7 @@ public class MapUserSessionProvider implements UserSessionProvider { LOG.tracef("removeUserSessions(%s)%s", realm, getShortStackTrace()); - userSessionTx.delete(userSessionStore.getKeyConvertor().yieldNewUniqueKey(), mcb); + userSessionTx.delete(userSessionStore.getKeyConvertor().yieldNewUniqueKey(), withCriteria(mcb)); } @Override @@ -462,7 +469,7 @@ public class MapUserSessionProvider implements UserSessionProvider { UK uk = userSessionStore.getKeyConvertor().fromString(userSession.getNote(CORRESPONDING_SESSION_ID)); mcb = realmAndOfflineCriteriaBuilder(realm, true) .compare(UserSessionModel.SearchableFields.ID, ModelCriteriaBuilder.Operator.EQ, uk); - userSessionTx.delete(userSessionStore.getKeyConvertor().yieldNewUniqueKey(), mcb); + userSessionTx.delete(userSessionStore.getKeyConvertor().yieldNewUniqueKey(), withCriteria(mcb)); userSession.removeNote(CORRESPONDING_SESSION_ID); } } @@ -496,7 +503,7 @@ public class MapUserSessionProvider implements UserSessionProvider { LOG.tracef("getOfflineUserSessionsStream(%s, %s)%s", realm, user, getShortStackTrace()); - return userSessionTx.read(mcb) + return userSessionTx.read(withCriteria(mcb)) .map(userEntityToAdapterFunc(realm)) .filter(Objects::nonNull); } @@ -508,7 +515,7 @@ public class MapUserSessionProvider implements UserSessionProvider { LOG.tracef("getOfflineUserSessionByBrokerSessionId(%s, %s)%s", realm, brokerSessionId, getShortStackTrace()); - return userSessionTx.read(mcb) + return userSessionTx.read(withCriteria(mcb)) .findFirst() .map(userEntityToAdapterFunc(realm)) .orElse(null); @@ -521,7 +528,7 @@ public class MapUserSessionProvider implements UserSessionProvider { LOG.tracef("getOfflineUserSessionByBrokerUserIdStream(%s, %s)%s", realm, brokerUserId, getShortStackTrace()); - return userSessionTx.read(mcb) + return userSessionTx.read(withCriteria(mcb)) .map(userEntityToAdapterFunc(realm)) .filter(Objects::nonNull); } @@ -533,7 +540,7 @@ public class MapUserSessionProvider implements UserSessionProvider { LOG.tracef("getOfflineSessionsCount(%s, %s)%s", realm, client, getShortStackTrace()); - return userSessionTx.getCount(mcb); + return userSessionTx.getCount(withCriteria(mcb)); } @Override @@ -544,10 +551,10 @@ public class MapUserSessionProvider implements UserSessionProvider { LOG.tracef("getOfflineUserSessionsStream(%s, %s, %s, %s)%s", realm, client, firstResult, maxResults, getShortStackTrace()); - return paginatedStream(userSessionTx.read(mcb) + return userSessionTx.read(withCriteria(mcb).pagination(firstResult, maxResults, + UserSessionModel.SearchableFields.LAST_SESSION_REFRESH)) .map(userEntityToAdapterFunc(realm)) - .filter(Objects::nonNull) - .sorted(Comparator.comparing(UserSessionModel::getLastSessionRefresh)), firstResult, maxResults); + .filter(Objects::nonNull); } @Override @@ -595,7 +602,7 @@ public class MapUserSessionProvider implements UserSessionProvider { .compare(UserSessionModel.SearchableFields.ID, ModelCriteriaBuilder.Operator.EQ, uuid); // check if it's an offline user session - MapUserSessionEntity userSessionEntity = userSessionTx.read(mcb).findFirst().orElse(null); + MapUserSessionEntity userSessionEntity = userSessionTx.read(withCriteria(mcb)).findFirst().orElse(null); if (userSessionEntity != null) { if (userSessionEntity.isOffline()) { return Stream.of(userSessionEntity); @@ -604,7 +611,7 @@ public class MapUserSessionProvider implements UserSessionProvider { // no session found by the given ID, try to find by corresponding session ID mcb = realmAndOfflineCriteriaBuilder(realm, true) .compare(UserSessionModel.SearchableFields.CORRESPONDING_SESSION_ID, ModelCriteriaBuilder.Operator.EQ, userSessionId); - return userSessionTx.read(mcb); + return userSessionTx.read(withCriteria(mcb)); } // it's online user session so lookup offline user session by corresponding session id reference @@ -613,7 +620,7 @@ public class MapUserSessionProvider implements UserSessionProvider { UK uk = userSessionStore.getKeyConvertor().fromStringSafe(offlineUserSessionId); mcb = realmAndOfflineCriteriaBuilder(realm, true) .compare(UserSessionModel.SearchableFields.ID, ModelCriteriaBuilder.Operator.EQ, uk); - return userSessionTx.read(mcb); + return userSessionTx.read(withCriteria(mcb)); } return Stream.empty(); diff --git a/server-spi/src/main/java/org/keycloak/storage/SearchableModelField.java b/server-spi/src/main/java/org/keycloak/storage/SearchableModelField.java index d34a33d0b3..8923565cb9 100644 --- a/server-spi/src/main/java/org/keycloak/storage/SearchableModelField.java +++ b/server-spi/src/main/java/org/keycloak/storage/SearchableModelField.java @@ -40,31 +40,6 @@ public class SearchableModelField { return fieldClass; } - @Override - public int hashCode() { - int hash = 5; - hash = 83 * hash + Objects.hashCode(this.name); - return hash; - } - - @Override - public boolean equals(Object obj) { - if (this == obj) { - return true; - } - if (obj == null) { - return false; - } - if (getClass() != obj.getClass()) { - return false; - } - final SearchableModelField other = (SearchableModelField) obj; - if ( ! Objects.equals(this.name, other.name)) { - return false; - } - return true; - } - @Override public String toString() { return "SearchableModelField " + name + " @ " + getClass().getTypeParameters()[0].getTypeName();