KEYCLOAK-14220 Complement methods for accessing clients with Stream variants

This commit is contained in:
Martin Kanis 2020-07-14 09:12:34 +02:00 committed by Marek Posolda
parent da6530471b
commit feef5b4db2
21 changed files with 392 additions and 247 deletions

View file

@ -612,18 +612,18 @@ public class ClientAdapter implements ClientModel, CachedObject {
} }
@Override @Override
public Set<RoleModel> getRoles() { public Stream<RoleModel> getRolesStream() {
return cacheSession.getClientRoles(cachedRealm, this); return cacheSession.getClientRolesStream(cachedRealm, this);
} }
@Override @Override
public Set<RoleModel> getRoles(Integer first, Integer max) { public Stream<RoleModel> getRolesStream(Integer first, Integer max) {
return cacheSession.getClientRoles(cachedRealm, this, first, max); return cacheSession.getClientRolesStream(cachedRealm, this, first, max);
} }
@Override @Override
public Set<RoleModel> searchForRoles(String search, Integer first, Integer max) { public Stream<RoleModel> searchForRolesStream(String search, Integer first, Integer max) {
return cacheSession.searchForClientRoles(cachedRealm, this, search, first, max); return cacheSession.searchForClientRolesStream(cachedRealm, this, search, first, max);
} }
@Override @Override

View file

@ -29,6 +29,7 @@ import org.keycloak.storage.client.ClientStorageProvider;
import java.util.*; import java.util.*;
import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Stream;
/** /**
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a> * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
@ -755,13 +756,13 @@ public class RealmAdapter implements CachedRealmModel {
} }
@Override @Override
public List<ClientModel> getClients() { public Stream<ClientModel> getClientsStream() {
return cacheSession.getClients(this); return cacheSession.getClientsStream(this);
} }
@Override @Override
public List<ClientModel> getAlwaysDisplayInConsoleClients() { public Stream<ClientModel> getAlwaysDisplayInConsoleClientsStream() {
return cacheSession.getAlwaysDisplayInConsoleClients(this); return cacheSession.getAlwaysDisplayInConsoleClientsStream(this);
} }
@Override @Override
@ -791,13 +792,13 @@ public class RealmAdapter implements CachedRealmModel {
} }
@Override @Override
public List<ClientModel> searchClientByClientId(String clientId, Integer firstResult, Integer maxResults) { public Stream<ClientModel> searchClientByClientIdStream(String clientId, Integer firstResult, Integer maxResults) {
return cacheSession.searchClientsByClientId(this, clientId, firstResult, maxResults); return cacheSession.searchClientsByClientIdStream(this, clientId, firstResult, maxResults);
} }
@Override @Override
public List<ClientModel> getClients(Integer firstResult, Integer maxResults) { public Stream<ClientModel> getClientsStream(Integer firstResult, Integer maxResults) {
return cacheSession.getClients(this, firstResult, maxResults); return cacheSession.getClientsStream(this, firstResult, maxResults);
} }
@Override @Override
@ -1022,18 +1023,18 @@ public class RealmAdapter implements CachedRealmModel {
} }
@Override @Override
public Set<RoleModel> getRoles() { public Stream<RoleModel> getRolesStream() {
return cacheSession.getRealmRoles(this); return cacheSession.getRealmRolesStream(this);
} }
@Override @Override
public Set<RoleModel> getRoles(Integer first, Integer max) { public Stream<RoleModel> getRolesStream(Integer first, Integer max) {
return cacheSession.getRealmRoles(this, first, max); return cacheSession.getRealmRolesStream(this, first, max);
} }
@Override @Override
public Set<RoleModel> searchForRoles(String search, Integer first, Integer max) { public Stream<RoleModel> searchForRolesStream(String search, Integer first, Integer max) {
return cacheSession.searchForRoles(this, search, first, max); return cacheSession.searchForRolesStream(this, search, first, max);
} }
@Override @Override

View file

@ -31,6 +31,8 @@ import org.keycloak.storage.StorageId;
import org.keycloak.storage.client.ClientStorageProviderModel; import org.keycloak.storage.client.ClientStorageProviderModel;
import java.util.*; import java.util.*;
import java.util.stream.Collectors;
import java.util.stream.Stream;
/** /**
@ -538,18 +540,18 @@ public class RealmCacheSession implements CacheRealmProvider {
} }
@Override @Override
public List<ClientModel> getClients(RealmModel realm, Integer firstResult, Integer maxResults) { public Stream<ClientModel> getClientsStream(RealmModel realm, Integer firstResult, Integer maxResults) {
return getClientDelegate().getClients(realm, firstResult, maxResults); return getClientDelegate().getClientsStream(realm, firstResult, maxResults);
} }
@Override @Override
public List<ClientModel> getClients(RealmModel realm) { public Stream<ClientModel> getClientsStream(RealmModel realm) {
return getClientDelegate().getClients(realm); return getClientDelegate().getClientsStream(realm);
} }
@Override @Override
public List<ClientModel> getAlwaysDisplayInConsoleClients(RealmModel realm) { public Stream<ClientModel> getAlwaysDisplayInConsoleClientsStream(RealmModel realm) {
return getClientDelegate().getAlwaysDisplayInConsoleClients(realm); return getClientDelegate().getAlwaysDisplayInConsoleClientsStream(realm);
} }
@Override @Override
@ -604,11 +606,11 @@ public class RealmCacheSession implements CacheRealmProvider {
} }
@Override @Override
public Set<RoleModel> getRealmRoles(RealmModel realm) { public Stream<RoleModel> getRealmRolesStream(RealmModel realm) {
String cacheKey = getRolesCacheKey(realm.getId()); String cacheKey = getRolesCacheKey(realm.getId());
boolean queryDB = invalidations.contains(cacheKey) || listInvalidations.contains(realm.getId()); boolean queryDB = invalidations.contains(cacheKey) || listInvalidations.contains(realm.getId());
if (queryDB) { if (queryDB) {
return getRealmDelegate().getRealmRoles(realm); return getRealmDelegate().getRealmRolesStream(realm);
} }
RoleListQuery query = cache.get(cacheKey, RoleListQuery.class); RoleListQuery query = cache.get(cacheKey, RoleListQuery.class);
@ -618,33 +620,33 @@ public class RealmCacheSession implements CacheRealmProvider {
if (query == null) { if (query == null) {
Long loaded = cache.getCurrentRevision(cacheKey); Long loaded = cache.getCurrentRevision(cacheKey);
// intentionally using deprecated method here because role ids needs to be collected for cashing while the stream returned
Set<RoleModel> model = getRealmDelegate().getRealmRoles(realm); Set<RoleModel> model = getRealmDelegate().getRealmRoles(realm);
if (model == null) return null; if (model == null) return null;
Set<String> ids = new HashSet<>(); Set<String> ids = model.stream().map(RoleModel::getId).collect(Collectors.toSet());
for (RoleModel role : model) ids.add(role.getId());
query = new RoleListQuery(loaded, cacheKey, realm, ids); query = new RoleListQuery(loaded, cacheKey, realm, ids);
logger.tracev("adding realm roles cache miss: realm {0} key {1}", realm.getName(), cacheKey); logger.tracev("adding realm roles cache miss: realm {0} key {1}", realm.getName(), cacheKey);
cache.addRevisioned(query, startupRevision); cache.addRevisioned(query, startupRevision);
return model; return model.stream();
} }
Set<RoleModel> list = new HashSet<>(); Set<RoleModel> list = new HashSet<>();
for (String id : query.getRoles()) { for (String id : query.getRoles()) {
RoleModel role = session.realms().getRoleById(id, realm); RoleModel role = session.realms().getRoleById(id, realm);
if (role == null) { if (role == null) {
invalidations.add(cacheKey); invalidations.add(cacheKey);
return getRealmDelegate().getRealmRoles(realm); return getRealmDelegate().getRealmRolesStream(realm);
} }
list.add(role); list.add(role);
} }
return list; return list.stream();
} }
@Override @Override
public Set<RoleModel> getClientRoles(RealmModel realm, ClientModel client) { public Stream<RoleModel> getClientRolesStream(RealmModel realm, ClientModel client) {
String cacheKey = getRolesCacheKey(client.getId()); String cacheKey = getRolesCacheKey(client.getId());
boolean queryDB = invalidations.contains(cacheKey) || listInvalidations.contains(client.getId()) || listInvalidations.contains(realm.getId()); boolean queryDB = invalidations.contains(cacheKey) || listInvalidations.contains(client.getId()) || listInvalidations.contains(realm.getId());
if (queryDB) { if (queryDB) {
return getRealmDelegate().getClientRoles(realm, client); return getRealmDelegate().getClientRolesStream(realm, client, null, null);
} }
RoleListQuery query = cache.get(cacheKey, RoleListQuery.class); RoleListQuery query = cache.get(cacheKey, RoleListQuery.class);
@ -654,46 +656,46 @@ public class RealmCacheSession implements CacheRealmProvider {
if (query == null) { if (query == null) {
Long loaded = cache.getCurrentRevision(cacheKey); Long loaded = cache.getCurrentRevision(cacheKey);
Set<RoleModel> model = getRealmDelegate().getClientRoles(realm, client); // intentionally using deprecated method here because role ids needs to be collected for cashing while the stream returned
Set<RoleModel> model = getRealmDelegate().getClientRoles(realm, client, null, null);
if (model == null) return null; if (model == null) return null;
Set<String> ids = new HashSet<>(); Set<String> ids = model.stream().map(RoleModel::getId).collect(Collectors.toSet());
for (RoleModel role : model) ids.add(role.getId());
query = new RoleListQuery(loaded, cacheKey, realm, ids, client.getClientId()); query = new RoleListQuery(loaded, cacheKey, realm, ids, client.getClientId());
logger.tracev("adding client roles cache miss: client {0} key {1}", client.getClientId(), cacheKey); logger.tracev("adding client roles cache miss: client {0} key {1}", client.getClientId(), cacheKey);
cache.addRevisioned(query, startupRevision); cache.addRevisioned(query, startupRevision);
return model; return model.stream();
} }
Set<RoleModel> list = new HashSet<>(); Set<RoleModel> list = new HashSet<>();
for (String id : query.getRoles()) { for (String id : query.getRoles()) {
RoleModel role = session.realms().getRoleById(id, realm); RoleModel role = session.realms().getRoleById(id, realm);
if (role == null) { if (role == null) {
invalidations.add(cacheKey); invalidations.add(cacheKey);
return getRealmDelegate().getClientRoles(realm, client); return getRealmDelegate().getClientRolesStream(realm, client, null, null);
} }
list.add(role); list.add(role);
} }
return list; return list.stream();
} }
@Override @Override
public Set<RoleModel> getRealmRoles(RealmModel realm, Integer first, Integer max) { public Stream<RoleModel> getRealmRolesStream(RealmModel realm, Integer first, Integer max) {
return getRealmDelegate().getRealmRoles(realm, first, max); return getRealmDelegate().getRealmRolesStream(realm, first, max);
} }
@Override @Override
public Set<RoleModel> getClientRoles(RealmModel realm, ClientModel client, Integer first, Integer max) { public Stream<RoleModel> getClientRolesStream(RealmModel realm, ClientModel client, Integer first, Integer max) {
return getRealmDelegate().getClientRoles(realm, client, first, max); return getRealmDelegate().getClientRolesStream(realm, client, first, max);
} }
@Override @Override
public Set<RoleModel> searchForClientRoles(RealmModel realm, ClientModel client, String search, Integer first, public Stream<RoleModel> searchForClientRolesStream(RealmModel realm, ClientModel client, String search, Integer first,
Integer max) { Integer max) {
return getRealmDelegate().searchForClientRoles(realm, client, search, first, max); return getRealmDelegate().searchForClientRolesStream(realm, client, search, first, max);
} }
@Override @Override
public Set<RoleModel> searchForRoles(RealmModel realm, String search, Integer first, Integer max) { public Stream<RoleModel> searchForRolesStream(RealmModel realm, String search, Integer first, Integer max) {
return getRealmDelegate().searchForRoles(realm, search, first, max); return getRealmDelegate().searchForRolesStream(realm, search, first, max);
} }
@Override @Override
@ -1128,8 +1130,8 @@ public class RealmCacheSession implements CacheRealmProvider {
} }
@Override @Override
public List<ClientModel> searchClientsByClientId(RealmModel realm, String clientId, Integer firstResult, Integer maxResults) { public Stream<ClientModel> searchClientsByClientIdStream(RealmModel realm, String clientId, Integer firstResult, Integer maxResults) {
return getClientDelegate().searchClientsByClientId(realm, clientId, firstResult, maxResults); return getClientDelegate().searchClientsByClientIdStream(realm, clientId, firstResult, maxResults);
} }
@Override @Override

View file

@ -671,18 +671,18 @@ public class ClientAdapter implements ClientModel, JpaModel<ClientEntity> {
} }
@Override @Override
public Set<RoleModel> getRoles() { public Stream<RoleModel> getRolesStream() {
return session.realms().getClientRoles(realm, this); return session.realms().getClientRolesStream(realm, this, null, null);
} }
@Override @Override
public Set<RoleModel> getRoles(Integer first, Integer max) { public Stream<RoleModel> getRolesStream(Integer first, Integer max) {
return session.realms().getClientRoles(realm, this, first, max); return session.realms().getClientRolesStream(realm, this, first, max);
} }
@Override @Override
public Set<RoleModel> searchForRoles(String search, Integer first, Integer max) { public Stream<RoleModel> searchForRolesStream(String search, Integer first, Integer max) {
return session.realms().searchForClientRoles(realm, this, search, first, max); return session.realms().searchForClientRolesStream(realm, this, search, first, max);
} }
@Override @Override

View file

@ -46,6 +46,8 @@ import javax.persistence.TypedQuery;
import java.util.*; import java.util.*;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import java.util.stream.Stream;
import org.keycloak.models.ModelException; import org.keycloak.models.ModelException;
import static org.keycloak.common.util.StackUtil.getShortStackTrace; import static org.keycloak.common.util.StackUtil.getShortStackTrace;
@ -253,17 +255,12 @@ public class JpaRealmProvider implements RealmProvider, ClientProvider {
} }
@Override @Override
public Set<RoleModel> getRealmRoles(RealmModel realm) { public Stream<RoleModel> getRealmRolesStream(RealmModel realm) {
TypedQuery<String> query = em.createNamedQuery("getRealmRoleIds", String.class); TypedQuery<String> query = em.createNamedQuery("getRealmRoleIds", String.class);
query.setParameter("realm", realm.getId()); query.setParameter("realm", realm.getId());
List<String> roles = query.getResultList(); Stream<String> roles = query.getResultStream();
if (roles.isEmpty()) return Collections.EMPTY_SET; return roles.map(realm::getRoleById);
Set<RoleModel> list = new HashSet<>();
for (String id : roles) {
list.add(session.realms().getRoleById(id, realm));
}
return Collections.unmodifiableSet(list);
} }
@Override @Override
@ -276,65 +273,49 @@ public class JpaRealmProvider implements RealmProvider, ClientProvider {
return session.realms().getRoleById(roles.get(0), realm); return session.realms().getRoleById(roles.get(0), realm);
} }
@Override @Override
public Set<RoleModel> getClientRoles(RealmModel realm, ClientModel client) { public Stream<RoleModel> getRealmRolesStream(RealmModel realm, Integer first, Integer max) {
Set<RoleModel> list = new HashSet<>();
TypedQuery<String> query = em.createNamedQuery("getClientRoleIds", String.class);
query.setParameter("client", client.getId());
List<String> roles = query.getResultList();
for (String id : roles) {
list.add(session.realms().getRoleById(id, realm));
}
return list;
}
@Override
public Set<RoleModel> getRealmRoles(RealmModel realm, Integer first, Integer max) {
TypedQuery<RoleEntity> query = em.createNamedQuery("getRealmRoles", RoleEntity.class); TypedQuery<RoleEntity> query = em.createNamedQuery("getRealmRoles", RoleEntity.class);
query.setParameter("realm", realm.getId()); query.setParameter("realm", realm.getId());
return getRoles(query, realm, first, max); return getRolesStream(query, realm, first, max);
} }
@Override @Override
public Set<RoleModel> getClientRoles(RealmModel realm, ClientModel client, Integer first, Integer max) { public Stream<RoleModel> getClientRolesStream(RealmModel realm, ClientModel client, Integer first, Integer max) {
TypedQuery<RoleEntity> query = em.createNamedQuery("getClientRoles", RoleEntity.class); TypedQuery<RoleEntity> query = em.createNamedQuery("getClientRoles", RoleEntity.class);
query.setParameter("client", client.getId()); query.setParameter("client", client.getId());
return getRoles(query, realm, first, max); return getRolesStream(query, realm, first, max);
} }
protected Set<RoleModel> getRoles(TypedQuery<RoleEntity> query, RealmModel realm, Integer first, Integer max) { protected Stream<RoleModel> getRolesStream(TypedQuery<RoleEntity> query, RealmModel realm, Integer first, Integer max) {
if(Objects.nonNull(first) && Objects.nonNull(max) if(Objects.nonNull(first) && Objects.nonNull(max)
&& first >= 0 && max >= 0) { && first >= 0 && max >= 0) {
query= query.setFirstResult(first).setMaxResults(max); query= query.setFirstResult(first).setMaxResults(max);
} }
List<RoleEntity> results = query.getResultList(); Stream<RoleEntity> results = query.getResultStream();
return results.stream() return results.map(role -> new RoleAdapter(session, realm, em, role));
.map(role -> new RoleAdapter(session, realm, em, role))
.collect(Collectors.collectingAndThen(
Collectors.toCollection(LinkedHashSet::new), Collections::unmodifiableSet));
} }
@Override @Override
public Set<RoleModel> searchForClientRoles(RealmModel realm, ClientModel client, String search, Integer first, Integer max) { public Stream<RoleModel> searchForClientRolesStream(RealmModel realm, ClientModel client, String search, Integer first, Integer max) {
TypedQuery<RoleEntity> query = em.createNamedQuery("searchForClientRoles", RoleEntity.class); TypedQuery<RoleEntity> query = em.createNamedQuery("searchForClientRoles", RoleEntity.class);
query.setParameter("client", client.getId()); query.setParameter("client", client.getId());
return searchForRoles(query, realm, search, first, max); return searchForRoles(query, realm, search, first, max);
} }
@Override @Override
public Set<RoleModel> searchForRoles(RealmModel realm, String search, Integer first, Integer max) { public Stream<RoleModel> searchForRolesStream(RealmModel realm, String search, Integer first, Integer max) {
TypedQuery<RoleEntity> query = em.createNamedQuery("searchForRealmRoles", RoleEntity.class); TypedQuery<RoleEntity> query = em.createNamedQuery("searchForRealmRoles", RoleEntity.class);
query.setParameter("realm", realm.getId()); query.setParameter("realm", realm.getId());
return searchForRoles(query, realm, search, first, max); return searchForRoles(query, realm, search, first, max);
} }
protected Set<RoleModel> searchForRoles(TypedQuery<RoleEntity> query, RealmModel realm, String search, Integer first, Integer max) { protected Stream<RoleModel> searchForRoles(TypedQuery<RoleEntity> query, RealmModel realm, String search, Integer first, Integer max) {
query.setParameter("search", "%" + search.trim().toLowerCase() + "%"); query.setParameter("search", "%" + search.trim().toLowerCase() + "%");
if(Objects.nonNull(first) && Objects.nonNull(max) if(Objects.nonNull(first) && Objects.nonNull(max)
@ -342,12 +323,9 @@ public class JpaRealmProvider implements RealmProvider, ClientProvider {
query= query.setFirstResult(first).setMaxResults(max); query= query.setFirstResult(first).setMaxResults(max);
} }
List<RoleEntity> results = query.getResultList(); Stream<RoleEntity> results = query.getResultStream();
return results.stream() return results.map(role -> new RoleAdapter(session, realm, em, role));
.map(role -> new RoleAdapter(session, realm, em, role))
.collect(Collectors.collectingAndThen(
Collectors.toSet(), Collections::unmodifiableSet));
} }
@Override @Override
@ -364,7 +342,7 @@ public class JpaRealmProvider implements RealmProvider, ClientProvider {
} }
String compositeRoleTable = JpaUtils.getTableNameForNativeQuery("COMPOSITE_ROLE", em); String compositeRoleTable = JpaUtils.getTableNameForNativeQuery("COMPOSITE_ROLE", em);
em.createNativeQuery("delete from " + compositeRoleTable + " where CHILD_ROLE = :role").setParameter("role", roleEntity).executeUpdate(); em.createNativeQuery("delete from " + compositeRoleTable + " where CHILD_ROLE = :role").setParameter("role", roleEntity).executeUpdate();
realm.getClients().forEach(c -> c.deleteScopeMapping(role)); realm.getClientsStream().forEach(c -> c.deleteScopeMapping(role));
em.createNamedQuery("deleteClientScopeRoleMappingByRole").setParameter("role", roleEntity).executeUpdate(); em.createNamedQuery("deleteClientScopeRoleMappingByRole").setParameter("role", roleEntity).executeUpdate();
int val = em.createNamedQuery("deleteGroupRoleMappingsByRole").setParameter("roleId", roleEntity.getId()).executeUpdate(); int val = em.createNamedQuery("deleteGroupRoleMappingsByRole").setParameter("roleId", roleEntity.getId()).executeUpdate();
@ -614,7 +592,12 @@ public class JpaRealmProvider implements RealmProvider, ClientProvider {
} }
@Override @Override
public List<ClientModel> getClients(RealmModel realm, Integer firstResult, Integer maxResults) { public Stream<ClientModel> getClientsStream(RealmModel realm) {
return getClientsStream(realm, null, null);
}
@Override
public Stream<ClientModel> getClientsStream(RealmModel realm, Integer firstResult, Integer maxResults) {
TypedQuery<String> query = em.createNamedQuery("getClientIdsByRealm", String.class); TypedQuery<String> query = em.createNamedQuery("getClientIdsByRealm", String.class);
if (firstResult != null && firstResult > 0) { if (firstResult != null && firstResult > 0) {
query.setFirstResult(firstResult); query.setFirstResult(firstResult);
@ -623,28 +606,18 @@ public class JpaRealmProvider implements RealmProvider, ClientProvider {
query.setMaxResults(maxResults); query.setMaxResults(maxResults);
} }
query.setParameter("realm", realm.getId()); query.setParameter("realm", realm.getId());
List<String> clients = query.getResultList(); Stream<String> clients = query.getResultStream();
if (clients.isEmpty()) return Collections.EMPTY_LIST;
List<ClientModel> list = new LinkedList<>(); return clients.map(c -> session.clients().getClientById(realm, c)).filter(Objects::nonNull);
for (String id : clients) {
ClientModel client = session.clients().getClientById(realm, id);
if (client != null) list.add(client);
}
return Collections.unmodifiableList(list);
} }
@Override @Override
public List<ClientModel> getAlwaysDisplayInConsoleClients(RealmModel realm) { public Stream<ClientModel> getAlwaysDisplayInConsoleClientsStream(RealmModel realm) {
TypedQuery<String> query = em.createNamedQuery("getAlwaysDisplayInConsoleClients", String.class); TypedQuery<String> query = em.createNamedQuery("getAlwaysDisplayInConsoleClients", String.class);
query.setParameter("realm", realm.getId()); query.setParameter("realm", realm.getId());
List<String> clients = query.getResultList(); Stream<String> clientStream = query.getResultStream();
if (clients.isEmpty()) return Collections.EMPTY_LIST;
List<ClientModel> list = new LinkedList<>(); return clientStream.map(c -> session.clients().getClientById(realm, c)).filter(Objects::nonNull);
for (String id : clients) {
ClientModel client = session.clients().getClientById(realm, id);
if (client != null) list.add(client);
}
return Collections.unmodifiableList(list);
} }
@Override @Override
@ -673,7 +646,7 @@ public class JpaRealmProvider implements RealmProvider, ClientProvider {
} }
@Override @Override
public List<ClientModel> searchClientsByClientId(RealmModel realm, String clientId, Integer firstResult, Integer maxResults) { public Stream<ClientModel> searchClientsByClientIdStream(RealmModel realm, String clientId, Integer firstResult, Integer maxResults) {
TypedQuery<String> query = em.createNamedQuery("searchClientsByClientId", String.class); TypedQuery<String> query = em.createNamedQuery("searchClientsByClientId", String.class);
if (firstResult != null && firstResult > 0) { if (firstResult != null && firstResult > 0) {
query.setFirstResult(firstResult); query.setFirstResult(firstResult);
@ -683,9 +656,8 @@ public class JpaRealmProvider implements RealmProvider, ClientProvider {
} }
query.setParameter("clientId", clientId); query.setParameter("clientId", clientId);
query.setParameter("realm", realm.getId()); query.setParameter("realm", realm.getId());
List<String> results = query.getResultList(); Stream<String> results = query.getResultStream();
if (results.isEmpty()) return Collections.EMPTY_LIST; return results.map(c -> session.clients().getClientById(realm, c));
return results.stream().map(id -> session.clients().getClientById(realm, id)).collect(Collectors.toList());
} }
@Override @Override

View file

@ -34,6 +34,7 @@ import javax.persistence.TypedQuery;
import java.util.*; import java.util.*;
import java.util.function.Predicate; import java.util.function.Predicate;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import java.util.stream.Stream;
import static java.util.Objects.nonNull; import static java.util.Objects.nonNull;
@ -829,18 +830,18 @@ public class RealmAdapter implements RealmModel, JpaModel<RealmEntity> {
} }
@Override @Override
public List<ClientModel> getClients() { public Stream<ClientModel> getClientsStream() {
return session.clients().getClients(this); return session.clients().getClientsStream(this);
} }
@Override @Override
public List<ClientModel> getClients(Integer firstResult, Integer maxResults) { public Stream<ClientModel> getClientsStream(Integer firstResult, Integer maxResults) {
return session.clients().getClients(this, firstResult, maxResults); return session.clients().getClientsStream(this, firstResult, maxResults);
} }
@Override @Override
public List<ClientModel> getAlwaysDisplayInConsoleClients() { public Stream<ClientModel> getAlwaysDisplayInConsoleClientsStream() {
return session.clients().getAlwaysDisplayInConsoleClients(this); return session.clients().getAlwaysDisplayInConsoleClientsStream(this);
} }
@Override @Override
@ -872,8 +873,8 @@ public class RealmAdapter implements RealmModel, JpaModel<RealmEntity> {
} }
@Override @Override
public List<ClientModel> searchClientByClientId(String clientId, Integer firstResult, Integer maxResults) { public Stream<ClientModel> searchClientByClientIdStream(String clientId, Integer firstResult, Integer maxResults) {
return session.clients().searchClientsByClientId(this, clientId, firstResult, maxResults); return session.clients().searchClientsByClientIdStream(this, clientId, firstResult, maxResults);
} }
private static final String BROWSER_HEADER_PREFIX = "_browser_header."; private static final String BROWSER_HEADER_PREFIX = "_browser_header.";
@ -933,18 +934,18 @@ public class RealmAdapter implements RealmModel, JpaModel<RealmEntity> {
} }
@Override @Override
public Set<RoleModel> getRoles() { public Stream<RoleModel> getRolesStream() {
return session.realms().getRealmRoles(this); return session.realms().getRealmRolesStream(this);
} }
@Override @Override
public Set<RoleModel> getRoles(Integer first, Integer max) { public Stream<RoleModel> getRolesStream(Integer first, Integer max) {
return session.realms().getRealmRoles(this, first, max); return session.realms().getRealmRolesStream(this, first, max);
} }
@Override @Override
public Set<RoleModel> searchForRoles(String search, Integer first, Integer max) { public Stream<RoleModel> searchForRolesStream(String search, Integer first, Integer max) {
return session.realms().searchForRoles(this, search, first, max); return session.realms().searchForRolesStream(this, search, first, max);
} }
@Override @Override

View file

@ -25,6 +25,7 @@ import org.keycloak.models.map.common.AbstractEntity;
import java.util.Objects; import java.util.Objects;
import java.util.Set; import java.util.Set;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import java.util.stream.Stream;
/** /**
* *
@ -81,18 +82,18 @@ public abstract class AbstractClientModel<E extends AbstractEntity> implements C
} }
@Override @Override
public Set<RoleModel> getRoles() { public Stream<RoleModel> getRolesStream() {
return session.realms().getClientRoles(realm, this); return session.realms().getClientRolesStream(realm, this, null, null);
} }
@Override @Override
public Set<RoleModel> getRoles(Integer firstResult, Integer maxResults) { public Stream<RoleModel> getRolesStream(Integer firstResult, Integer maxResults) {
return session.realms().getClientRoles(realm, this, firstResult, maxResults); return session.realms().getClientRolesStream(realm, this, firstResult, maxResults);
} }
@Override @Override
public Set<RoleModel> searchForRoles(String search, Integer first, Integer max) { public Stream<RoleModel> searchForRolesStream(String search, Integer first, Integer max) {
return session.realms().searchForClientRoles(realm, this, search, first, max); return session.realms().searchForClientRolesStream(realm, this, search, first, max);
} }
@Override @Override

View file

@ -29,9 +29,7 @@ import org.keycloak.models.RealmProvider;
import org.keycloak.models.RoleModel; import org.keycloak.models.RoleModel;
import org.keycloak.models.map.storage.MapKeycloakTransaction; import org.keycloak.models.map.storage.MapKeycloakTransaction;
import org.keycloak.models.map.common.Serialization; import org.keycloak.models.map.common.Serialization;
import java.util.Collections;
import java.util.Comparator; import java.util.Comparator;
import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Objects; import java.util.Objects;
import java.util.UUID; import java.util.UUID;
@ -134,15 +132,15 @@ public class MapClientProvider implements ClientProvider {
} }
@Override @Override
public List<ClientModel> getClients(RealmModel realm, Integer firstResult, Integer maxResults) { public Stream<ClientModel> getClientsStream(RealmModel realm, Integer firstResult, Integer maxResults) {
Stream<ClientModel> s = getClientsStream(realm); Stream<ClientModel> s = getClientsStream(realm);
if (firstResult >= 0) { if (firstResult != null && firstResult >= 0) {
s = s.skip(firstResult); s = s.skip(firstResult);
} }
if (maxResults >= 0) { if (maxResults != null && maxResults >= 0) {
s = s.limit(maxResults); s = s.limit(maxResults);
} }
return s.collect(Collectors.toList()); return s;
} }
private Stream<MapClientEntity> getNotRemovedUpdatedClientsStream() { private Stream<MapClientEntity> getNotRemovedUpdatedClientsStream() {
@ -152,7 +150,7 @@ public class MapClientProvider implements ClientProvider {
return Stream.concat(tx.createdValuesStream(clientStore.keySet()), updatedAndNotRemovedClientsStream); return Stream.concat(tx.createdValuesStream(clientStore.keySet()), updatedAndNotRemovedClientsStream);
} }
// @Override @Override
public Stream<ClientModel> getClientsStream(RealmModel realm) { public Stream<ClientModel> getClientsStream(RealmModel realm) {
return getNotRemovedUpdatedClientsStream() return getNotRemovedUpdatedClientsStream()
.filter(entityRealmFilter(realm)) .filter(entityRealmFilter(realm))
@ -161,11 +159,6 @@ public class MapClientProvider implements ClientProvider {
; ;
} }
@Override
public List<ClientModel> getClients(RealmModel realm) {
return getClientsStream(realm).collect(Collectors.toList());
}
@Override @Override
public ClientModel addClient(RealmModel realm, String id, String clientId) { public ClientModel addClient(RealmModel realm, String id, String clientId) {
final UUID entityId = id == null ? UUID.randomUUID() : UUID.fromString(id); final UUID entityId = id == null ? UUID.randomUUID() : UUID.fromString(id);
@ -194,10 +187,9 @@ public class MapClientProvider implements ClientProvider {
} }
@Override @Override
public List<ClientModel> getAlwaysDisplayInConsoleClients(RealmModel realm) { public Stream<ClientModel> getAlwaysDisplayInConsoleClientsStream(RealmModel realm) {
return getClientsStream(realm) return getClientsStream(realm)
.filter(ClientModel::isAlwaysDisplayInConsole) .filter(ClientModel::isAlwaysDisplayInConsole);
.collect(Collectors.toList());
} }
@Override @Override
@ -285,9 +277,9 @@ public class MapClientProvider implements ClientProvider {
} }
@Override @Override
public List<ClientModel> searchClientsByClientId(RealmModel realm, String clientId, Integer firstResult, Integer maxResults) { public Stream<ClientModel> searchClientsByClientIdStream(RealmModel realm, String clientId, Integer firstResult, Integer maxResults) {
if (clientId == null) { if (clientId == null) {
return Collections.EMPTY_LIST; return Stream.empty();
} }
String clientIdLower = clientId.toLowerCase(); String clientIdLower = clientId.toLowerCase();
Stream<MapClientEntity> s = getNotRemovedUpdatedClientsStream() Stream<MapClientEntity> s = getNotRemovedUpdatedClientsStream()
@ -295,17 +287,14 @@ public class MapClientProvider implements ClientProvider {
.filter(entity -> entity.getClientId() != null && entity.getClientId().toLowerCase().contains(clientIdLower)) .filter(entity -> entity.getClientId() != null && entity.getClientId().toLowerCase().contains(clientIdLower))
.sorted(COMPARE_BY_CLIENT_ID); .sorted(COMPARE_BY_CLIENT_ID);
if (firstResult >= 0) { if (firstResult != null && firstResult >= 0) {
s = s.skip(firstResult); s = s.skip(firstResult);
} }
if (maxResults >= 0) { if (maxResults != null && maxResults >= 0) {
s = s.limit(maxResults); s = s.limit(maxResults);
} }
return s return s.map(entityToAdapterFunc(realm));
.map(entityToAdapterFunc(realm))
.collect(Collectors.toList())
;
} }
@Override @Override

View file

@ -22,7 +22,7 @@ import org.keycloak.models.RoleModel;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.Set; import java.util.stream.Stream;
/** /**
* Base helper class. Unsupported operations are implemented here that throw exception on invocation. * Base helper class. Unsupported operations are implemented here that throw exception on invocation.
@ -52,18 +52,18 @@ public abstract class UnsupportedOperationsClientStorageAdapter implements Clien
} }
@Override @Override
public final Set<RoleModel> getRoles() { public final Stream<RoleModel> getRolesStream() {
return Collections.EMPTY_SET; return Stream.empty();
} }
@Override @Override
public final Set<RoleModel> getRoles(Integer first, Integer max) { public final Stream<RoleModel> getRolesStream(Integer first, Integer max) {
return Collections.EMPTY_SET; return Stream.empty();
} }
@Override @Override
public final Set<RoleModel> searchForRoles(String search, Integer first, Integer max) { public final Stream<RoleModel> searchForRolesStream(String search, Integer first, Integer max) {
return Collections.EMPTY_SET; return Stream.empty();
} }
@Override @Override

View file

@ -17,7 +17,6 @@
package org.keycloak.models; package org.keycloak.models;
import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Set; import java.util.Set;

View file

@ -20,6 +20,8 @@ import org.keycloak.provider.Provider;
import org.keycloak.storage.client.ClientLookupProvider; import org.keycloak.storage.client.ClientLookupProvider;
import java.util.List; import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.Stream;
/** /**
* Provider of the client records. * Provider of the client records.
@ -31,21 +33,46 @@ public interface ClientProvider extends ClientLookupProvider, Provider {
/** /**
* Returns the clients of the given realm. * Returns the clients of the given realm.
* @deprecated use the stream variant instead
* @param realm Realm. * @param realm Realm.
* @param firstResult First result to return. Ignored if negative or {@code null}. * @param firstResult First result to return. Ignored if negative or {@code null}.
* @param maxResults Maximim number of results to return. Ignored if negative or {@code null}. * @param maxResults Maximum number of results to return. Ignored if negative or {@code null}.
* @return List of the clients. Never returns {@code null}. * @return List of the clients. Never returns {@code null}.
*/ */
List<ClientModel> getClients(RealmModel realm, Integer firstResult, Integer maxResults); @Deprecated
default List<ClientModel> getClients(RealmModel realm, Integer firstResult, Integer maxResults) {
return getClientsStream(realm, firstResult, maxResults).collect(Collectors.toList());
}
/**
* Returns the clients of the given realm as a stream.
* @param realm Realm.
* @param firstResult First result to return. Ignored if negative or {@code null}.
* @param maxResults Maximum number of results to return. Ignored if negative or {@code null}.
* @return Stream of the clients. Never returns {@code null}.
*/
Stream<ClientModel> getClientsStream(RealmModel realm, Integer firstResult, Integer maxResults);
/** /**
* Returns all the clients of the given realm. * Returns all the clients of the given realm.
* @deprecated use the stream variant instead
* Effectively the same as the call {@code getClients(realm, null, null)}. * Effectively the same as the call {@code getClients(realm, null, null)}.
* @param realm Realm. * @param realm Realm.
* @return List of the clients. Never returns {@code null}. * @return List of the clients. Never returns {@code null}.
*/ */
@Deprecated
default List<ClientModel> getClients(RealmModel realm) { default List<ClientModel> getClients(RealmModel realm) {
return this.getClients(realm, null, null); return this.getClientsStream(realm, null, null).collect(Collectors.toList());
}
/**
* Returns all the clients of the given realm as a stream.
* Effectively the same as the call {@code getClientsStream(realm, null, null)}.
* @param realm Realm.
* @return Stream of the clients. Never returns {@code null}.
*/
default Stream<ClientModel> getClientsStream(RealmModel realm) {
return this.getClientsStream(realm, null, null);
} }
/** /**
@ -81,10 +108,21 @@ public interface ClientProvider extends ClientLookupProvider, Provider {
/** /**
* Returns a list of clients that are expected to always show up in account console. * Returns a list of clients that are expected to always show up in account console.
* @deprecated use the stream variant instead
* @param realm Realm owning the clients. * @param realm Realm owning the clients.
* @return List of the clients. Never returns {@code null}. * @return List of the clients. Never returns {@code null}.
*/ */
List<ClientModel> getAlwaysDisplayInConsoleClients(RealmModel realm); @Deprecated
default List<ClientModel> getAlwaysDisplayInConsoleClients(RealmModel realm) {
return getAlwaysDisplayInConsoleClientsStream(realm).collect(Collectors.toList());
}
/**
* Returns a stream of clients that are expected to always show up in account console.
* @param realm Realm owning the clients.
* @return Stream of the clients. Never returns {@code null}.
*/
Stream<ClientModel> getAlwaysDisplayInConsoleClientsStream(RealmModel realm);
/** /**
* Removes given client from the given realm. * Removes given client from the given realm.

View file

@ -26,6 +26,8 @@ import org.keycloak.storage.client.ClientStorageProvider;
import org.keycloak.storage.client.ClientStorageProviderModel; import org.keycloak.storage.client.ClientStorageProviderModel;
import java.util.*; import java.util.*;
import java.util.stream.Collectors;
import java.util.stream.Stream;
/** /**
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a> * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
@ -284,11 +286,28 @@ public interface RealmModel extends RoleContainerModel {
void removeDefaultGroup(GroupModel group); void removeDefaultGroup(GroupModel group);
List<ClientModel> getClients(); @Deprecated
List<ClientModel> getClients(Integer firstResult, Integer maxResults); default List<ClientModel> getClients() {
return getClientsStream(null, null).collect(Collectors.toList());
}
Stream<ClientModel> getClientsStream();
@Deprecated
default List<ClientModel> getClients(Integer firstResult, Integer maxResults) {
return getClientsStream(firstResult, maxResults).collect(Collectors.toList());
}
Stream<ClientModel> getClientsStream(Integer firstResult, Integer maxResults);
Long getClientsCount(); Long getClientsCount();
List<ClientModel> getAlwaysDisplayInConsoleClients(); @Deprecated
default List<ClientModel> getAlwaysDisplayInConsoleClients() {
return getAlwaysDisplayInConsoleClientsStream().collect(Collectors.toList());
}
Stream<ClientModel> getAlwaysDisplayInConsoleClientsStream();
ClientModel addClient(String name); ClientModel addClient(String name);
@ -298,7 +317,13 @@ public interface RealmModel extends RoleContainerModel {
ClientModel getClientById(String id); ClientModel getClientById(String id);
ClientModel getClientByClientId(String clientId); ClientModel getClientByClientId(String clientId);
List<ClientModel> searchClientByClientId(String clientId, Integer firstResult, Integer maxResults);
@Deprecated
default List<ClientModel> searchClientByClientId(String clientId, Integer firstResult, Integer maxResults) {
return searchClientByClientIdStream(clientId, firstResult, maxResults).collect(Collectors.toList());
}
Stream<ClientModel> searchClientByClientIdStream(String clientId, Integer firstResult, Integer maxResults);
void updateRequiredCredentials(Set<String> creds); void updateRequiredCredentials(Set<String> creds);

View file

@ -22,6 +22,8 @@ import org.keycloak.provider.Provider;
import java.util.List; import java.util.List;
import java.util.Set; import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.Stream;
/** /**
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a> * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
@ -81,16 +83,44 @@ public interface RealmProvider extends Provider /* TODO: Remove in future versio
RoleModel getRealmRole(RealmModel realm, String name); RoleModel getRealmRole(RealmModel realm, String name);
Set<RoleModel> getRealmRoles(RealmModel realm); // TODO switch all usages to the stream variant
@Deprecated
default Set<RoleModel> getRealmRoles(RealmModel realm) {
return getRealmRolesStream(realm).collect(Collectors.toSet());
}
Set<RoleModel> getRealmRoles(RealmModel realm, Integer first, Integer max); Stream<RoleModel> getRealmRolesStream(RealmModel realm);
Set<RoleModel> getClientRoles(RealmModel realm, ClientModel client, Integer first, Integer max); @Deprecated
default Set<RoleModel> getRealmRoles(RealmModel realm, Integer first, Integer max) {
return getRealmRolesStream(realm, first, max).collect(Collectors.toSet());
}
Set<RoleModel> searchForClientRoles(RealmModel realm, ClientModel client, String search, Integer first, Stream<RoleModel> getRealmRolesStream(RealmModel realm, Integer first, Integer max);
Integer max);
Set<RoleModel> searchForRoles(RealmModel realm, String search, Integer first, Integer max); // TODO switch all usages to the stream variant
@Deprecated
default Set<RoleModel> getClientRoles(RealmModel realm, ClientModel client, Integer first, Integer max) {
return getClientRolesStream(realm, client, first, max).collect(Collectors.toSet());
}
Stream<RoleModel> getClientRolesStream(RealmModel realm, ClientModel client, Integer first, Integer max);
@Deprecated
default Set<RoleModel> searchForClientRoles(RealmModel realm, ClientModel client, String search, Integer first,
Integer max) {
return searchForClientRolesStream(realm, client, search, first, max).collect(Collectors.toSet());
}
Stream<RoleModel> searchForClientRolesStream(RealmModel realm, ClientModel client, String search, Integer first,
Integer max);
@Deprecated
default Set<RoleModel> searchForRoles(RealmModel realm, String search, Integer first, Integer max) {
return searchForRolesStream(realm, search, first, max).collect(Collectors.toSet());
}
Stream<RoleModel> searchForRolesStream(RealmModel realm, String search, Integer first, Integer max);
boolean removeRole(RealmModel realm, RoleModel role); boolean removeRole(RealmModel realm, RoleModel role);
@ -114,7 +144,9 @@ public interface RealmProvider extends Provider /* TODO: Remove in future versio
/** /**
* TODO: To be @deprecated Use the corresponding method from {@link ??RoleProvider}. */ * TODO: To be @deprecated Use the corresponding method from {@link ??RoleProvider}. */
public Set<RoleModel> getClientRoles(RealmModel realm, ClientModel client); default public Stream<RoleModel> getClientRolesStream(RealmModel realm, ClientModel client) {
return getClientRolesStream(realm, client, null, null);
}
/** /**
* TODO: To be @deprecated Use the corresponding method from {@link ??RoleProvider}. */ * TODO: To be @deprecated Use the corresponding method from {@link ??RoleProvider}. */
@ -151,16 +183,11 @@ public interface RealmProvider extends Provider /* TODO: Remove in future versio
return this.getClients(realm, null, null); return this.getClients(realm, null, null);
} }
/**
* @deprecated Use the corresponding method from {@link ClientProvider}. */
@Override
public List<ClientModel> getClients(RealmModel realm, Integer firstResult, Integer maxResults);
/** /**
* @deprecated Use the corresponding method from {@link ClientProvider}. */ * @deprecated Use the corresponding method from {@link ClientProvider}. */
@Override @Override
default List<ClientModel> searchClientsByClientId(String clientId, Integer firstResult, Integer maxResults, RealmModel realm) { default List<ClientModel> searchClientsByClientId(String clientId, Integer firstResult, Integer maxResults, RealmModel realm) {
return searchClientsByClientId(realm, clientId, firstResult, maxResults); return searchClientsByClientId(clientId, firstResult, maxResults, realm);
} }
/** /**
@ -173,11 +200,6 @@ public interface RealmProvider extends Provider /* TODO: Remove in future versio
@Override @Override
default ClientModel getClientById(String id, RealmModel realm) { return getClientById(realm, id); } default ClientModel getClientById(String id, RealmModel realm) { return getClientById(realm, id); }
/**
* @deprecated Use the corresponding method from {@link ClientProvider}. */
@Override
public List<ClientModel> searchClientsByClientId(RealmModel realm, String clientId, Integer firstResult, Integer maxResults);
/** /**
* @deprecated Use the corresponding method from {@link ClientProvider}. */ * @deprecated Use the corresponding method from {@link ClientProvider}. */
@Override @Override
@ -196,10 +218,4 @@ public interface RealmProvider extends Provider /* TODO: Remove in future versio
/** /**
* @deprecated Use the corresponding method from {@link ClientProvider}. */ * @deprecated Use the corresponding method from {@link ClientProvider}. */
default boolean removeClient(String id, RealmModel realm) { return this.removeClient(realm, id); } default boolean removeClient(String id, RealmModel realm) { return this.removeClient(realm, id); }
/**
* @deprecated Use the corresponding method from {@link ClientProvider}. */
@Override
public List<ClientModel> getAlwaysDisplayInConsoleClients(RealmModel realm);
} }

View file

@ -25,6 +25,8 @@ import java.util.Collection;
import java.util.HashSet; import java.util.HashSet;
import java.util.List; import java.util.List;
import java.util.Set; import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.Stream;
/** /**
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a> * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
@ -47,11 +49,29 @@ public interface RoleContainerModel {
boolean removeRole(RoleModel role); boolean removeRole(RoleModel role);
Set<RoleModel> getRoles(); // TODO switch all usages to the stream variant
@Deprecated
default Set<RoleModel> getRoles() {
return getRolesStream().collect(Collectors.toSet());
}
Set<RoleModel> getRoles(Integer firstResult, Integer maxResults); Stream<RoleModel> getRolesStream();
Set<RoleModel> searchForRoles(String search, Integer first, Integer max); // TODO switch all usages to the stream variant
@Deprecated
default Set<RoleModel> getRoles(Integer firstResult, Integer maxResults) {
return getRolesStream(firstResult, maxResults).collect(Collectors.toSet());
}
Stream<RoleModel> getRolesStream(Integer firstResult, Integer maxResults);
// TODO switch all usages to the stream variant
@Deprecated
default Set<RoleModel> searchForRoles(String search, Integer first, Integer max) {
return searchForRolesStream(search, first, max).collect(Collectors.toSet());
}
Stream<RoleModel> searchForRolesStream(String search, Integer first, Integer max);
List<String> getDefaultRoles(); List<String> getDefaultRoles();

View file

@ -20,6 +20,8 @@ import org.keycloak.models.ClientModel;
import org.keycloak.models.RealmModel; import org.keycloak.models.RealmModel;
import java.util.List; import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.Stream;
/** /**
* Abstraction interface for lookoup of clients by id and clientId. These methods required for participating in login flows. * Abstraction interface for lookoup of clients by id and clientId. These methods required for participating in login flows.
@ -71,10 +73,14 @@ public interface ClientLookupProvider {
* @param clientId Searched substring of the public client * @param clientId Searched substring of the public client
* identifier ({@code client_id} in OIDC or {@code entityID} in SAML.) * identifier ({@code client_id} in OIDC or {@code entityID} in SAML.)
* @param firstResult First result to return. Ignored if negative or {@code null}. * @param firstResult First result to return. Ignored if negative or {@code null}.
* @param maxResults Maximim number of results to return. Ignored if negative or {@code null}. * @param maxResults Maximum number of results to return. Ignored if negative or {@code null}.
* @return Model of the client, or {@code null} if no client is found. * @return List of ClientModel or an empty list if no client is found.
* @deprecated Use {@link #searchClientsByClientIdStream(org.keycloak.models.RealmModel, java.lang.String, java.lang.Integer, java.lang.Integer)} instead.
*/ */
List<ClientModel> searchClientsByClientId(RealmModel realm, String clientId, Integer firstResult, Integer maxResults); @Deprecated
default List<ClientModel> searchClientsByClientId(String clientId, Integer firstResult, Integer maxResults, RealmModel realm) {
return searchClientsByClientIdStream(realm, clientId, firstResult, maxResults).collect(Collectors.toList());
}
/** /**
* Case-insensitive search for clients that contain the given string in their public client identifier. * Case-insensitive search for clients that contain the given string in their public client identifier.
@ -82,11 +88,8 @@ public interface ClientLookupProvider {
* @param clientId Searched substring of the public client * @param clientId Searched substring of the public client
* identifier ({@code client_id} in OIDC or {@code entityID} in SAML.) * identifier ({@code client_id} in OIDC or {@code entityID} in SAML.)
* @param firstResult First result to return. Ignored if negative or {@code null}. * @param firstResult First result to return. Ignored if negative or {@code null}.
* @param maxResults Maximim number of results to return. Ignored if negative or {@code null}. * @param maxResults Maximum number of results to return. Ignored if negative or {@code null}.
* @return Models of the matching clients. Never returns {@code null}. * @return Stream of ClientModel or an empty stream if no client is found.
* @deprecated Use {@link #searchClientsByClientId(org.keycloak.models.RealmModel, java.lang.String, java.lang.Integer, java.lang.Integer)} instead.
*/ */
default List<ClientModel> searchClientsByClientId(String clientId, Integer firstResult, Integer maxResults, RealmModel realm) { Stream<ClientModel> searchClientsByClientIdStream(RealmModel realm, String clientId, Integer firstResult, Integer maxResults);
return searchClientsByClientId(realm, clientId, firstResult, maxResults);
}
} }

View file

@ -178,7 +178,9 @@ public class DefaultKeycloakSession implements KeycloakSession {
@Override @Override
public ClientProvider clientStorageManager() { public ClientProvider clientStorageManager() {
if (clientStorageManager == null) clientStorageManager = new ClientStorageManager(this); if (clientStorageManager == null) {
clientStorageManager = new ClientStorageManager(this, factory.getClientStorageProviderTimeout());
}
return clientStorageManager; return clientStorageManager;
} }

View file

@ -59,6 +59,12 @@ public class DefaultKeycloakSessionFactory implements KeycloakSessionFactory, Pr
// TODO: Likely should be changed to int and use Time.currentTime() to be compatible with all our "time" reps // TODO: Likely should be changed to int and use Time.currentTime() to be compatible with all our "time" reps
protected long serverStartupTimestamp; protected long serverStartupTimestamp;
/**
* Timeout is used as time boundary for obtaining clients from an external client storage. Default value is set
* to 3000 milliseconds and it's configurable.
*/
private long clientStorageProviderTimeout;
@Override @Override
public void register(ProviderEventListener listener) { public void register(ProviderEventListener listener) {
listeners.add(listener); listeners.add(listener);
@ -79,6 +85,8 @@ public class DefaultKeycloakSessionFactory implements KeycloakSessionFactory, Pr
public void init() { public void init() {
serverStartupTimestamp = System.currentTimeMillis(); serverStartupTimestamp = System.currentTimeMillis();
clientStorageProviderTimeout = Config.scope("client").getLong("storageProviderTimeout", 3000L);
ProviderManager pm = new ProviderManager(KeycloakDeploymentInfo.create().services(), getClass().getClassLoader(), Config.scope().getArray("providers")); ProviderManager pm = new ProviderManager(KeycloakDeploymentInfo.create().services(), getClass().getClassLoader(), Config.scope().getArray("providers"));
spis.addAll(pm.loadSpis()); spis.addAll(pm.loadSpis());
factoriesMap = loadFactories(pm); factoriesMap = loadFactories(pm);
@ -351,6 +359,10 @@ public class DefaultKeycloakSessionFactory implements KeycloakSessionFactory, Pr
return packageName.startsWith("org.keycloak") && !packageName.startsWith("org.keycloak.examples"); return packageName.startsWith("org.keycloak") && !packageName.startsWith("org.keycloak.examples");
} }
public long getClientStorageProviderTimeout() {
return clientStorageProviderTimeout;
}
/** /**
* @return timestamp of Keycloak server startup * @return timestamp of Keycloak server startup
*/ */

View file

@ -28,9 +28,11 @@ import org.keycloak.storage.client.ClientLookupProvider;
import org.keycloak.storage.client.ClientStorageProvider; import org.keycloak.storage.client.ClientStorageProvider;
import org.keycloak.storage.client.ClientStorageProviderFactory; import org.keycloak.storage.client.ClientStorageProviderFactory;
import org.keycloak.storage.client.ClientStorageProviderModel; import org.keycloak.storage.client.ClientStorageProviderModel;
import org.keycloak.utils.ServicesUtils;
import java.util.LinkedList; import java.util.LinkedList;
import java.util.List; import java.util.List;
import java.util.stream.Stream;
/** /**
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a> * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
@ -41,6 +43,8 @@ public class ClientStorageManager implements ClientProvider {
protected KeycloakSession session; protected KeycloakSession session;
private long clientStorageProviderTimeout;
public static boolean isStorageProviderEnabled(RealmModel realm, String providerId) { public static boolean isStorageProviderEnabled(RealmModel realm, String providerId) {
ClientStorageProviderModel model = getStorageProviderModel(realm, providerId); ClientStorageProviderModel model = getStorageProviderModel(realm, providerId);
return model.isEnabled(); return model.isEnabled();
@ -118,8 +122,9 @@ public class ClientStorageManager implements ClientProvider {
} }
public ClientStorageManager(KeycloakSession session) { public ClientStorageManager(KeycloakSession session, long clientStorageProviderTimeout) {
this.session = session; this.session = session;
this.clientStorageProviderTimeout = clientStorageProviderTimeout;
} }
@Override @Override
@ -147,17 +152,22 @@ public class ClientStorageManager implements ClientProvider {
return null; return null;
} }
/**
* Obtaining clients from an external client storage is time-bounded. In case the external client storage
* isn't available at least clients from a local storage are returned. For this purpose
* the {@link org.keycloak.services.DefaultKeycloakSessionFactory#getClientStorageProviderTimeout()} property is used.
* Default value is 3000 milliseconds and it's configurable.
* See {@link org.keycloak.services.DefaultKeycloakSessionFactory} for details.
*/
@Override @Override
public List<ClientModel> searchClientsByClientId(RealmModel realm, String clientId, Integer firstResult, Integer maxResults) { public Stream<ClientModel> searchClientsByClientIdStream(RealmModel realm, String clientId, Integer firstResult, Integer maxResults) {
List<ClientModel> clients = session.clientLocalStorage().searchClientsByClientId(realm, clientId, firstResult, maxResults); Stream<ClientModel> local = session.clientLocalStorage().searchClientsByClientIdStream(realm, clientId, firstResult, maxResults);
if (clients != null) { Stream<ClientModel> ext = getEnabledStorageProviders(session, realm, ClientLookupProvider.class).stream()
return clients; .flatMap(ServicesUtils.timeBound(session,
} clientStorageProviderTimeout,
for (ClientLookupProvider provider : getEnabledStorageProviders(session, realm, ClientLookupProvider.class)) { p -> ((ClientLookupProvider) p).searchClientsByClientIdStream(realm, clientId, firstResult, maxResults)));
clients = provider.searchClientsByClientId(realm, clientId, firstResult, maxResults);
if (clients != null) return clients; return Stream.concat(local, ext);
}
return null;
} }
@Override @Override
@ -171,13 +181,13 @@ public class ClientStorageManager implements ClientProvider {
} }
@Override @Override
public List<ClientModel> getClients(RealmModel realm, Integer firstResult, Integer maxResults) { public Stream<ClientModel> getClientsStream(RealmModel realm, Integer firstResult, Integer maxResults) {
return session.clientLocalStorage().getClients(realm, firstResult, maxResults); return session.clientLocalStorage().getClientsStream(realm, firstResult, maxResults);
} }
@Override @Override
public List<ClientModel> getClients(RealmModel realm) { public Stream<ClientModel> getClientsStream(RealmModel realm) {
return session.clientLocalStorage().getClients(realm); return session.clientLocalStorage().getClientsStream(realm);
} }
@Override @Override
@ -186,8 +196,8 @@ public class ClientStorageManager implements ClientProvider {
} }
@Override @Override
public List<ClientModel> getAlwaysDisplayInConsoleClients(RealmModel realm) { public Stream<ClientModel> getAlwaysDisplayInConsoleClientsStream(RealmModel realm) {
return session.clientLocalStorage().getAlwaysDisplayInConsoleClients(realm); return session.clientLocalStorage().getAlwaysDisplayInConsoleClientsStream(realm);
} }
@Override @Override

View file

@ -26,9 +26,8 @@ import org.keycloak.storage.StorageId;
import org.keycloak.storage.client.ClientStorageProvider; import org.keycloak.storage.client.ClientStorageProvider;
import org.keycloak.storage.client.ClientStorageProviderModel; import org.keycloak.storage.client.ClientStorageProviderModel;
import java.util.Collections;
import java.util.List;
import java.util.regex.Matcher; import java.util.regex.Matcher;
import java.util.stream.Stream;
/** /**
* @author <a href="mailto:psilva@redhat.com">Pedro Igor</a> * @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
@ -76,9 +75,9 @@ public class OpenshiftClientStorageProvider implements ClientStorageProvider {
} }
@Override @Override
public List<ClientModel> searchClientsByClientId(RealmModel realm, String clientId, Integer firstResult, Integer maxResults) { public Stream<ClientModel> searchClientsByClientIdStream(RealmModel realm, String clientId, Integer firstResult, Integer maxResults) {
// TODO not sure about this, but I don't see this implementation using the search now // TODO not sure about this, but I don't see this implementation using the search now
return Collections.singletonList(getClientByClientId(realm, clientId)); return Stream.of(getClientByClientId(realm, clientId));
} }
@Override @Override

View file

@ -0,0 +1,56 @@
/*
* Copyright 2020 Red Hat, Inc. and/or its affiliates
* and other contributors as indicated by the @author tags.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.keycloak.utils;
import org.jboss.logging.Logger;
import org.keycloak.executors.ExecutorsProvider;
import org.keycloak.models.KeycloakSession;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.function.Function;
import java.util.stream.Stream;
/**
* Utility class for general helper methods used across the keycloak-services.
*/
public class ServicesUtils {
private static final Logger logger = Logger.getLogger(ServicesUtils.class);
public static <T, R> Function<? super T,? extends Stream<? extends R>> timeBound(KeycloakSession session,
long timeout,
Function<T, ? extends Stream<R>> func) {
ExecutorService executor = session.getProvider(ExecutorsProvider.class).getExecutor("storage-provider-threads");
return p -> {
Callable<? extends Stream<R>> c = () -> func.apply(p);
Future<? extends Stream<R>> future = executor.submit(c);
try {
return future.get(timeout, TimeUnit.MILLISECONDS);
} catch (InterruptedException | ExecutionException | TimeoutException e) {
future.cancel(true);
logger.debug("Function failed to return on time.", e);
return Stream.empty();
}
};
}
}

View file

@ -36,7 +36,6 @@ import java.util.Collections;
import java.util.HashSet; import java.util.HashSet;
import java.util.Map; import java.util.Map;
import java.util.Set; import java.util.Set;
import java.util.List;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import java.util.stream.Stream; import java.util.stream.Stream;
@ -79,11 +78,11 @@ public class HardcodedClientStorageProvider implements ClientStorageProvider, Cl
} }
@Override @Override
public List<ClientModel> searchClientsByClientId(RealmModel realm, String clientId, Integer firstResult, Integer maxResults) { public Stream<ClientModel> searchClientsByClientIdStream(RealmModel realm, String clientId, Integer firstResult, Integer maxResults) {
if (clientId != null && this.clientId.toLowerCase().contains(clientId.toLowerCase())) { if (clientId != null && this.clientId.toLowerCase().contains(clientId.toLowerCase())) {
return Collections.singletonList(new ClientAdapter(realm)); return Stream.of(new ClientAdapter(realm));
} }
return Collections.EMPTY_LIST; return Stream.empty();
} }
public class ClientAdapter extends AbstractReadOnlyClientStorageAdapter { public class ClientAdapter extends AbstractReadOnlyClientStorageAdapter {