diff --git a/federation/ldap/src/main/java/org/keycloak/storage/ldap/LDAPStorageProvider.java b/federation/ldap/src/main/java/org/keycloak/storage/ldap/LDAPStorageProvider.java index 45d34e572f..7e46763613 100755 --- a/federation/ldap/src/main/java/org/keycloak/storage/ldap/LDAPStorageProvider.java +++ b/federation/ldap/src/main/java/org/keycloak/storage/ldap/LDAPStorageProvider.java @@ -92,7 +92,7 @@ public class LDAPStorageProvider implements UserStorageProvider, CredentialAuthentication, UserLookupProvider, UserRegistrationProvider, - UserQueryProvider, + UserQueryProvider.Streams, ImportedUserValidation { private static final Logger logger = Logger.getLogger(LDAPStorageProvider.class); diff --git a/federation/ldap/src/main/java/org/keycloak/storage/ldap/ReadonlyLDAPUserModelDelegate.java b/federation/ldap/src/main/java/org/keycloak/storage/ldap/ReadonlyLDAPUserModelDelegate.java index b22645bf2b..23e404ed8b 100644 --- a/federation/ldap/src/main/java/org/keycloak/storage/ldap/ReadonlyLDAPUserModelDelegate.java +++ b/federation/ldap/src/main/java/org/keycloak/storage/ldap/ReadonlyLDAPUserModelDelegate.java @@ -32,7 +32,7 @@ import org.keycloak.storage.ReadOnlyException; * @author Bill Burke * @version $Revision: 1 $ */ -public class ReadonlyLDAPUserModelDelegate extends UserModelDelegate implements UserModel { +public class ReadonlyLDAPUserModelDelegate extends UserModelDelegate { public ReadonlyLDAPUserModelDelegate(UserModel delegate) { super(delegate); diff --git a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/GroupAdapter.java b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/GroupAdapter.java index df900dc157..71911f699c 100755 --- a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/GroupAdapter.java +++ b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/GroupAdapter.java @@ -36,7 +36,7 @@ import java.util.stream.Stream; * @author Bill Burke * @version $Revision: 1 $ */ -public class GroupAdapter implements GroupModel { +public class GroupAdapter implements GroupModel.Streams { protected final CachedGroup cached; protected final RealmCacheSession cacheSession; diff --git a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/UserAdapter.java b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/UserAdapter.java index 157397c875..de102c7c09 100755 --- a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/UserAdapter.java +++ b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/UserAdapter.java @@ -42,7 +42,7 @@ import java.util.stream.Stream; * @author Bill Burke * @version $Revision: 1 $ */ -public class UserAdapter implements CachedUserModel { +public class UserAdapter implements CachedUserModel.Streams { private final Supplier modelSupplier; protected final CachedUser cached; diff --git a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/UserCacheSession.java b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/UserCacheSession.java index 6a0c37e105..15338b9a51 100755 --- a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/UserCacheSession.java +++ b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/UserCacheSession.java @@ -71,7 +71,7 @@ import java.util.stream.Stream; * @author Bill Burke * @version $Revision: 1 $ */ -public class UserCacheSession implements UserCache { +public class UserCacheSession implements UserCache.Streams { protected static final Logger logger = Logger.getLogger(UserCacheSession.class); protected UserCacheManager cache; protected KeycloakSession session; diff --git a/model/jpa/src/main/java/org/keycloak/models/jpa/GroupAdapter.java b/model/jpa/src/main/java/org/keycloak/models/jpa/GroupAdapter.java index e69f66a88f..2ae1c828f5 100755 --- a/model/jpa/src/main/java/org/keycloak/models/jpa/GroupAdapter.java +++ b/model/jpa/src/main/java/org/keycloak/models/jpa/GroupAdapter.java @@ -31,12 +31,10 @@ import org.keycloak.models.utils.RoleUtils; import javax.persistence.EntityManager; import javax.persistence.TypedQuery; import java.util.ArrayList; -import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Objects; -import java.util.Set; import java.util.stream.Stream; import javax.persistence.LockModeType; @@ -46,7 +44,7 @@ import static org.keycloak.utils.StreamsUtil.closing; * @author Bill Burke * @version $Revision: 1 $ */ -public class GroupAdapter implements GroupModel , JpaModel { +public class GroupAdapter implements GroupModel.Streams , JpaModel { protected GroupEntity group; protected EntityManager em; diff --git a/model/jpa/src/main/java/org/keycloak/models/jpa/JpaUserProvider.java b/model/jpa/src/main/java/org/keycloak/models/jpa/JpaUserProvider.java index 348009b9d9..5224568fc0 100755 --- a/model/jpa/src/main/java/org/keycloak/models/jpa/JpaUserProvider.java +++ b/model/jpa/src/main/java/org/keycloak/models/jpa/JpaUserProvider.java @@ -82,7 +82,7 @@ import static org.keycloak.utils.StreamsUtil.closing; * @version $Revision: 1 $ */ @SuppressWarnings("JpaQueryApiInspection") -public class JpaUserProvider implements UserProvider, UserCredentialStore { +public class JpaUserProvider implements UserProvider.Streams, UserCredentialStore { private static final String EMAIL = "email"; private static final String EMAIL_VERIFIED = "emailVerified"; diff --git a/model/jpa/src/main/java/org/keycloak/models/jpa/UserAdapter.java b/model/jpa/src/main/java/org/keycloak/models/jpa/UserAdapter.java index 42d21d2d76..7d9089c2b9 100755 --- a/model/jpa/src/main/java/org/keycloak/models/jpa/UserAdapter.java +++ b/model/jpa/src/main/java/org/keycloak/models/jpa/UserAdapter.java @@ -53,7 +53,7 @@ import static org.keycloak.utils.StreamsUtil.closing; * @author Bill Burke * @version $Revision: 1 $ */ -public class UserAdapter implements UserModel, JpaModel { +public class UserAdapter implements UserModel.Streams, JpaModel { protected UserEntity user; protected EntityManager em; diff --git a/model/jpa/src/main/java/org/keycloak/storage/jpa/JpaUserFederatedStorageProvider.java b/model/jpa/src/main/java/org/keycloak/storage/jpa/JpaUserFederatedStorageProvider.java index ee4c4f5ca3..f065ab62c6 100644 --- a/model/jpa/src/main/java/org/keycloak/storage/jpa/JpaUserFederatedStorageProvider.java +++ b/model/jpa/src/main/java/org/keycloak/storage/jpa/JpaUserFederatedStorageProvider.java @@ -70,7 +70,7 @@ import static org.keycloak.utils.StreamsUtil.closing; * @version $Revision: 1 $ */ public class JpaUserFederatedStorageProvider implements - UserFederatedStorageProvider, + UserFederatedStorageProvider.Streams, UserCredentialStore { protected static final Logger logger = Logger.getLogger(JpaUserFederatedStorageProvider.class); diff --git a/model/map/src/main/java/org/keycloak/models/map/group/AbstractGroupModel.java b/model/map/src/main/java/org/keycloak/models/map/group/AbstractGroupModel.java index 30a345f96c..5cd77faf87 100644 --- a/model/map/src/main/java/org/keycloak/models/map/group/AbstractGroupModel.java +++ b/model/map/src/main/java/org/keycloak/models/map/group/AbstractGroupModel.java @@ -24,7 +24,7 @@ import org.keycloak.models.map.common.AbstractEntity; import java.util.Objects; -public abstract class AbstractGroupModel implements GroupModel { +public abstract class AbstractGroupModel implements GroupModel.Streams { protected final KeycloakSession session; protected final RealmModel realm; diff --git a/server-spi-private/src/main/java/org/keycloak/storage/adapter/InMemoryUserAdapter.java b/server-spi-private/src/main/java/org/keycloak/storage/adapter/InMemoryUserAdapter.java index 0d1471e5a2..50d5bfd0c8 100644 --- a/server-spi-private/src/main/java/org/keycloak/storage/adapter/InMemoryUserAdapter.java +++ b/server-spi-private/src/main/java/org/keycloak/storage/adapter/InMemoryUserAdapter.java @@ -31,7 +31,6 @@ import org.keycloak.models.utils.RoleUtils; import org.keycloak.storage.ReadOnlyException; import java.util.HashSet; -import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Set; @@ -41,7 +40,7 @@ import java.util.stream.Stream; * @author Bill Burke * @version $Revision: 1 $ */ -public class InMemoryUserAdapter extends UserModelDefaultMethods { +public class InMemoryUserAdapter extends UserModelDefaultMethods.Streams { private Long createdTimestamp = Time.currentTimeMillis(); private boolean emailVerified; private boolean enabled; diff --git a/server-spi/src/main/java/org/keycloak/models/GroupModel.java b/server-spi/src/main/java/org/keycloak/models/GroupModel.java index c7971a9a1d..cd2e680a3c 100755 --- a/server-spi/src/main/java/org/keycloak/models/GroupModel.java +++ b/server-spi/src/main/java/org/keycloak/models/GroupModel.java @@ -68,11 +68,12 @@ public interface GroupModel extends RoleMapperModel { * @return list of all attribute values or empty list if there are not any values. Never return null */ @Deprecated - default List getAttribute(String name) { - return getAttributeStream(name).collect(Collectors.toList()); - } + List getAttribute(String name); - Stream getAttributeStream(String name); + default Stream getAttributeStream(String name) { + List value = this.getAttribute(name); + return value != null ? value.stream() : Stream.empty(); + } Map> getAttributes(); @@ -80,11 +81,12 @@ public interface GroupModel extends RoleMapperModel { String getParentId(); @Deprecated - default Set getSubGroups() { - return getSubGroupsStream().collect(Collectors.toSet()); - } + Set getSubGroups(); - Stream getSubGroupsStream(); + default Stream getSubGroupsStream() { + Set value = this.getSubGroups(); + return value != null ? value.stream() : Stream.empty(); + } /** * You must also call addChild on the parent group, addChild on RealmModel if there is no parent group @@ -106,4 +108,29 @@ public interface GroupModel extends RoleMapperModel { * @param subGroup */ void removeChild(GroupModel subGroup); + + /** + * The {@link GroupModel.Streams} interface makes all collection-based methods in {@link GroupModel} default by providing + * implementations that delegate to the {@link Stream}-based variants instead of the other way around. + *

+ * It allows for implementations to focus on the {@link Stream}-based approach for processing sets of data and benefit + * from the potential memory and performance optimizations of that approach. + */ + interface Streams extends GroupModel, RoleMapperModel.Streams { + @Override + default List getAttribute(String name) { + return this.getAttributeStream(name).collect(Collectors.toList()); + } + + @Override + Stream getAttributeStream(String name); + + @Override + default Set getSubGroups() { + return this.getSubGroupsStream().collect(Collectors.toSet()); + } + + @Override + Stream getSubGroupsStream(); + } } diff --git a/server-spi/src/main/java/org/keycloak/models/RoleMapperModel.java b/server-spi/src/main/java/org/keycloak/models/RoleMapperModel.java index bf049d7291..fac9709041 100755 --- a/server-spi/src/main/java/org/keycloak/models/RoleMapperModel.java +++ b/server-spi/src/main/java/org/keycloak/models/RoleMapperModel.java @@ -32,15 +32,16 @@ public interface RoleMapperModel { * @deprecated Use {@link #getRealmRoleMappingsStream()} getRealmRoleMappingsStream} instead. */ @Deprecated - default Set getRealmRoleMappings() { - return getRealmRoleMappingsStream().collect(Collectors.toSet()); - } + Set getRealmRoleMappings(); /** * Returns stream of realm roles that are directly set to this object. * @return stream of {@link RoleModel} */ - Stream getRealmRoleMappingsStream(); + default Stream getRealmRoleMappingsStream() { + Set value = this.getRealmRoleMappings(); + return value != null ? value.stream() : Stream.empty(); + } /** * Returns set of client roles that are directly set to this object for the given client. @@ -49,16 +50,17 @@ public interface RoleMapperModel { * @deprecated Use {@link #getClientRoleMappingsStream(ClientModel)} getClientRoleMappingsStream} instead. */ @Deprecated - default Set getClientRoleMappings(ClientModel app) { - return getClientRoleMappingsStream(app).collect(Collectors.toSet()); - } + Set getClientRoleMappings(ClientModel app); /** * Returns stream of client roles that are directly set to this object for the given client. * @param app Client to get the roles for * @return stream of {@link RoleModel} */ - Stream getClientRoleMappingsStream(ClientModel app); + default Stream getClientRoleMappingsStream(ClientModel app) { + Set value = this.getClientRoleMappings(app); + return value != null ? value.stream() : Stream.empty(); + } /** * Returns {@code true} if this object is directly or indirectly assigned the given role, {@code false} otherwise. @@ -86,19 +88,53 @@ public interface RoleMapperModel { * @deprecated Use {@link #getRoleMappingsStream()} getRoleMappingsStream} instead. */ @Deprecated - default Set getRoleMappings() { - return getRoleMappingsStream().collect(Collectors.toSet()); - } + Set getRoleMappings(); /** * Returns stream of all role (both realm all client) that are directly set to this object. * @return stream of {@link RoleModel} */ - Stream getRoleMappingsStream(); + default Stream getRoleMappingsStream() { + Set value = this.getRoleMappings(); + return value != null ? value.stream() : Stream.empty(); + } /** * Removes the given role mapping from this object. * @param role Role to remove */ void deleteRoleMapping(RoleModel role); + + /** + * The {@link Streams} interface makes all collection-based methods in {@link RoleMapperModel} default by providing + * implementations that delegate to the {@link Stream}-based variants instead of the other way around. + *

+ * It allows for implementations to focus on the {@link Stream}-based approach for processing sets of data and benefit + * from the potential memory and performance optimizations of that approach. + */ + interface Streams extends RoleMapperModel { + @Override + default Set getRealmRoleMappings() { + return this.getRealmRoleMappingsStream().collect(Collectors.toSet()); + } + + @Override + Stream getRealmRoleMappingsStream(); + + @Override + default Set getClientRoleMappings(ClientModel app) { + return this.getClientRoleMappingsStream(app).collect(Collectors.toSet()); + } + + @Override + Stream getClientRoleMappingsStream(ClientModel app); + + @Override + default Set getRoleMappings() { + return this.getRoleMappingsStream().collect(Collectors.toSet()); + } + + @Override + Stream getRoleMappingsStream(); + } } diff --git a/server-spi/src/main/java/org/keycloak/models/UserModel.java b/server-spi/src/main/java/org/keycloak/models/UserModel.java index 99738d036d..35f1b15f57 100755 --- a/server-spi/src/main/java/org/keycloak/models/UserModel.java +++ b/server-spi/src/main/java/org/keycloak/models/UserModel.java @@ -94,17 +94,18 @@ public interface UserModel extends RoleMapperModel { * @deprecated Use {@link #getAttributeStream(String) getAttributeStream} instead. */ @Deprecated - default List getAttribute(String name) { - return this.getAttributeStream(name).collect(Collectors.toList()); - } + List getAttribute(String name); /** * Obtains all values associated with the specified attribute name. * * @param name the name of the attribute. - * @return a non-null {@code Stream} of attribute values. + * @return a non-null {@link Stream} of attribute values. */ - Stream getAttributeStream(final String name); + default Stream getAttributeStream(final String name) { + List value = this.getAttribute(name); + return value != null ? value.stream() : Stream.empty(); + } Map> getAttributes(); @@ -112,16 +113,17 @@ public interface UserModel extends RoleMapperModel { * @deprecated Use {@link #getRequiredActionsStream() getRequiredActionsStream} instead. */ @Deprecated - default Set getRequiredActions() { - return this.getRequiredActionsStream().collect(Collectors.toSet()); - } + Set getRequiredActions(); /** * Obtains the names of required actions associated with the user. * - * @return a non-null {@code Stream} of required action names. + * @return a non-null {@link Stream} of required action names. */ - Stream getRequiredActionsStream(); + default Stream getRequiredActionsStream() { + Set value = this.getRequiredActions(); + return value != null ? value.stream() : Stream.empty(); + } void addRequiredAction(String action); @@ -151,16 +153,17 @@ public interface UserModel extends RoleMapperModel { * @deprecated Use {@link #getGroupsStream() getGroupsStream} instead. */ @Deprecated - default Set getGroups() { - return getGroupsStream().collect(Collectors.toSet()); - } + Set getGroups(); /** * Obtains the groups associated with the user. * - * @return a non-null {@code Stream} of groups. + * @return a non-null {@link Stream} of groups. */ - Stream getGroupsStream(); + default Stream getGroupsStream() { + Set value = this.getGroups(); + return value != null ? value.stream() : Stream.empty(); + } /** * @deprecated Use {@link #getGroupsStream(String, Integer, Integer) getGroupsStream} instead. @@ -230,4 +233,37 @@ public interface UserModel extends RoleMapperModel { enum RequiredAction { VERIFY_EMAIL, UPDATE_PROFILE, CONFIGURE_TOTP, UPDATE_PASSWORD, TERMS_AND_CONDITIONS } + + /** + * The {@link UserModel.Streams} interface makes all collection-based methods in {@link UserModel} default by providing + * implementations that delegate to the {@link Stream}-based variants instead of the other way around. + *

+ * It allows for implementations to focus on the {@link Stream}-based approach for processing sets of data and benefit + * from the potential memory and performance optimizations of that approach. + */ + interface Streams extends UserModel, RoleMapperModel.Streams { + @Override + default List getAttribute(String name) { + return this.getAttributeStream(name).collect(Collectors.toList()); + } + + @Override + Stream getAttributeStream(final String name); + + @Override + default Set getRequiredActions() { + return this.getRequiredActionsStream().collect(Collectors.toSet()); + } + + @Override + Stream getRequiredActionsStream(); + + @Override + default Set getGroups() { + return this.getGroupsStream().collect(Collectors.toSet()); + } + + @Override + Stream getGroupsStream(); + } } diff --git a/server-spi/src/main/java/org/keycloak/models/UserModelDefaultMethods.java b/server-spi/src/main/java/org/keycloak/models/UserModelDefaultMethods.java index fc9f6b9e40..179a4030bb 100644 --- a/server-spi/src/main/java/org/keycloak/models/UserModelDefaultMethods.java +++ b/server-spi/src/main/java/org/keycloak/models/UserModelDefaultMethods.java @@ -17,6 +17,8 @@ package org.keycloak.models; +import java.util.stream.Stream; + /** * @author Martin Idel * @version $Revision: 1 $ @@ -53,4 +55,13 @@ public abstract class UserModelDefaultMethods implements UserModel { email = email == null ? null : email.toLowerCase(); setSingleAttribute(EMAIL, email); } + + /** + * The {@link UserModelDefaultMethods.Streams} class extends the {@link UserModelDefaultMethods} abstract class and + * implements the {@link UserModel.Streams} interface, allowing subclasses to focus on the implementation of the + * {@link Stream}-based query methods and providing default implementations for the collections-based variants that + * delegate to their {@link Stream} counterparts. + */ + public abstract static class Streams extends UserModelDefaultMethods implements UserModel.Streams { + } } diff --git a/server-spi/src/main/java/org/keycloak/models/UserProvider.java b/server-spi/src/main/java/org/keycloak/models/UserProvider.java index a67fc4ac99..0e3685e713 100755 --- a/server-spi/src/main/java/org/keycloak/models/UserProvider.java +++ b/server-spi/src/main/java/org/keycloak/models/UserProvider.java @@ -49,18 +49,19 @@ public interface UserProvider extends Provider, * @deprecated Use {@link #getFederatedIdentitiesStream(UserModel, RealmModel) getFederatedIdentitiesStream} instead. */ @Deprecated - default Set getFederatedIdentities(UserModel user, RealmModel realm) { - return this.getFederatedIdentitiesStream(user, realm).collect(Collectors.toSet()); - } + Set getFederatedIdentities(UserModel user, RealmModel realm); /** * Obtains the federated identities of the specified user. * * @param user a reference to the user. * @param realm a reference to the realm. - * @return a non-null {@code Stream} of federated identities associated with the user. + * @return a non-null {@link Stream} of federated identities associated with the user. */ - Stream getFederatedIdentitiesStream(UserModel user, RealmModel realm); + default Stream getFederatedIdentitiesStream(UserModel user, RealmModel realm) { + Set value = this.getFederatedIdentities(user, realm); + return value != null ? value.stream() : Stream.empty(); + } FederatedIdentityModel getFederatedIdentity(UserModel user, String socialProvider, RealmModel realm); UserModel getUserByFederatedIdentity(FederatedIdentityModel socialLink, RealmModel realm); @@ -72,18 +73,19 @@ public interface UserProvider extends Provider, * @deprecated Use {@link #getConsentsStream(RealmModel, String) getConsentsStream} instead. */ @Deprecated - default List getConsents(RealmModel realm, String userId) { - return getConsentsStream(realm, userId).collect(Collectors.toList()); - } + List getConsents(RealmModel realm, String userId); /** * Obtains the consents associated with the user identified by the specified {@code userId}. * * @param realm a reference to the realm. * @param userId the user identifier. - * @return a non-null {@code Stream} of consents associated with the user. + * @return a non-null {@link Stream} of consents associated with the user. */ - Stream getConsentsStream(RealmModel realm, String userId); + default Stream getConsentsStream(RealmModel realm, String userId) { + List value = this.getConsents(realm, userId); + return value != null ? value.stream() : Stream.empty(); + } void updateConsent(RealmModel realm, String userId, UserConsentModel consent); boolean revokeConsentForClient(RealmModel realm, String userId, String clientInternalId); @@ -97,26 +99,25 @@ public interface UserProvider extends Provider, * @deprecated Use {@link #getUsersStream(RealmModel, boolean) getUsersStream} instead. */ @Deprecated - default List getUsers(RealmModel realm, boolean includeServiceAccounts) { - return this.getUsersStream(realm, includeServiceAccounts).collect(Collectors.toList()); - } + List getUsers(RealmModel realm, boolean includeServiceAccounts); /** * Obtains the users associated with the specified realm. * * @param realm a reference to the realm being used for the search. * @param includeServiceAccounts {@code true} if service accounts should be included in the result; {@code false} otherwise. - * @return a non-null {@code Stream} of users associated withe the realm. + * @return a non-null {@link Stream} of users associated withe the realm. */ - Stream getUsersStream(RealmModel realm, boolean includeServiceAccounts); + default Stream getUsersStream(RealmModel realm, boolean includeServiceAccounts) { + List value = this.getUsers(realm, includeServiceAccounts); + return value != null ? value.stream() : Stream.empty(); + } /** * @deprecated Use {@link #getUsersStream(RealmModel, int, int, boolean) getUsersStream} instead. */ @Deprecated - default List getUsers(RealmModel realm, int firstResult, int maxResults, boolean includeServiceAccounts) { - return this.getUsersStream(realm, firstResult, maxResults, includeServiceAccounts).collect(Collectors.toList()); - } + List getUsers(RealmModel realm, int firstResult, int maxResults, boolean includeServiceAccounts); /** * Obtains the users associated with the specified realm. @@ -125,9 +126,12 @@ public interface UserProvider extends Provider, * @param firstResult first result to return. Ignored if negative. * @param maxResults maximum number of results to return. Ignored if negative. * @param includeServiceAccounts {@code true} if service accounts should be included in the result; {@code false} otherwise. - * @return a non-null {@code Stream} of users associated withe the realm. + * @return a non-null {@link Stream} of users associated withe the realm. */ - Stream getUsersStream(RealmModel realm, int firstResult, int maxResults, boolean includeServiceAccounts); + default Stream getUsersStream(RealmModel realm, int firstResult, int maxResults, boolean includeServiceAccounts) { + List value = this.getUsers(realm, firstResult, maxResults, includeServiceAccounts); + return value != null ? value.stream() : Stream.empty(); + } /** * only used for local storage @@ -168,4 +172,38 @@ public interface UserProvider extends Provider, void close(); void preRemove(RealmModel realm, ComponentModel component); + + interface Streams extends UserProvider, UserQueryProvider.Streams { + @Override + default Set getFederatedIdentities(UserModel user, RealmModel realm) { + return this.getFederatedIdentitiesStream(user, realm).collect(Collectors.toSet()); + } + + @Override + Stream getFederatedIdentitiesStream(UserModel user, RealmModel realm); + + @Override + default List getConsents(RealmModel realm, String userId) { + return this.getConsentsStream(realm, userId).collect(Collectors.toList()); + } + + @Override + Stream getConsentsStream(RealmModel realm, String userId); + + @Override + default List getUsers(RealmModel realm, boolean includeServiceAccounts) { + return this.getUsersStream(realm, includeServiceAccounts).collect(Collectors.toList()); + } + + @Override + Stream getUsersStream(RealmModel realm, boolean includeServiceAccounts); + + @Override + default List getUsers(RealmModel realm, int firstResult, int maxResults, boolean includeServiceAccounts) { + return this.getUsersStream(realm, firstResult, maxResults, includeServiceAccounts).collect(Collectors.toList()); + } + + @Override + Stream getUsersStream(RealmModel realm, int firstResult, int maxResults, boolean includeServiceAccounts); + } } diff --git a/server-spi/src/main/java/org/keycloak/models/cache/CachedUserModel.java b/server-spi/src/main/java/org/keycloak/models/cache/CachedUserModel.java index 1d3f59f289..268c816297 100644 --- a/server-spi/src/main/java/org/keycloak/models/cache/CachedUserModel.java +++ b/server-spi/src/main/java/org/keycloak/models/cache/CachedUserModel.java @@ -17,6 +17,7 @@ package org.keycloak.models.cache; import org.keycloak.models.UserModel; +import org.keycloak.models.UserProvider; import java.util.concurrent.ConcurrentMap; @@ -56,4 +57,12 @@ public interface CachedUserModel extends UserModel { * @return */ ConcurrentMap getCachedWith(); + + /** + * The {@link CachedUserModel.Streams} interface differs from {@link CachedUserModel} in that it extends the + * {@link UserModel.Streams} interface, allowing implementations of {@link CachedUserModel} to focus on the + * {@link java.util.stream.Stream}-based methods in the {@link UserModel} interface. + */ + interface Streams extends CachedUserModel, UserModel.Streams { + } } diff --git a/server-spi/src/main/java/org/keycloak/models/cache/UserCache.java b/server-spi/src/main/java/org/keycloak/models/cache/UserCache.java index ab7080817f..8d1b1343f3 100755 --- a/server-spi/src/main/java/org/keycloak/models/cache/UserCache.java +++ b/server-spi/src/main/java/org/keycloak/models/cache/UserCache.java @@ -47,4 +47,12 @@ public interface UserCache extends UserProvider { * */ void clear(); + + /** + * The {@link UserCache.Streams} interface differs from {@link UserCache} in that it extends the {@link UserProvider.Streams} + * interface, allowing implementations of {@link UserCache} to focus on the {@link java.util.stream.Stream}-based methods + * in the {@link UserProvider} interface. + */ + interface Streams extends UserCache, UserProvider.Streams { + } } diff --git a/server-spi/src/main/java/org/keycloak/models/utils/UserModelDelegate.java b/server-spi/src/main/java/org/keycloak/models/utils/UserModelDelegate.java index 19fc110b5f..3acca8400b 100755 --- a/server-spi/src/main/java/org/keycloak/models/utils/UserModelDelegate.java +++ b/server-spi/src/main/java/org/keycloak/models/utils/UserModelDelegate.java @@ -33,7 +33,7 @@ import java.util.stream.Stream; * @author Bill Burke * @version $Revision: 1 $ */ -public class UserModelDelegate implements UserModel { +public class UserModelDelegate implements UserModel.Streams { protected UserModel delegate; public UserModelDelegate(UserModel delegate) { diff --git a/server-spi/src/main/java/org/keycloak/storage/adapter/AbstractUserAdapter.java b/server-spi/src/main/java/org/keycloak/storage/adapter/AbstractUserAdapter.java index 511e99a4cc..d047904fef 100644 --- a/server-spi/src/main/java/org/keycloak/storage/adapter/AbstractUserAdapter.java +++ b/server-spi/src/main/java/org/keycloak/storage/adapter/AbstractUserAdapter.java @@ -30,8 +30,12 @@ import org.keycloak.models.utils.RoleUtils; import org.keycloak.storage.ReadOnlyException; import org.keycloak.storage.StorageId; +import java.util.Collections; +import java.util.HashSet; import java.util.List; import java.util.Map; +import java.util.Set; +import java.util.stream.Collectors; import java.util.stream.Stream; /** @@ -59,8 +63,8 @@ public abstract class AbstractUserAdapter extends UserModelDefaultMethods { } @Override - public Stream getRequiredActionsStream() { - return Stream.empty(); + public Set getRequiredActions() { + return Collections.emptySet(); } @Override @@ -91,8 +95,8 @@ public abstract class AbstractUserAdapter extends UserModelDefaultMethods { * * @return */ - protected Stream getGroupsInternal() { - return Stream.empty(); + protected Set getGroupsInternal() { + return Collections.emptySet(); } /** @@ -107,10 +111,11 @@ public abstract class AbstractUserAdapter extends UserModelDefaultMethods { } @Override - public Stream getGroupsStream() { - Stream groups = getGroupsInternal(); - if (appendDefaultGroups()) groups = Stream.concat(groups, realm.getDefaultGroupsStream()); - return groups; + public Set getGroups() { + Set set = new HashSet<>(); + if (appendDefaultGroups()) set.addAll(realm.getDefaultGroupsStream().collect(Collectors.toSet())); + set.addAll(getGroupsInternal()); + return set; } @Override @@ -127,23 +132,23 @@ public abstract class AbstractUserAdapter extends UserModelDefaultMethods { @Override public boolean isMemberOf(GroupModel group) { - return RoleUtils.isMember(getGroupsStream(), group); + return RoleUtils.isMember(getGroups().stream(), group); } @Override - public Stream getRealmRoleMappingsStream() { - return getRoleMappingsStream().filter(RoleUtils::isRealmRole); + public Set getRealmRoleMappings() { + return getRoleMappings().stream().filter(RoleUtils::isRealmRole).collect(Collectors.toSet()); } @Override - public Stream getClientRoleMappingsStream(ClientModel app) { - return getRoleMappingsStream().filter(r -> RoleUtils.isClientRole(r, app)); + public Set getClientRoleMappings(ClientModel app) { + return getRoleMappings().stream().filter(r -> RoleUtils.isClientRole(r, app)).collect(Collectors.toSet()); } @Override public boolean hasRole(RoleModel role) { - return RoleUtils.hasRole(getRoleMappingsStream(), role) - || RoleUtils.hasRoleFromGroup(getGroupsStream(), role, true); + return RoleUtils.hasRole(getRoleMappings().stream(), role) + || RoleUtils.hasRoleFromGroup(getGroups().stream(), role, true); } @Override @@ -163,15 +168,16 @@ public abstract class AbstractUserAdapter extends UserModelDefaultMethods { return true; } - protected Stream getRoleMappingsInternal() { - return Stream.empty(); + protected Set getRoleMappingsInternal() { + return Collections.emptySet(); } @Override - public Stream getRoleMappingsStream() { - Stream roleMappings = getRoleMappingsInternal(); - if (appendDefaultRolesToRoleMappings()) return Stream.concat(roleMappings, DefaultRoles.getDefaultRoles(realm)); - return roleMappings; + public Set getRoleMappings() { + Set set = new HashSet<>(); + if (appendDefaultRolesToRoleMappings()) set.addAll(DefaultRoles.getDefaultRoles(realm).collect(Collectors.toSet())); + set.addAll(getRoleMappingsInternal()); + return set; } @@ -300,11 +306,11 @@ public abstract class AbstractUserAdapter extends UserModelDefaultMethods { } @Override - public Stream getAttributeStream(String name) { + public List getAttribute(String name) { if (name.equals(UserModel.USERNAME)) { - return Stream.of(getUsername()); + return Collections.singletonList(getUsername()); } - return Stream.empty(); + return Collections.emptyList(); } @Override @@ -365,4 +371,100 @@ public abstract class AbstractUserAdapter extends UserModelDefaultMethods { return getId().hashCode(); } + /** + * The {@link AbstractUserAdapter.Streams} class extends the {@link AbstractUserAdapter} abstract class and implements + * the {@link UserModel.Streams} interface, allowing subclasses to focus on the implementation of the {@link Stream}-based + * query methods and providing default implementations for the collections-based variants that delegate to their + * {@link Stream} counterparts. + */ + public abstract static class Streams extends AbstractUserAdapter implements UserModel.Streams { + + public Streams(final KeycloakSession session, final RealmModel realm, final ComponentModel storageProviderModel) { + super(session, realm, storageProviderModel); + } + + @Override + public Set getRequiredActions() { + return this.getRequiredActionsStream().collect(Collectors.toSet()); + } + + @Override + public Stream getRequiredActionsStream() { + return Stream.empty(); + } + + @Override + public List getAttribute(String name) { + return this.getAttributeStream(name).collect(Collectors.toList()); + } + + @Override + public Stream getAttributeStream(String name) { + if (name.equals(UserModel.USERNAME)) { + return Stream.of(getUsername()); + } + return Stream.empty(); + } + + // group-related methods. + + + @Override + public Set getGroups() { + return this.getGroupsStream().collect(Collectors.toSet()); + } + + @Override + public Stream getGroupsStream() { + Stream groups = getGroupsInternal().stream(); + if (appendDefaultGroups()) groups = Stream.concat(groups, realm.getDefaultGroupsStream()); + return groups; + } + + @Override + public boolean isMemberOf(GroupModel group) { + return RoleUtils.isMember(this.getGroupsStream(), group); + } + + // role-related methods. + + + @Override + public Set getRealmRoleMappings() { + return this.getRealmRoleMappingsStream().collect(Collectors.toSet()); + } + + @Override + public Stream getRealmRoleMappingsStream() { + return getRoleMappingsStream().filter(RoleUtils::isRealmRole); + } + + @Override + public Set getClientRoleMappings(ClientModel app) { + return this.getClientRoleMappingsStream(app).collect(Collectors.toSet()); + } + + @Override + public Stream getClientRoleMappingsStream(ClientModel app) { + return getRoleMappingsStream().filter(r -> RoleUtils.isClientRole(r, app)); + } + + @Override + public Set getRoleMappings() { + return this.getRoleMappingsStream().collect(Collectors.toSet()); + } + + @Override + public Stream getRoleMappingsStream() { + Stream roleMappings = getRoleMappingsInternal().stream(); + if (appendDefaultRolesToRoleMappings()) return Stream.concat(roleMappings, DefaultRoles.getDefaultRoles(realm)); + return roleMappings; + } + + @Override + public boolean hasRole(RoleModel role) { + return RoleUtils.hasRole(this.getRoleMappingsStream(), role) + || RoleUtils.hasRoleFromGroup(this.getGroupsStream(), role, true); + } + } } diff --git a/server-spi/src/main/java/org/keycloak/storage/adapter/AbstractUserAdapterFederatedStorage.java b/server-spi/src/main/java/org/keycloak/storage/adapter/AbstractUserAdapterFederatedStorage.java index f48b94a9a7..c420b70b0f 100644 --- a/server-spi/src/main/java/org/keycloak/storage/adapter/AbstractUserAdapterFederatedStorage.java +++ b/server-spi/src/main/java/org/keycloak/storage/adapter/AbstractUserAdapterFederatedStorage.java @@ -30,8 +30,12 @@ import org.keycloak.models.utils.RoleUtils; import org.keycloak.storage.StorageId; import org.keycloak.storage.federated.UserFederatedStorageProvider; +import java.util.Collections; +import java.util.HashSet; import java.util.List; import java.util.Map; +import java.util.Set; +import java.util.stream.Collectors; import java.util.stream.Stream; /** @@ -68,8 +72,8 @@ public abstract class AbstractUserAdapterFederatedStorage extends UserModelDefau } @Override - public Stream getRequiredActionsStream() { - return getFederatedStorage().getRequiredActionsStream(realm, this.getId()); + public Set getRequiredActions() { + return getFederatedStorage().getRequiredActions(realm, this.getId()); } @Override @@ -100,8 +104,8 @@ public abstract class AbstractUserAdapterFederatedStorage extends UserModelDefau * * @return */ - protected Stream getGroupsInternal() { - return Stream.empty(); + protected Set getGroupsInternal() { + return Collections.emptySet(); } /** @@ -124,10 +128,11 @@ public abstract class AbstractUserAdapterFederatedStorage extends UserModelDefau * @return */ @Override - public Stream getGroupsStream() { - Stream groups = getFederatedStorage().getGroupsStream(realm, this.getId()); - if (appendDefaultGroups()) groups = Stream.concat(groups, realm.getDefaultGroupsStream()); - return Stream.concat(groups, getGroupsInternal()); + public Set getGroups() { + Set set = new HashSet<>(getFederatedStorage().getGroups(realm, this.getId())); + if (appendDefaultGroups()) set.addAll(realm.getDefaultGroupsStream().collect(Collectors.toSet())); + set.addAll(getGroupsInternal()); + return set; } @Override @@ -144,7 +149,7 @@ public abstract class AbstractUserAdapterFederatedStorage extends UserModelDefau @Override public boolean isMemberOf(GroupModel group) { - return RoleUtils.isMember(getGroupsStream(), group); + return RoleUtils.isMember(getGroups().stream(), group); } /** @@ -156,8 +161,8 @@ public abstract class AbstractUserAdapterFederatedStorage extends UserModelDefau * @return */ @Override - public Stream getRealmRoleMappingsStream() { - return getRoleMappingsStream().filter(RoleUtils::isRealmRole); + public Set getRealmRoleMappings() { + return this.getRoleMappings().stream().filter(RoleUtils::isRealmRole).collect(Collectors.toSet()); } /** @@ -169,14 +174,15 @@ public abstract class AbstractUserAdapterFederatedStorage extends UserModelDefau * @return */ @Override - public Stream getClientRoleMappingsStream(ClientModel app) { - return getRoleMappingsStream().filter(r -> RoleUtils.isClientRole(r, app)); + public Set getClientRoleMappings(ClientModel app) { + return getRoleMappings().stream().filter(r -> RoleUtils.isClientRole(r, app)).collect(Collectors.toSet()); } + @Override public boolean hasRole(RoleModel role) { - return RoleUtils.hasRole(getRoleMappingsStream(), role) - || RoleUtils.hasRoleFromGroup(getGroupsStream(), role, true); + return RoleUtils.hasRole(getRoleMappings().stream(), role) + || RoleUtils.hasRoleFromGroup(getGroups().stream(), role, true); } @Override @@ -196,8 +202,8 @@ public abstract class AbstractUserAdapterFederatedStorage extends UserModelDefau return true; } - protected Stream getRoleMappingsInternal() { - return Stream.empty(); + protected Set getRoleMappingsInternal() { + return Collections.emptySet(); } /** @@ -208,14 +214,15 @@ public abstract class AbstractUserAdapterFederatedStorage extends UserModelDefau * @return */ @Override - public Stream getRoleMappingsStream() { - Stream roleMappings = getFederatedRoleMappings(); - if (appendDefaultRolesToRoleMappings()) roleMappings = Stream.concat(roleMappings, DefaultRoles.getDefaultRoles(realm)); - return Stream.concat(roleMappings, getRoleMappingsInternal()); + public Set getRoleMappings() { + Set set = new HashSet<>(getFederatedRoleMappings()); + if (appendDefaultRolesToRoleMappings()) set.addAll(DefaultRoles.getDefaultRoles(realm).collect(Collectors.toSet())); + set.addAll(getRoleMappingsInternal()); + return set; } - protected Stream getFederatedRoleMappings() { - return getFederatedStorage().getRoleMappingsStream(realm, this.getId()); + protected Set getFederatedRoleMappings() { + return getFederatedStorage().getRoleMappings(realm, this.getId()); } @Override @@ -357,15 +364,15 @@ public abstract class AbstractUserAdapterFederatedStorage extends UserModelDefau } @Override - public Stream getAttributeStream(String name) { + public List getAttribute(String name) { if (UserModel.USERNAME.equals(name)) { - return Stream.of(getUsername()); + return Collections.singletonList(getUsername()); } List result = getFederatedStorage().getAttributes(realm, this.getId()).get(mapAttribute(name)); - return (result == null) ? Stream.empty() : result.stream(); + return (result == null) ? Collections.emptyList() : result; } - private String mapAttribute(String attributeName) { + protected String mapAttribute(String attributeName) { if (UserModel.FIRST_NAME.equals(attributeName)) { return FIRST_NAME_ATTRIBUTE; } else if (UserModel.LAST_NAME.equals(attributeName)) { @@ -409,4 +416,101 @@ public abstract class AbstractUserAdapterFederatedStorage extends UserModelDefau return getId().hashCode(); } + /** + * The {@link AbstractUserAdapterFederatedStorage.Streams} class extends the {@link AbstractUserAdapterFederatedStorage} + * abstract class and implements the {@link UserModel.Streams} interface, allowing subclasses to focus on the implementation + * of the {@link Stream}-based query methods and providing default implementations for the collections-based variants + * that delegate to their {@link Stream} counterparts. + */ + public abstract static class Streams extends AbstractUserAdapterFederatedStorage implements UserModel.Streams { + + public Streams(final KeycloakSession session, final RealmModel realm, final ComponentModel storageProviderModel) { + super(session, realm, storageProviderModel); + } + + // user-related methods. + + @Override + public Set getRequiredActions() { + return this.getRequiredActionsStream().collect(Collectors.toSet()); + } + + @Override + public Stream getRequiredActionsStream() { + return super.getFederatedStorage().getRequiredActionsStream(super.realm, super.getId()); + } + + @Override + public List getAttribute(String name) { + return this.getAttributeStream(name).collect(Collectors.toList()); + } + + @Override + public Stream getAttributeStream(String name) { + if (UserModel.USERNAME.equals(name)) { + return Stream.of(getUsername()); + } + List result = super.getFederatedStorage().getAttributes(realm, this.getId()).get(super.mapAttribute(name)); + return (result == null) ? Stream.empty() : result.stream(); + } + + // group-related methods. + + @Override + public Set getGroups() { + return this.getGroupsStream().collect(Collectors.toSet()); + } + + @Override + public Stream getGroupsStream() { + Stream groups = getFederatedStorage().getGroupsStream(realm, this.getId()); + if (appendDefaultGroups()) groups = Stream.concat(groups, realm.getDefaultGroupsStream()); + return Stream.concat(groups, getGroupsInternal().stream()); + } + + @Override + public boolean isMemberOf(GroupModel group) { + return RoleUtils.isMember(this.getGroupsStream(), group); + } + + // role-related methods. + + @Override + public Set getRealmRoleMappings() { + return this.getRealmRoleMappingsStream().collect(Collectors.toSet()); + } + + @Override + public Stream getRealmRoleMappingsStream() { + return getRoleMappingsStream().filter(RoleUtils::isRealmRole); + } + + @Override + public Set getClientRoleMappings(ClientModel app) { + return this.getClientRoleMappingsStream(app).collect(Collectors.toSet()); + } + + @Override + public Stream getClientRoleMappingsStream(ClientModel app) { + return getRoleMappingsStream().filter(r -> RoleUtils.isClientRole(r, app)); + } + + @Override + public Set getRoleMappings() { + return this.getRoleMappingsStream().collect(Collectors.toSet()); + } + + @Override + public Stream getRoleMappingsStream() { + Stream roleMappings = getFederatedRoleMappings().stream(); + if (appendDefaultRolesToRoleMappings()) roleMappings = Stream.concat(roleMappings, DefaultRoles.getDefaultRoles(realm)); + return Stream.concat(roleMappings, getRoleMappingsInternal().stream()); + } + + @Override + public boolean hasRole(RoleModel role) { + return RoleUtils.hasRole(this.getRoleMappingsStream(), role) + || RoleUtils.hasRoleFromGroup(this.getGroupsStream(), role, true); + } + } } diff --git a/server-spi/src/main/java/org/keycloak/storage/federated/UserAttributeFederatedStorage.java b/server-spi/src/main/java/org/keycloak/storage/federated/UserAttributeFederatedStorage.java index b1919bc525..c27ca677b8 100644 --- a/server-spi/src/main/java/org/keycloak/storage/federated/UserAttributeFederatedStorage.java +++ b/server-spi/src/main/java/org/keycloak/storage/federated/UserAttributeFederatedStorage.java @@ -37,9 +37,7 @@ public interface UserAttributeFederatedStorage { * @deprecated Use {@link #getUsersByUserAttributeStream(RealmModel, String, String) getUsersByUserAttributeStream} instead. */ @Deprecated - default List getUsersByUserAttribute(RealmModel realm, String name, String value) { - return this.getUsersByUserAttributeStream(realm, name, value).collect(Collectors.toList()); - } + List getUsersByUserAttribute(RealmModel realm, String name, String value); /** * Searches for federated users that have an attribute with the specified {@code name} and {@code value}. @@ -47,7 +45,29 @@ public interface UserAttributeFederatedStorage { * @param realm a reference to the realm. * @param name the attribute name. * @param value the attribute value. - * @return a non-null {@code Stream} of users that match the search criteria. + * @return a non-null {@link Stream} of users that match the search criteria. */ - Stream getUsersByUserAttributeStream(RealmModel realm, String name, String value); + default Stream getUsersByUserAttributeStream(RealmModel realm, String name, String value) { + List users = this.getUsersByUserAttribute(realm, name, value); + return users != null ? users.stream() : Stream.empty(); + } + + /** + * The {@link Streams} interface makes all collection-based methods in {@link UserAttributeFederatedStorage} + * default by providing implementations that delegate to the {@link Stream}-based variants instead of the other way + * around. + *

+ * It allows for implementations to focus on the {@link Stream}-based approach for processing sets of data and benefit + * from the potential memory and performance optimizations of that approach. + */ + interface Streams extends UserAttributeFederatedStorage { + + @Override + default List getUsersByUserAttribute(RealmModel realm, String name, String value) { + return this.getUsersByUserAttributeStream(realm, name, value).collect(Collectors.toList()); + } + + @Override + Stream getUsersByUserAttributeStream(RealmModel realm, String name, String value); + } } diff --git a/server-spi/src/main/java/org/keycloak/storage/federated/UserBrokerLinkFederatedStorage.java b/server-spi/src/main/java/org/keycloak/storage/federated/UserBrokerLinkFederatedStorage.java index 68ae813e70..e0140cd148 100644 --- a/server-spi/src/main/java/org/keycloak/storage/federated/UserBrokerLinkFederatedStorage.java +++ b/server-spi/src/main/java/org/keycloak/storage/federated/UserBrokerLinkFederatedStorage.java @@ -39,18 +39,37 @@ public interface UserBrokerLinkFederatedStorage { * @deprecated Use {@link #getFederatedIdentitiesStream(String, RealmModel) getFederatedIdentitiesStream} instead. */ @Deprecated - default Set getFederatedIdentities(String userId, RealmModel realm) { - return this.getFederatedIdentitiesStream(userId, realm).collect(Collectors.toSet()); - } + Set getFederatedIdentities(String userId, RealmModel realm); /** * Obtains the identities of the federated user identified by {@code userId}. * * @param userId the user identifier. * @param realm a reference to the realm. - * @return a non-null {@code Stream} of federated identities associated with the user. + * @return a non-null {@link Stream} of federated identities associated with the user. */ - Stream getFederatedIdentitiesStream(String userId, RealmModel realm); + default Stream getFederatedIdentitiesStream(String userId, RealmModel realm) { + Set value = this.getFederatedIdentities(userId, realm); + return value != null ? value.stream() : Stream.empty(); + } FederatedIdentityModel getFederatedIdentity(String userId, String socialProvider, RealmModel realm); + + /** + * The {@link Streams} interface makes all collection-based methods in {@link UserBrokerLinkFederatedStorage} + * default by providing implementations that delegate to the {@link Stream}-based variants instead of the other way + * around. + *

+ * It allows for implementations to focus on the {@link Stream}-based approach for processing sets of data and benefit + * from the potential memory and performance optimizations of that approach. + */ + interface Streams extends UserBrokerLinkFederatedStorage { + @Override + default Set getFederatedIdentities(String userId, RealmModel realm) { + return this.getFederatedIdentitiesStream(userId, realm).collect(Collectors.toSet()); + } + + @Override + Stream getFederatedIdentitiesStream(String userId, RealmModel realm); + } } diff --git a/server-spi/src/main/java/org/keycloak/storage/federated/UserConsentFederatedStorage.java b/server-spi/src/main/java/org/keycloak/storage/federated/UserConsentFederatedStorage.java index 86219a2a4a..f0078d5e5f 100644 --- a/server-spi/src/main/java/org/keycloak/storage/federated/UserConsentFederatedStorage.java +++ b/server-spi/src/main/java/org/keycloak/storage/federated/UserConsentFederatedStorage.java @@ -35,19 +35,38 @@ public interface UserConsentFederatedStorage { * @deprecated Use {@link #getConsentsStream(RealmModel, String) getConsentsStream} instead. */ @Deprecated - default List getConsents(RealmModel realm, String userId) { - return this.getConsentsStream(realm, userId).collect(Collectors.toList()); - } + List getConsents(RealmModel realm, String userId); /** * Obtains the consents associated with the federated user identified by {@code userId}. * * @param realm a reference to the realm. * @param userId the user identifier. - * @return a non-null {@code Stream} of consents associated with the user. + * @return a non-null {@link Stream} of consents associated with the user. */ - Stream getConsentsStream(RealmModel realm, String userId); + default Stream getConsentsStream(RealmModel realm, String userId) { + List value = this.getConsents(realm, userId); + return value != null ? value.stream() : Stream.empty(); + } void updateConsent(RealmModel realm, String userId, UserConsentModel consent); boolean revokeConsentForClient(RealmModel realm, String userId, String clientInternalId); + + /** + * The {@link Streams} interface makes all collection-based methods in {@link UserConsentFederatedStorage} + * default by providing implementations that delegate to the {@link Stream}-based variants instead of the other way + * around. + *

+ * It allows for implementations to focus on the {@link Stream}-based approach for processing sets of data and benefit + * from the potential memory and performance optimizations of that approach. + */ + interface Streams extends UserConsentFederatedStorage { + @Override + default List getConsents(RealmModel realm, String userId) { + return this.getConsentsStream(realm, userId).collect(Collectors.toList()); + } + + @Override + Stream getConsentsStream(RealmModel realm, String userId); + } } diff --git a/server-spi/src/main/java/org/keycloak/storage/federated/UserFederatedStorageProvider.java b/server-spi/src/main/java/org/keycloak/storage/federated/UserFederatedStorageProvider.java index 0479631c9c..d91b94525d 100755 --- a/server-spi/src/main/java/org/keycloak/storage/federated/UserFederatedStorageProvider.java +++ b/server-spi/src/main/java/org/keycloak/storage/federated/UserFederatedStorageProvider.java @@ -49,9 +49,7 @@ public interface UserFederatedStorageProvider extends Provider, * @deprecated Use {@link #getStoredUsersStream(RealmModel, int, int) getStoredUsersStream} instead. */ @Deprecated - default List getStoredUsers(RealmModel realm, int first, int max) { - return getStoredUsersStream(realm, first, max).collect(Collectors.toList()); - } + List getStoredUsers(RealmModel realm, int first, int max); /** * Obtains the ids of all federated users in the realm. @@ -59,9 +57,12 @@ public interface UserFederatedStorageProvider extends Provider, * @param realm a reference to the realm. * @param first first result to return. Ignored if negative. * @param max maximum number of results to return. Ignored if negative. - * @return a non-null {@code Stream} of federated user ids. + * @return a non-null {@link Stream} of federated user ids. */ - Stream getStoredUsersStream(RealmModel realm, int first, int max); + default Stream getStoredUsersStream(RealmModel realm, int first, int max) { + List value = this.getStoredUsers(realm, first, max); + return value != null ? value.stream() : Stream.empty(); + } int getStoredUsersCount(RealmModel realm); @@ -80,4 +81,30 @@ public interface UserFederatedStorageProvider extends Provider, void preRemove(RealmModel realm, UserModel user); void preRemove(RealmModel realm, ComponentModel model); + + /** + * The {@link UserFederatedStorageProvider.Streams} interface makes all collection-based methods in {@link UserFederatedStorageProvider} + * default by providing implementations that delegate to the {@link Stream}-based variants instead of the other way + * around. + *

+ * It allows for implementations to focus on the {@link Stream}-based approach for processing sets of data and benefit + * from the potential memory and performance optimizations of that approach. + */ + interface Streams extends UserFederatedStorageProvider, + UserAttributeFederatedStorage.Streams, + UserBrokerLinkFederatedStorage.Streams, + UserConsentFederatedStorage.Streams, + UserFederatedUserCredentialStore.Streams, + UserGroupMembershipFederatedStorage.Streams, + UserRequiredActionsFederatedStorage.Streams, + UserRoleMappingsFederatedStorage.Streams { + + @Override + default List getStoredUsers(RealmModel realm, int first, int max) { + return this.getStoredUsersStream(realm, first, max).collect(Collectors.toList()); + } + + @Override + Stream getStoredUsersStream(RealmModel realm, int first, int max); + } } diff --git a/server-spi/src/main/java/org/keycloak/storage/federated/UserFederatedUserCredentialStore.java b/server-spi/src/main/java/org/keycloak/storage/federated/UserFederatedUserCredentialStore.java index 676cce05d4..8b943d8fed 100644 --- a/server-spi/src/main/java/org/keycloak/storage/federated/UserFederatedUserCredentialStore.java +++ b/server-spi/src/main/java/org/keycloak/storage/federated/UserFederatedUserCredentialStore.java @@ -38,26 +38,25 @@ public interface UserFederatedUserCredentialStore extends Provider { * @deprecated Use {@link #getStoredCredentialsStream(RealmModel, String) getStoredCredentialsStream} instead. */ @Deprecated - default List getStoredCredentials(RealmModel realm, String userId) { - return this.getStoredCredentialsStream(realm, userId).collect(Collectors.toList()); - } + List getStoredCredentials(RealmModel realm, String userId); /** * Obtains the credentials associated with the federated user identified by {@code userId}. * * @param realm a reference to the realm. * @param userId the user identifier. - * @return a non-null {@code Stream} of credentials. + * @return a non-null {@link Stream} of credentials. */ - Stream getStoredCredentialsStream(RealmModel realm, String userId); + default Stream getStoredCredentialsStream(RealmModel realm, String userId) { + List value = this.getStoredCredentials(realm, userId); + return value != null ? value.stream() : Stream.empty(); + } /** * @deprecated Use {@link #getStoredCredentialsByTypeStream(RealmModel, String, String) getStoredCredentialsByTypeStream} instead. */ @Deprecated - default List getStoredCredentialsByType(RealmModel realm, String userId, String type) { - return this.getStoredCredentialsByTypeStream(realm, userId, type).collect(Collectors.toList()); - } + List getStoredCredentialsByType(RealmModel realm, String userId, String type); /** * Obtains the credentials of type {@code type} that are associated with the federated user identified by {@code userId}. @@ -65,9 +64,38 @@ public interface UserFederatedUserCredentialStore extends Provider { * @param realm a reference to the realm. * @param userId the user identifier. * @param type the credential type. - * @return a non-null {@code Stream} of credentials. + * @return a non-null {@link Stream} of credentials. */ - Stream getStoredCredentialsByTypeStream(RealmModel realm, String userId, String type); + default Stream getStoredCredentialsByTypeStream(RealmModel realm, String userId, String type) { + List value = this.getStoredCredentialsByType(realm, userId, type); + return value != null ? value.stream() : Stream.empty(); + } CredentialModel getStoredCredentialByNameAndType(RealmModel realm, String userId, String name, String type); + + /** + * The {@link Streams} interface makes all collection-based methods in {@link UserFederatedUserCredentialStore} + * default by providing implementations that delegate to the {@link Stream}-based variants instead of the other way + * around. + *

+ * It allows for implementations to focus on the {@link Stream}-based approach for processing sets of data and benefit + * from the potential memory and performance optimizations of that approach. + */ + interface Streams extends UserFederatedUserCredentialStore { + @Override + default List getStoredCredentials(RealmModel realm, String userId) { + return this.getStoredCredentialsStream(realm, userId).collect(Collectors.toList()); + } + + @Override + Stream getStoredCredentialsStream(RealmModel realm, String userId); + + @Override + default List getStoredCredentialsByType(RealmModel realm, String userId, String type) { + return this.getStoredCredentialsByTypeStream(realm, userId, type).collect(Collectors.toList()); + } + + @Override + Stream getStoredCredentialsByTypeStream(RealmModel realm, String userId, String type); + } } diff --git a/server-spi/src/main/java/org/keycloak/storage/federated/UserGroupMembershipFederatedStorage.java b/server-spi/src/main/java/org/keycloak/storage/federated/UserGroupMembershipFederatedStorage.java index aad956d471..0933ad2447 100644 --- a/server-spi/src/main/java/org/keycloak/storage/federated/UserGroupMembershipFederatedStorage.java +++ b/server-spi/src/main/java/org/keycloak/storage/federated/UserGroupMembershipFederatedStorage.java @@ -29,12 +29,24 @@ import java.util.stream.Stream; * @version $Revision: 1 $ */ public interface UserGroupMembershipFederatedStorage { - @Deprecated - default Set getGroups(RealmModel realm, String userId) { - return getGroupsStream(realm, userId).collect(Collectors.toSet()); - } - Stream getGroupsStream(RealmModel realm, String userId); + /** + * @deprecated Use {@link #getGroupsStream(RealmModel, String) getGroupsStream} instead. + */ + @Deprecated + Set getGroups(RealmModel realm, String userId); + + /** + * Obtains the groups associated with the federated user. + * + * @param realm a reference to the realm. + * @param userId the user identifier. + * @return a non-null {@code Stream} of groups. + */ + default Stream getGroupsStream(RealmModel realm, String userId) { + Set value = this.getGroups(realm, userId); + return value != null ? value.stream() : Stream.empty(); + } void joinGroup(RealmModel realm, String userId, GroupModel group); void leaveGroup(RealmModel realm, String userId, GroupModel group); @@ -43,9 +55,7 @@ public interface UserGroupMembershipFederatedStorage { * @deprecated Use {@link #getMembershipStream(RealmModel, GroupModel, int, int) getMembershipStream} instead. */ @Deprecated - default List getMembership(RealmModel realm, GroupModel group, int firstResult, int max) { - return this.getMembershipStream(realm, group, firstResult, max).collect(Collectors.toList()); - } + List getMembership(RealmModel realm, GroupModel group, int firstResult, int max); /** * Obtains the federated users that are members of the given {@code group} in the specified {@code realm}. @@ -56,6 +66,34 @@ public interface UserGroupMembershipFederatedStorage { * @param max maximum number of results to return. Ignored if negative. * @return a non-null {@code Stream} of federated user ids that are members of the group in the realm. */ - Stream getMembershipStream(RealmModel realm, GroupModel group, int firstResult, int max); + default Stream getMembershipStream(RealmModel realm, GroupModel group, int firstResult, int max) { + List value = this.getMembership(realm, group, firstResult, max); + return value != null ? value.stream() : Stream.empty(); + } + /** + * The {@link Streams} interface makes all collection-based methods in {@link UserGroupMembershipFederatedStorage} + * default by providing implementations that delegate to the {@link Stream}-based variants instead of the other way + * around. + *

+ * It allows for implementations to focus on the {@link Stream}-based approach for processing sets of data and benefit + * from the potential memory and performance optimizations of that approach. + */ + interface Streams extends UserGroupMembershipFederatedStorage { + @Override + default Set getGroups(RealmModel realm, String userId) { + return getGroupsStream(realm, userId).collect(Collectors.toSet()); + } + + @Override + Stream getGroupsStream(RealmModel realm, String userId); + + @Override + default List getMembership(RealmModel realm, GroupModel group, int firstResult, int max) { + return this.getMembershipStream(realm, group, firstResult, max).collect(Collectors.toList()); + } + + @Override + Stream getMembershipStream(RealmModel realm, GroupModel group, int firstResult, int max); + } } diff --git a/server-spi/src/main/java/org/keycloak/storage/federated/UserRequiredActionsFederatedStorage.java b/server-spi/src/main/java/org/keycloak/storage/federated/UserRequiredActionsFederatedStorage.java index 23dce8e90c..165542aaf2 100644 --- a/server-spi/src/main/java/org/keycloak/storage/federated/UserRequiredActionsFederatedStorage.java +++ b/server-spi/src/main/java/org/keycloak/storage/federated/UserRequiredActionsFederatedStorage.java @@ -32,18 +32,38 @@ public interface UserRequiredActionsFederatedStorage { * @deprecated Use {@link #getRequiredActionsStream(RealmModel, String) getRequiredActionsStream} instead. */ @Deprecated - default Set getRequiredActions(RealmModel realm, String userId) { - return this.getRequiredActionsStream(realm, userId).collect(Collectors.toSet()); - } + Set getRequiredActions(RealmModel realm, String userId); + /** * Obtains the names of required actions associated with the federated user identified by {@code userId}. * * @param realm a reference to the realm. * @param userId the user identifier. - * @return a non-null {@code Stream} of required action names. + * @return a non-null {@link Stream} of required action names. */ - Stream getRequiredActionsStream(RealmModel realm, String userId); + default Stream getRequiredActionsStream(RealmModel realm, String userId) { + Set value = this.getRequiredActions(realm, userId); + return value != null ? value.stream() : Stream.empty(); + } void addRequiredAction(RealmModel realm, String userId, String action); void removeRequiredAction(RealmModel realm, String userId, String action); + + /** + * The {@link Streams} interface makes all collection-based methods in {@link UserRequiredActionsFederatedStorage} + * default by providing implementations that delegate to the {@link Stream}-based variants instead of the other way + * around. + *

+ * It allows for implementations to focus on the {@link Stream}-based approach for processing sets of data and benefit + * from the potential memory and performance optimizations of that approach. + */ + interface Streams extends UserRequiredActionsFederatedStorage { + @Override + default Set getRequiredActions(RealmModel realm, String userId) { + return this.getRequiredActionsStream(realm, userId).collect(Collectors.toSet()); + } + + @Override + Stream getRequiredActionsStream(RealmModel realm, String userId); + } } diff --git a/server-spi/src/main/java/org/keycloak/storage/federated/UserRoleMappingsFederatedStorage.java b/server-spi/src/main/java/org/keycloak/storage/federated/UserRoleMappingsFederatedStorage.java index 10ea27c3ec..3d8c0d0357 100644 --- a/server-spi/src/main/java/org/keycloak/storage/federated/UserRoleMappingsFederatedStorage.java +++ b/server-spi/src/main/java/org/keycloak/storage/federated/UserRoleMappingsFederatedStorage.java @@ -29,15 +29,13 @@ import java.util.stream.Stream; */ public interface UserRoleMappingsFederatedStorage { - void grantRole(RealmModel realm, String userId, RoleModel role); - /** * @deprecated Use {@link #getRoleMappingsStream(RealmModel, String) getRoleMappingsStream} instead. */ @Deprecated - default Set getRoleMappings(RealmModel realm,String userId) { - return getRoleMappingsStream(realm, userId).collect(Collectors.toSet()); - } + Set getRoleMappings(RealmModel realm,String userId); + + void grantRole(RealmModel realm, String userId, RoleModel role); /** * Obtains the roles associated with the federated user identified by {@code userId}. @@ -46,7 +44,28 @@ public interface UserRoleMappingsFederatedStorage { * @param userId the user identifier. * @return a non-null {@code Stream} of roles. */ - Stream getRoleMappingsStream(RealmModel realm, String userId); + default Stream getRoleMappingsStream(RealmModel realm, String userId) { + Set value = this.getRoleMappings(realm, userId); + return value != null ? value.stream() : Stream.empty(); + } void deleteRoleMapping(RealmModel realm, String userId, RoleModel role); + + /** + * The {@link Streams} interface makes all collection-based methods in {@link UserRoleMappingsFederatedStorage} + * default by providing implementations that delegate to the {@link Stream}-based variants instead of the other way + * around. + *

+ * It allows for implementations to focus on the {@link Stream}-based approach for processing sets of data and benefit + * from the potential memory and performance optimizations of that approach. + */ + interface Streams extends UserRoleMappingsFederatedStorage { + @Override + default Set getRoleMappings(RealmModel realm, String userId) { + return getRoleMappingsStream(realm, userId).collect(Collectors.toSet()); + } + + @Override + Stream getRoleMappingsStream(RealmModel realm, String userId); + } } diff --git a/server-spi/src/main/java/org/keycloak/storage/user/UserQueryProvider.java b/server-spi/src/main/java/org/keycloak/storage/user/UserQueryProvider.java index 8e8de6ad5b..f9d550133d 100644 --- a/server-spi/src/main/java/org/keycloak/storage/user/UserQueryProvider.java +++ b/server-spi/src/main/java/org/keycloak/storage/user/UserQueryProvider.java @@ -141,25 +141,23 @@ public interface UserQueryProvider { * @deprecated Use {@link #getUsersStream(RealmModel) getUsersStream} instead. */ @Deprecated - default List getUsers(RealmModel realm) { - return this.getUsersStream(realm).collect(Collectors.toList()); - } - + List getUsers(RealmModel realm); /** * Searches all users in the realm. * * @param realm a reference to the realm. - * @return a non-null {@code Stream} of users. + * @return a non-null {@link Stream} of users. */ - Stream getUsersStream(RealmModel realm); + default Stream getUsersStream(RealmModel realm) { + List value = this.getUsers(realm); + return value != null ? value.stream() : Stream.empty(); + } /** * @deprecated Use {@link #getUsersStream(RealmModel, int, int) getUsersStream} instead. */ @Deprecated - default List getUsers(RealmModel realm, int firstResult, int maxResults) { - return this.getUsersStream(realm, firstResult, maxResults).collect(Collectors.toList()); - } + List getUsers(RealmModel realm, int firstResult, int maxResults); /** * Searches all users in the realm, starting from the {@code firstResult} and containing at most {@code maxResults}. @@ -167,9 +165,12 @@ public interface UserQueryProvider { * @param realm a reference to the realm. * @param firstResult first result to return. Ignored if negative. * @param maxResults maximum number of results to return. Ignored if negative. - * @return a non-null {@code Stream} of users. + * @return a non-null {@link Stream} of users. */ - Stream getUsersStream(RealmModel realm, int firstResult, int maxResults); + default Stream getUsersStream(RealmModel realm, int firstResult, int maxResults) { + List value = this.getUsers(realm, firstResult, maxResults); + return value != null ? value.stream() : Stream.empty(); + } /** * Search for users with username, email or first + last name that is like search string. @@ -184,9 +185,7 @@ public interface UserQueryProvider { * @deprecated Use {@link #searchForUserStream(String, RealmModel) searchForUserStream} instead. */ @Deprecated - default List searchForUser(String search, RealmModel realm) { - return this.searchForUserStream(search, realm).collect(Collectors.toList()); - } + List searchForUser(String search, RealmModel realm); /** * Searches for users with username, email or first + last name that is like search string. If possible, implementations @@ -196,9 +195,12 @@ public interface UserQueryProvider { * * @param search case sensitive search string. * @param realm a reference to the realm. - * @return a non-null {@code Stream} of users that match the search string. + * @return a non-null {@link Stream} of users that match the search string. */ - Stream searchForUserStream(String search, RealmModel realm); + default Stream searchForUserStream(String search, RealmModel realm) { + List value = this.searchForUser(search, realm); + return value != null ? value.stream() : Stream.empty(); + } /** * Search for users with username, email or first + last name that is like search string. @@ -215,9 +217,7 @@ public interface UserQueryProvider { * @deprecated Use {@link #searchForUserStream(String, RealmModel, int, int) searchForUserStream} instead. */ @Deprecated - default List searchForUser(String search, RealmModel realm, int firstResult, int maxResults) { - return this.searchForUserStream(search, realm, firstResult, maxResults).collect(Collectors.toList()); - } + List searchForUser(String search, RealmModel realm, int firstResult, int maxResults); /** * Searches for users with username, email or first + last name that is like search string. If possible, implementations @@ -229,9 +229,12 @@ public interface UserQueryProvider { * @param realm a reference to the realm. * @param firstResult first result to return. Ignored if negative. * @param maxResults maximum number of results to return. Ignored if negative. - * @return a non-null {@code Stream} of users that match the search criteria. + * @return a non-null {@link Stream} of users that match the search criteria. */ - Stream searchForUserStream(String search, RealmModel realm, int firstResult, int maxResults); + default Stream searchForUserStream(String search, RealmModel realm, int firstResult, int maxResults) { + List value = this.searchForUser(search, realm, firstResult, maxResults); + return value != null ? value.stream() : Stream.empty(); + } /** * Search for user by parameter. Valid parameters are: @@ -251,9 +254,7 @@ public interface UserQueryProvider { * @deprecated Use {@link #searchForUserStream(Map, RealmModel) searchForUserStream} instead. */ @Deprecated - default List searchForUser(Map params, RealmModel realm) { - return this.searchForUserStream(params, realm).collect(Collectors.toList()); - } + List searchForUser(Map params, RealmModel realm); /** * Searches for user by parameter. If possible, implementations should treat the parameter values as partial match patterns @@ -269,9 +270,12 @@ public interface UserQueryProvider { * * @param params a map containing the search parameters. * @param realm a reference to the realm. - * @return a non-null {@code Stream} of users that match the search parameters. + * @return a non-null {@link Stream} of users that match the search parameters. */ - Stream searchForUserStream(Map params, RealmModel realm); + default Stream searchForUserStream(Map params, RealmModel realm) { + List value = this.searchForUser(params, realm); + return value != null ? value.stream() : Stream.empty(); + } /** * Search for user by parameter. Valid parameters are: @@ -293,9 +297,7 @@ public interface UserQueryProvider { * @deprecated Use {@link #searchForUserStream(Map, RealmModel, int, int) searchForUserStream} instead. */ @Deprecated - default List searchForUser(Map params, RealmModel realm, int firstResult, int maxResults) { - return this.searchForUserStream(params, realm, firstResult, maxResults).collect(Collectors.toList()); - } + List searchForUser(Map params, RealmModel realm, int firstResult, int maxResults); /** * Searches for user by parameter. If possible, implementations should treat the parameter values as partial match patterns @@ -313,9 +315,12 @@ public interface UserQueryProvider { * @param realm a reference to the realm. * @param firstResult first result to return. Ignored if negative. * @param maxResults maximum number of results to return. Ignored if negative. - * @return a non-null {@code Stream} of users that match the search criteria. + * @return a non-null {@link Stream} of users that match the search criteria. */ - Stream searchForUserStream(Map params, RealmModel realm, int firstResult, int maxResults); + default Stream searchForUserStream(Map params, RealmModel realm, int firstResult, int maxResults) { + List value = this.searchForUser(params, realm, firstResult, maxResults); + return value != null ? value.stream() : Stream.empty(); + } /** * Get users that belong to a specific group. Implementations do not have to search in UserFederatedStorageProvider @@ -329,9 +334,7 @@ public interface UserQueryProvider { * @deprecated Use {@link #getGroupMembersStream(RealmModel, GroupModel) getGroupMembersStream} instead. */ @Deprecated - default List getGroupMembers(RealmModel realm, GroupModel group) { - return this.getGroupMembersStream(realm, group).collect(Collectors.toList()); - } + List getGroupMembers(RealmModel realm, GroupModel group); /** * Obtains users that belong to a specific group. Implementations do not have to search in {@code UserFederatedStorageProvider} @@ -341,9 +344,12 @@ public interface UserQueryProvider { * * @param realm a reference to the realm. * @param group a reference to the group. - * @return a non-null {@code Stream} of users that belong to the group. + * @return a non-null {@link Stream} of users that belong to the group. */ - Stream getGroupMembersStream(RealmModel realm, GroupModel group); + default Stream getGroupMembersStream(RealmModel realm, GroupModel group) { + List value = this.getGroupMembers(realm, group); + return value != null ? value.stream() : Stream.empty(); + } /** * Get users that belong to a specific group. Implementations do not have to search in UserFederatedStorageProvider @@ -359,9 +365,7 @@ public interface UserQueryProvider { * @deprecated Use {@link #getGroupMembersStream(RealmModel, GroupModel, int, int) getGroupMembersStream} instead. */ @Deprecated - default List getGroupMembers(RealmModel realm, GroupModel group, int firstResult, int maxResults) { - return this.getGroupMembersStream(realm, group, firstResult, maxResults).collect(Collectors.toList()); - } + List getGroupMembers(RealmModel realm, GroupModel group, int firstResult, int maxResults); /** * Obtains users that belong to a specific group. Implementations do not have to search in {@code UserFederatedStorageProvider} @@ -373,9 +377,12 @@ public interface UserQueryProvider { * @param group a reference to the group. * @param firstResult first result to return. Ignored if negative. * @param maxResults maximum number of results to return. Ignored if negative. - * @return a non-null {@code Stream} of users that belong to the group. + * @return a non-null {@link Stream} of users that belong to the group. */ - Stream getGroupMembersStream(RealmModel realm, GroupModel group, int firstResult, int maxResults); + default Stream getGroupMembersStream(RealmModel realm, GroupModel group, int firstResult, int maxResults) { + List value = this.getGroupMembers(realm, group, firstResult, maxResults); + return value != null ? value.stream() : Stream.empty(); + } /** * Get users that belong to a specific role. @@ -395,7 +402,7 @@ public interface UserQueryProvider { * * @param realm a reference to the realm. * @param role a reference to the role. - * @return a non-null {@code Stream} of users that have the specified role. + * @return a non-null {@link Stream} of users that have the specified role. */ default Stream getRoleMembersStream(RealmModel realm, RoleModel role) { return Stream.empty(); @@ -422,7 +429,7 @@ public interface UserQueryProvider { * @param role a reference to the role. * @param firstResult first result to return. Ignored if negative. * @param maxResults maximum number of results to return. Ignored if negative. - * @return a non-null {@code Stream} of users that have the specified role. + * @return a non-null {@link Stream} of users that have the specified role. */ default Stream getRoleMembersStream(RealmModel realm, RoleModel role, int firstResult, int maxResults) { return Stream.empty(); @@ -443,9 +450,7 @@ public interface UserQueryProvider { * instead. */ @Deprecated - default List searchForUserByUserAttribute(String attrName, String attrValue, RealmModel realm) { - return this.searchForUserByUserAttributeStream(attrName, attrValue, realm).collect(Collectors.toList()); - } + List searchForUserByUserAttribute(String attrName, String attrValue, RealmModel realm); /** * Searches for users that have a specific attribute with a specific value. Implementations do not have to search in @@ -456,7 +461,91 @@ public interface UserQueryProvider { * @param attrName the attribute name. * @param attrValue the attribute value. * @param realm a reference to the realm. - * @return a non-null {@code Stream} of users that match the search criteria. + * @return a non-null {@link Stream} of users that match the search criteria. */ - Stream searchForUserByUserAttributeStream(String attrName, String attrValue, RealmModel realm); + default Stream searchForUserByUserAttributeStream(String attrName, String attrValue, RealmModel realm) { + List value = this.searchForUserByUserAttribute(attrName, attrValue, realm); + return value != null ? value.stream() : Stream.empty(); + } + + /** + * The {@link Streams} interface makes all collection-based methods in {@link UserQueryProvider} default by + * providing implementations that delegate to the {@link Stream}-based variants instead of the other way around. + *

+ * It allows for implementations to focus on the {@link Stream}-based approach for processing sets of data and benefit + * from the potential memory and performance optimizations of that approach. + */ + interface Streams extends UserQueryProvider { + @Override + default List getUsers(RealmModel realm) { + return this.getUsersStream(realm).collect(Collectors.toList()); + } + + @Override + Stream getUsersStream(RealmModel realm); + + @Override + default List getUsers(RealmModel realm, int firstResult, int maxResults) { + return this.getUsersStream(realm, firstResult, maxResults).collect(Collectors.toList()); + } + + @Override + Stream getUsersStream(RealmModel realm, int firstResult, int maxResults); + + @Override + default List searchForUser(String search, RealmModel realm) { + return this.searchForUserStream(search, realm).collect(Collectors.toList()); + } + + @Override + Stream searchForUserStream(String search, RealmModel realm); + + @Override + default List searchForUser(String search, RealmModel realm, int firstResult, int maxResults) { + return this.searchForUserStream(search, realm, firstResult, maxResults).collect(Collectors.toList()); + } + + @Override + Stream searchForUserStream(String search, RealmModel realm, int firstResult, int maxResults); + + @Override + default List searchForUser(Map params, RealmModel realm) { + return this.searchForUserStream(params, realm).collect(Collectors.toList()); + } + + @Override + Stream searchForUserStream(Map params, RealmModel realm); + + @Override + default List searchForUser(Map params, RealmModel realm, int firstResult, int maxResults) { + return this.searchForUserStream(params, realm, firstResult, maxResults).collect(Collectors.toList()); + } + + @Override + Stream searchForUserStream(Map params, RealmModel realm, int firstResult, int maxResults); + + @Override + default List getGroupMembers(RealmModel realm, GroupModel group) { + return this.getGroupMembersStream(realm, group).collect(Collectors.toList()); + } + + @Override + Stream getGroupMembersStream(RealmModel realm, GroupModel group); + + @Override + default List getGroupMembers(RealmModel realm, GroupModel group, int firstResult, int maxResults) { + return this.getGroupMembersStream(realm, group, firstResult, maxResults).collect(Collectors.toList()); + } + + @Override + Stream getGroupMembersStream(RealmModel realm, GroupModel group, int firstResult, int maxResults); + + @Override + default List searchForUserByUserAttribute(String attrName, String attrValue, RealmModel realm) { + return this.searchForUserByUserAttributeStream(attrName, attrValue, realm).collect(Collectors.toList()); + } + + @Override + Stream searchForUserByUserAttributeStream(String attrName, String attrValue, RealmModel realm); + } } diff --git a/services/src/main/java/org/keycloak/storage/UserStorageManager.java b/services/src/main/java/org/keycloak/storage/UserStorageManager.java index 02c4244464..0ab9d6f44f 100755 --- a/services/src/main/java/org/keycloak/storage/UserStorageManager.java +++ b/services/src/main/java/org/keycloak/storage/UserStorageManager.java @@ -17,7 +17,6 @@ package org.keycloak.storage; -import com.google.common.collect.Streams; import org.jboss.logging.Logger; import org.keycloak.component.ComponentFactory; import org.keycloak.component.ComponentModel; @@ -27,7 +26,6 @@ import org.keycloak.models.FederatedIdentityModel; import org.keycloak.models.GroupModel; import org.keycloak.models.IdentityProviderModel; import org.keycloak.models.KeycloakSession; -import org.keycloak.models.KeycloakSessionTask; import org.keycloak.models.ModelException; import org.keycloak.models.ProtocolMapperModel; import org.keycloak.models.RealmModel; @@ -65,7 +63,8 @@ import static org.keycloak.models.utils.KeycloakModelUtils.runJobInTransaction; * @author Bill Burke * @version $Revision: 1 $ */ -public class UserStorageManager extends AbstractStorageManager implements UserProvider, OnUserCache, OnCreateComponent, OnUpdateComponent { +public class UserStorageManager extends AbstractStorageManager + implements UserProvider.Streams, OnUserCache, OnCreateComponent, OnUpdateComponent { private static final Logger logger = Logger.getLogger(UserStorageManager.class); @@ -605,7 +604,7 @@ public class UserStorageManager extends AbstractStorageManager stream = StorageId.isLocalStorage(user) ? localStorage().getFederatedIdentitiesStream(user, realm) : Stream.empty(); if (getFederatedStorage() != null) - stream = Streams.concat(stream, getFederatedStorage().getFederatedIdentitiesStream(user.getId(), realm)); + stream = Stream.concat(stream, getFederatedStorage().getFederatedIdentitiesStream(user.getId(), realm)); return stream.distinct(); } diff --git a/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/java/org/keycloak/testsuite/federation/BackwardsCompatibilityUserStorage.java b/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/java/org/keycloak/testsuite/federation/BackwardsCompatibilityUserStorage.java index ad8563e63a..89e7585c5f 100644 --- a/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/java/org/keycloak/testsuite/federation/BackwardsCompatibilityUserStorage.java +++ b/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/java/org/keycloak/testsuite/federation/BackwardsCompatibilityUserStorage.java @@ -57,7 +57,7 @@ import org.keycloak.storage.user.UserRegistrationProvider; * @author Marek Posolda */ public class BackwardsCompatibilityUserStorage implements UserLookupProvider, UserStorageProvider, UserRegistrationProvider, - CredentialInputUpdater, CredentialInputValidator, UserQueryProvider { + CredentialInputUpdater, CredentialInputValidator, UserQueryProvider.Streams { private static final Logger log = Logger.getLogger(BackwardsCompatibilityUserStorage.class); @@ -82,7 +82,7 @@ public class BackwardsCompatibilityUserStorage implements UserLookupProvider, Us } private UserModel createUser(RealmModel realm, String username) { - return new AbstractUserAdapterFederatedStorage(session, realm, model) { + return new AbstractUserAdapterFederatedStorage.Streams(session, realm, model) { @Override public String getUsername() { return username; diff --git a/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/java/org/keycloak/testsuite/federation/FailableHardcodedStorageProvider.java b/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/java/org/keycloak/testsuite/federation/FailableHardcodedStorageProvider.java index 23eb58283d..cf0ef1c653 100644 --- a/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/java/org/keycloak/testsuite/federation/FailableHardcodedStorageProvider.java +++ b/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/java/org/keycloak/testsuite/federation/FailableHardcodedStorageProvider.java @@ -43,7 +43,8 @@ import java.util.stream.Stream; * @author Bill Burke * @version $Revision: 1 $ */ -public class FailableHardcodedStorageProvider implements UserStorageProvider, UserLookupProvider, UserQueryProvider, ImportedUserValidation, CredentialInputUpdater, CredentialInputValidator { +public class FailableHardcodedStorageProvider implements UserStorageProvider, UserLookupProvider, UserQueryProvider.Streams, + ImportedUserValidation, CredentialInputUpdater, CredentialInputValidator { public static String username = "billb"; public static String password = "password"; diff --git a/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/java/org/keycloak/testsuite/federation/HardcodedGroupStorageProvider.java b/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/java/org/keycloak/testsuite/federation/HardcodedGroupStorageProvider.java index e511d11b43..8e2119b0b3 100644 --- a/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/java/org/keycloak/testsuite/federation/HardcodedGroupStorageProvider.java +++ b/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/java/org/keycloak/testsuite/federation/HardcodedGroupStorageProvider.java @@ -68,7 +68,7 @@ public class HardcodedGroupStorageProvider implements GroupStorageProvider { } - public class HardcodedGroupAdapter implements GroupModel { + public class HardcodedGroupAdapter implements GroupModel.Streams { private final RealmModel realm; private StorageId storageId; diff --git a/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/java/org/keycloak/testsuite/federation/PassThroughFederatedUserStorageProvider.java b/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/java/org/keycloak/testsuite/federation/PassThroughFederatedUserStorageProvider.java index 6a8d309fa9..dc53ef9f2c 100644 --- a/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/java/org/keycloak/testsuite/federation/PassThroughFederatedUserStorageProvider.java +++ b/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/java/org/keycloak/testsuite/federation/PassThroughFederatedUserStorageProvider.java @@ -154,7 +154,7 @@ public class PassThroughFederatedUserStorageProvider implements } private UserModel getUserModel(final RealmModel realm) { - return new AbstractUserAdapterFederatedStorage(session, realm, component) { + return new AbstractUserAdapterFederatedStorage.Streams(session, realm, component) { @Override public String getUsername() { return PASSTHROUGH_USERNAME; diff --git a/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/java/org/keycloak/testsuite/federation/UserMapStorage.java b/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/java/org/keycloak/testsuite/federation/UserMapStorage.java index dc8da1c643..eb183d3f00 100644 --- a/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/java/org/keycloak/testsuite/federation/UserMapStorage.java +++ b/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/java/org/keycloak/testsuite/federation/UserMapStorage.java @@ -54,8 +54,8 @@ import static org.keycloak.storage.UserStorageProviderModel.IMPORT_ENABLED; * @author Bill Burke * @version $Revision: 1 $ */ -public class UserMapStorage implements UserLookupProvider, UserStorageProvider, UserRegistrationProvider, CredentialInputUpdater, CredentialInputValidator, - UserGroupMembershipFederatedStorage, UserQueryProvider, ImportedUserValidation { +public class UserMapStorage implements UserLookupProvider, UserStorageProvider, UserRegistrationProvider, CredentialInputUpdater, + CredentialInputValidator, UserGroupMembershipFederatedStorage.Streams, UserQueryProvider.Streams, ImportedUserValidation { private static final Logger log = Logger.getLogger(UserMapStorage.class); @@ -113,7 +113,7 @@ public class UserMapStorage implements UserLookupProvider, UserStorageProvider, user.setEnabled(true); user.setFederationLink(model.getId()); } else { - user = new AbstractUserAdapterFederatedStorage(session, realm, model) { + user = new AbstractUserAdapterFederatedStorage.Streams(session, realm, model) { @Override public String getUsername() { return username; diff --git a/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/java/org/keycloak/testsuite/federation/UserPropertyFileStorage.java b/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/java/org/keycloak/testsuite/federation/UserPropertyFileStorage.java index dd8ba1594e..25eabfc4b0 100644 --- a/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/java/org/keycloak/testsuite/federation/UserPropertyFileStorage.java +++ b/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/java/org/keycloak/testsuite/federation/UserPropertyFileStorage.java @@ -33,11 +33,13 @@ import org.keycloak.storage.adapter.AbstractUserAdapterFederatedStorage; import org.keycloak.storage.user.UserLookupProvider; import org.keycloak.storage.user.UserQueryProvider; +import java.util.Collections; +import java.util.LinkedList; +import java.util.List; import java.util.Map; import java.util.Optional; import java.util.Properties; import java.util.function.Predicate; -import java.util.stream.Stream; /** * @author Bill Burke @@ -69,7 +71,7 @@ public class UserPropertyFileStorage implements UserLookupProvider, UserStorageP private UserModel createUser(RealmModel realm, String username) { if (federatedStorageEnabled) { - return new AbstractUserAdapterFederatedStorage(session, realm, model) { + return new AbstractUserAdapterFederatedStorage.Streams(session, realm, model) { @Override public String getUsername() { return username; @@ -81,7 +83,7 @@ public class UserPropertyFileStorage implements UserLookupProvider, UserStorageP } }; } else { - return new AbstractUserAdapter(session, realm, model) { + return new AbstractUserAdapter.Streams(session, realm, model) { @Override public String getUsername() { return username; @@ -90,7 +92,6 @@ public class UserPropertyFileStorage implements UserLookupProvider, UserStorageP } } - @Override public UserModel getUserByUsername(String username, RealmModel realm) { if (!userPasswords.containsKey(username)) return null; @@ -144,59 +145,67 @@ public class UserPropertyFileStorage implements UserLookupProvider, UserStorageP } @Override - public Stream getUsersStream(RealmModel realm) { - return userPasswords.keySet().stream().map(obj -> createUser(realm, (String) obj)); + public List getUsers(RealmModel realm) { + List users = new LinkedList<>(); + for (Object username : userPasswords.keySet()) { + users.add(createUser(realm, (String)username)); + } + return users; } @Override - public Stream searchForUserStream(Map attributes, RealmModel realm) { - return searchForUserStream(attributes, realm, 0, Integer.MAX_VALUE - 1); + public List searchForUser(Map attributes, RealmModel realm) { + return searchForUser(attributes, realm, 0, Integer.MAX_VALUE - 1); } @Override - public Stream getUsersStream(RealmModel realm, int firstResult, int maxResults) { - Stream stream = userPasswords.keySet().stream(); - if (firstResult > 0) - stream = stream.skip(firstResult); - if (maxResults >= 0) - stream = stream.limit(maxResults); - return stream.map(obj -> createUser(realm, (String) obj)); + public List getUsers(RealmModel realm, int firstResult, int maxResults) { + if (maxResults == 0) return Collections.EMPTY_LIST; + List users = new LinkedList<>(); + int count = 0; + for (Object un : userPasswords.keySet()) { + if (count++ < firstResult) continue; + String username = (String)un; + users.add(createUser(realm, username)); + if (users.size() + 1 > maxResults) break; + } + return users; } @Override - public Stream searchForUserStream(String search, RealmModel realm, int firstResult, int maxResults) { - return searchForUserStream(search, realm, firstResult, maxResults, username -> username.contains(search)); + public List searchForUser(String search, RealmModel realm, int firstResult, int maxResults) { + return searchForUser(search, realm, firstResult, maxResults, username -> username.contains(search)); } @Override - public Stream searchForUserStream(Map attributes, RealmModel realm, int firstResult, int maxResults) { + public List searchForUser(Map attributes, RealmModel realm, int firstResult, int maxResults) { String search = Optional.ofNullable(attributes.get(UserModel.USERNAME)) .orElseGet(()-> attributes.get(UserModel.SEARCH)); - if (search == null) return Stream.empty(); + if (search == null) return Collections.EMPTY_LIST; Predicate p = Boolean.valueOf(attributes.getOrDefault(UserModel.EXACT, Boolean.FALSE.toString())) - ? username -> username.equals(search) - : username -> username.contains(search); - return searchForUserStream(search, realm, firstResult, maxResults, p); + ? username -> username.equals(search) + : username -> username.contains(search); + return searchForUser(search, realm, firstResult, maxResults, p); } @Override - public Stream getGroupMembersStream(RealmModel realm, GroupModel group, int firstResult, int maxResults) { - return Stream.empty(); + public List getGroupMembers(RealmModel realm, GroupModel group, int firstResult, int maxResults) { + return Collections.EMPTY_LIST; } @Override - public Stream getGroupMembersStream(RealmModel realm, GroupModel group) { - return Stream.empty(); + public List getGroupMembers(RealmModel realm, GroupModel group) { + return Collections.EMPTY_LIST; } @Override - public Stream searchForUserStream(String search, RealmModel realm) { - return searchForUserStream(search, realm, 0, Integer.MAX_VALUE - 1); + public List searchForUser(String search, RealmModel realm) { + return searchForUser(search, realm, 0, Integer.MAX_VALUE - 1); } @Override - public Stream searchForUserByUserAttributeStream(String attrName, String attrValue, RealmModel realm) { - return Stream.empty(); + public List searchForUserByUserAttribute(String attrName, String attrValue, RealmModel realm) { + return Collections.EMPTY_LIST; } @Override @@ -204,8 +213,20 @@ public class UserPropertyFileStorage implements UserLookupProvider, UserStorageP } - private Stream searchForUserStream(String search, RealmModel realm, int firstResult, int maxResults, Predicate matcher) { - return userPasswords.keySet().stream().filter(obj -> matcher.test((String) obj)).skip(firstResult < 0 ? 0 : firstResult) - .limit(maxResults < 0 ? 0 : maxResults).map(obj -> createUser(realm, (String) obj)); + private List searchForUser(String search, RealmModel realm, int firstResult, int maxResults, Predicate matcher) { + if (maxResults == 0) return Collections.EMPTY_LIST; + List users = new LinkedList<>(); + int count = 0; + for (Object un : userPasswords.keySet()) { + String username = (String)un; + if (matcher.test(username)) { + if (count++ < firstResult) { + continue; + } + users.add(createUser(realm, username)); + if (users.size() + 1 > maxResults) break; + } + } + return users; } }