Introduce model-entity util methods

Fixes: #9025
This commit is contained in:
Hynek Mlnarik 2021-10-26 11:55:53 +02:00 committed by Hynek Mlnařík
parent 1f9b2be65a
commit 3602873df2
3 changed files with 172 additions and 93 deletions

View file

@ -295,6 +295,24 @@ public class DeepCloner {
return res; return res;
} }
/**
* Returns a class type of an instance that would be instantiated by {@link #newInstance(java.lang.Class)} method.
* @param <V> Type (class or a {@code @Root} interface) to create a new instance
* @param clazz Type (class or a {@code @Root} interface) to create a new instance
* @return See description
*/
public <V> Class<? extends V> newInstanceType(Class<V> valueType) {
if (valueType == null) {
return null;
}
try {
V v = newInstance(valueType);
return v == null ? null : (Class<? extends V>) v.getClass();
} catch (IllegalStateException ex) {
return null;
}
}
/** /**
* Deeply clones properties from the {@code from} instance to the {@code to} instance. * Deeply clones properties from the {@code from} instance to the {@code to} instance.
* @param <V> Type (class or a {@code @Root} interface) to clone the instance * @param <V> Type (class or a {@code @Root} interface) to clone the instance

View file

@ -0,0 +1,144 @@
/*
* Copyright 2021 Red Hat, Inc. and/or its affiliates
* and other contributors as indicated by the @author tags.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.keycloak.models.map.storage;
import org.keycloak.authorization.model.PermissionTicket;
import org.keycloak.authorization.model.Policy;
import org.keycloak.authorization.model.Resource;
import org.keycloak.authorization.model.ResourceServer;
import org.keycloak.models.AuthenticatedClientSessionModel;
import org.keycloak.models.ClientModel;
import org.keycloak.models.ClientScopeModel;
import org.keycloak.models.GroupModel;
import org.keycloak.models.RealmModel;
import org.keycloak.models.RoleModel;
import org.keycloak.models.UserLoginFailureModel;
import org.keycloak.models.UserModel;
import org.keycloak.models.UserSessionModel;
import org.keycloak.models.map.authSession.MapRootAuthenticationSessionEntity;
import org.keycloak.models.map.authorization.entity.MapPermissionTicketEntity;
import org.keycloak.models.map.authorization.entity.MapPolicyEntity;
import org.keycloak.models.map.authorization.entity.MapResourceEntity;
import org.keycloak.models.map.authorization.entity.MapResourceServerEntity;
import org.keycloak.models.map.authorization.entity.MapScopeEntity;
import org.keycloak.models.map.client.MapClientEntity;
import org.keycloak.models.map.clientscope.MapClientScopeEntity;
import org.keycloak.models.map.common.AbstractEntity;
import org.keycloak.models.map.group.MapGroupEntity;
import org.keycloak.models.map.loginFailure.MapUserLoginFailureEntity;
import org.keycloak.models.map.realm.MapRealmEntity;
import org.keycloak.models.map.role.MapRoleEntity;
import org.keycloak.models.map.user.MapUserEntity;
import org.keycloak.models.map.userSession.MapAuthenticatedClientSessionEntity;
import org.keycloak.models.map.userSession.MapUserSessionEntity;
import org.keycloak.sessions.RootAuthenticationSessionModel;
import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.stream.Collectors;
/**
* Utility class covering various aspects of relationship between model and entity classes.
* @author hmlnarik
*/
public class ModelEntityUtil {
private static final Map<Class<?>, String> MODEL_TO_NAME = new HashMap<>();
static {
MODEL_TO_NAME.put(AuthenticatedClientSessionModel.class, "client-sessions");
MODEL_TO_NAME.put(ClientScopeModel.class, "client-scopes");
MODEL_TO_NAME.put(ClientModel.class, "clients");
MODEL_TO_NAME.put(GroupModel.class, "groups");
MODEL_TO_NAME.put(RealmModel.class, "realms");
MODEL_TO_NAME.put(RoleModel.class, "roles");
MODEL_TO_NAME.put(RootAuthenticationSessionModel.class, "auth-sessions");
MODEL_TO_NAME.put(UserLoginFailureModel.class, "user-login-failures");
MODEL_TO_NAME.put(UserModel.class, "users");
MODEL_TO_NAME.put(UserSessionModel.class, "user-sessions");
// authz
MODEL_TO_NAME.put(PermissionTicket.class, "authz-permission-tickets");
MODEL_TO_NAME.put(Policy.class, "authz-policies");
MODEL_TO_NAME.put(ResourceServer.class, "authz-resource-servers");
MODEL_TO_NAME.put(Resource.class, "authz-resources");
MODEL_TO_NAME.put(org.keycloak.authorization.model.Scope.class, "authz-scopes");
}
private static final Map<String, Class<?>> NAME_TO_MODEL = MODEL_TO_NAME.entrySet().stream().collect(Collectors.toMap(Entry::getValue, Entry::getKey));
private static final Map<Class<?>, Class<? extends AbstractEntity>> MODEL_TO_ENTITY_TYPE = new HashMap<>();
static {
MODEL_TO_ENTITY_TYPE.put(AuthenticatedClientSessionModel.class, MapAuthenticatedClientSessionEntity.class);
MODEL_TO_ENTITY_TYPE.put(ClientScopeModel.class, MapClientScopeEntity.class);
MODEL_TO_ENTITY_TYPE.put(ClientModel.class, MapClientEntity.class);
MODEL_TO_ENTITY_TYPE.put(GroupModel.class, MapGroupEntity.class);
MODEL_TO_ENTITY_TYPE.put(RealmModel.class, MapRealmEntity.class);
MODEL_TO_ENTITY_TYPE.put(RoleModel.class, MapRoleEntity.class);
MODEL_TO_ENTITY_TYPE.put(RootAuthenticationSessionModel.class, MapRootAuthenticationSessionEntity.class);
MODEL_TO_ENTITY_TYPE.put(UserLoginFailureModel.class, MapUserLoginFailureEntity.class);
MODEL_TO_ENTITY_TYPE.put(UserModel.class, MapUserEntity.class);
MODEL_TO_ENTITY_TYPE.put(UserSessionModel.class, MapUserSessionEntity.class);
// authz
MODEL_TO_ENTITY_TYPE.put(PermissionTicket.class, MapPermissionTicketEntity.class);
MODEL_TO_ENTITY_TYPE.put(Policy.class, MapPolicyEntity.class);
MODEL_TO_ENTITY_TYPE.put(ResourceServer.class, MapResourceServerEntity.class);
MODEL_TO_ENTITY_TYPE.put(Resource.class, MapResourceEntity.class);
MODEL_TO_ENTITY_TYPE.put(org.keycloak.authorization.model.Scope.class, MapScopeEntity.class);
}
private static final Map<Class<?>, Class<?>> ENTITY_TO_MODEL_TYPE = MODEL_TO_ENTITY_TYPE.entrySet().stream().collect(Collectors.toMap(Entry::getValue, Entry::getKey));
@SuppressWarnings("unchecked")
public static <V extends AbstractEntity, M> Class<V> getEntityType(Class<M> modelClass) {
return (Class<V>) MODEL_TO_ENTITY_TYPE.get(modelClass);
}
@SuppressWarnings("unchecked")
public static <V extends AbstractEntity, M> Class<V> getEntityType(Class<M> modelClass, Class<? extends AbstractEntity> defaultClass) {
return (Class<V>) MODEL_TO_ENTITY_TYPE.getOrDefault(modelClass, defaultClass);
}
@SuppressWarnings("unchecked")
public static <V extends AbstractEntity, M> Class<M> getModelType(Class<V> entityClass) {
return (Class<M>) ENTITY_TO_MODEL_TYPE.get(entityClass);
}
@SuppressWarnings("unchecked")
public static <V extends AbstractEntity, M> Class<M> getModelType(Class<V> entityClass, Class<M> defaultClass) {
return (Class<M>) ENTITY_TO_MODEL_TYPE.getOrDefault(entityClass, defaultClass);
}
public static String getModelName(Class<?> key, String defaultValue) {
return MODEL_TO_NAME.getOrDefault(key, defaultValue);
}
public static String getModelName(Class<?> key) {
return MODEL_TO_NAME.get(key);
}
public static Set<String> getModelNames() {
return NAME_TO_MODEL.keySet();
}
@SuppressWarnings("unchecked")
public static <M> Class<M> getModelClass(String key) {
return (Class<M>) NAME_TO_MODEL.get(key);
}
}

View file

@ -19,43 +19,20 @@ package org.keycloak.models.map.storage.chm;
import org.keycloak.models.map.common.StringKeyConvertor; import org.keycloak.models.map.common.StringKeyConvertor;
import org.keycloak.component.AmphibianProviderFactory; import org.keycloak.component.AmphibianProviderFactory;
import org.keycloak.Config.Scope; import org.keycloak.Config.Scope;
import org.keycloak.authorization.model.PermissionTicket;
import org.keycloak.authorization.model.Policy;
import org.keycloak.authorization.model.Resource;
import org.keycloak.authorization.model.ResourceServer;
import org.keycloak.common.Profile; import org.keycloak.common.Profile;
import org.keycloak.component.ComponentModelScope; import org.keycloak.component.ComponentModelScope;
import org.keycloak.models.AuthenticatedClientSessionModel; import org.keycloak.models.AuthenticatedClientSessionModel;
import org.keycloak.models.ClientModel;
import org.keycloak.models.ClientScopeModel;
import org.keycloak.models.GroupModel;
import org.keycloak.models.KeycloakSession; import org.keycloak.models.KeycloakSession;
import org.keycloak.models.KeycloakSessionFactory; import org.keycloak.models.KeycloakSessionFactory;
import org.keycloak.models.RealmModel;
import org.keycloak.models.RoleModel;
import org.keycloak.models.UserLoginFailureModel;
import org.keycloak.models.UserModel;
import org.keycloak.models.UserSessionModel; import org.keycloak.models.UserSessionModel;
import org.keycloak.models.map.authSession.MapRootAuthenticationSessionEntity;
import org.keycloak.models.map.authorization.entity.MapPermissionTicketEntity;
import org.keycloak.models.map.authorization.entity.MapPolicyEntity;
import org.keycloak.models.map.authorization.entity.MapResourceEntity;
import org.keycloak.models.map.authorization.entity.MapResourceServerEntity;
import org.keycloak.models.map.authorization.entity.MapScopeEntity;
import org.keycloak.models.map.client.MapClientEntity;
import org.keycloak.models.map.client.MapClientEntityImpl; import org.keycloak.models.map.client.MapClientEntityImpl;
import org.keycloak.models.map.client.MapProtocolMapperEntity; import org.keycloak.models.map.client.MapProtocolMapperEntity;
import org.keycloak.models.map.client.MapProtocolMapperEntityImpl; import org.keycloak.models.map.client.MapProtocolMapperEntityImpl;
import org.keycloak.models.map.clientscope.MapClientScopeEntity;
import org.keycloak.models.map.common.AbstractEntity; import org.keycloak.models.map.common.AbstractEntity;
import org.keycloak.models.map.common.DeepCloner; import org.keycloak.models.map.common.DeepCloner;
import org.keycloak.models.map.common.Serialization; import org.keycloak.models.map.common.Serialization;
import org.keycloak.models.map.common.UpdatableEntity; import org.keycloak.models.map.common.UpdatableEntity;
import org.keycloak.models.map.group.MapGroupEntity;
import org.keycloak.models.map.group.MapGroupEntityImpl; import org.keycloak.models.map.group.MapGroupEntityImpl;
import org.keycloak.models.map.loginFailure.MapUserLoginFailureEntity;
import org.keycloak.models.map.realm.MapRealmEntity;
import org.keycloak.models.map.role.MapRoleEntity;
import org.keycloak.models.map.role.MapRoleEntityImpl; import org.keycloak.models.map.role.MapRoleEntityImpl;
import com.fasterxml.jackson.databind.JavaType; import com.fasterxml.jackson.databind.JavaType;
import java.io.File; import java.io.File;
@ -67,18 +44,17 @@ import java.util.concurrent.ConcurrentHashMap;
import org.jboss.logging.Logger; import org.jboss.logging.Logger;
import org.keycloak.models.map.storage.MapStorageProvider; import org.keycloak.models.map.storage.MapStorageProvider;
import org.keycloak.models.map.storage.MapStorageProviderFactory; import org.keycloak.models.map.storage.MapStorageProviderFactory;
import org.keycloak.models.map.userSession.MapAuthenticatedClientSessionEntity; import org.keycloak.models.map.storage.ModelEntityUtil;
import org.keycloak.models.map.user.MapUserEntity;
import org.keycloak.models.map.userSession.MapUserSessionEntity;
import org.keycloak.provider.EnvironmentDependentProviderFactory; import org.keycloak.provider.EnvironmentDependentProviderFactory;
import org.keycloak.provider.ProviderConfigProperty; import org.keycloak.provider.ProviderConfigProperty;
import org.keycloak.models.map.storage.criteria.DefaultModelCriteria; import org.keycloak.models.map.storage.criteria.DefaultModelCriteria;
import org.keycloak.sessions.RootAuthenticationSessionModel;
import java.util.Collections; import java.util.Collections;
import java.util.HashMap; import java.util.HashMap;
import java.util.LinkedList; import java.util.LinkedList;
import java.util.Map; import java.util.Map;
import static org.keycloak.models.map.storage.ModelEntityUtil.getModelName;
import static org.keycloak.models.map.storage.ModelEntityUtil.getModelNames;
import static org.keycloak.models.map.storage.QueryParameters.withCriteria; import static org.keycloak.models.map.storage.QueryParameters.withCriteria;
import static org.keycloak.models.map.storage.criteria.DefaultModelCriteria.criteria; import static org.keycloak.models.map.storage.criteria.DefaultModelCriteria.criteria;
@ -110,68 +86,6 @@ public class ConcurrentHashMapStorageProviderFactory implements AmphibianProvide
.constructor(MapRoleEntityImpl.class, MapRoleEntityImpl::new) .constructor(MapRoleEntityImpl.class, MapRoleEntityImpl::new)
.build(); .build();
public static final Map<Class<?>, String> MODEL_TO_NAME = new HashMap<>();
static {
MODEL_TO_NAME.put(AuthenticatedClientSessionModel.class, "client-sessions");
MODEL_TO_NAME.put(ClientScopeModel.class, "client-scopes");
MODEL_TO_NAME.put(ClientModel.class, "clients");
MODEL_TO_NAME.put(GroupModel.class, "groups");
MODEL_TO_NAME.put(RealmModel.class, "realms");
MODEL_TO_NAME.put(RoleModel.class, "roles");
MODEL_TO_NAME.put(RootAuthenticationSessionModel.class, "auth-sessions");
MODEL_TO_NAME.put(UserLoginFailureModel.class, "user-login-failures");
MODEL_TO_NAME.put(UserModel.class, "users");
MODEL_TO_NAME.put(UserSessionModel.class, "user-sessions");
// authz
MODEL_TO_NAME.put(PermissionTicket.class, "authz-permission-tickets");
MODEL_TO_NAME.put(Policy.class, "authz-policies");
MODEL_TO_NAME.put(ResourceServer.class, "authz-resource-servers");
MODEL_TO_NAME.put(Resource.class, "authz-resources");
MODEL_TO_NAME.put(org.keycloak.authorization.model.Scope.class, "authz-scopes");
}
public static final Map<Class<?>, Class<? extends AbstractEntity>> MODEL_TO_VALUE_TYPE = new HashMap<>();
static {
MODEL_TO_VALUE_TYPE.put(AuthenticatedClientSessionModel.class, MapAuthenticatedClientSessionEntity.class);
MODEL_TO_VALUE_TYPE.put(ClientScopeModel.class, MapClientScopeEntity.class);
MODEL_TO_VALUE_TYPE.put(ClientModel.class, MapClientEntity.class);
MODEL_TO_VALUE_TYPE.put(GroupModel.class, MapGroupEntity.class);
MODEL_TO_VALUE_TYPE.put(RealmModel.class, MapRealmEntity.class);
MODEL_TO_VALUE_TYPE.put(RoleModel.class, MapRoleEntity.class);
MODEL_TO_VALUE_TYPE.put(RootAuthenticationSessionModel.class, MapRootAuthenticationSessionEntity.class);
MODEL_TO_VALUE_TYPE.put(UserLoginFailureModel.class, MapUserLoginFailureEntity.class);
MODEL_TO_VALUE_TYPE.put(UserModel.class, MapUserEntity.class);
MODEL_TO_VALUE_TYPE.put(UserSessionModel.class, MapUserSessionEntity.class);
// authz
MODEL_TO_VALUE_TYPE.put(PermissionTicket.class, MapPermissionTicketEntity.class);
MODEL_TO_VALUE_TYPE.put(Policy.class, MapPolicyEntity.class);
MODEL_TO_VALUE_TYPE.put(ResourceServer.class, MapResourceServerEntity.class);
MODEL_TO_VALUE_TYPE.put(Resource.class, MapResourceEntity.class);
MODEL_TO_VALUE_TYPE.put(org.keycloak.authorization.model.Scope.class, MapScopeEntity.class);
}
public static final Map<Class<?>, Class<?>> INTERFACE_TO_IMPL = new HashMap<>();
static {
INTERFACE_TO_IMPL.put(MapClientEntity.class, MapClientEntityImpl.class);
// INTERFACE_TO_IMPL.put(MapClientScopeEntity.class, MapClientScopeEntityImpl.class);
INTERFACE_TO_IMPL.put(MapGroupEntity.class, MapGroupEntityImpl.class);
// INTERFACE_TO_IMPL.put(MapRealmEntity.class, MapRealmEntityImpl.class);
INTERFACE_TO_IMPL.put(MapRoleEntity.class, MapRoleEntityImpl.class);
// INTERFACE_TO_IMPL.put(MapRootAuthenticationSessionEntity.class, MapRootAuthenticationSessionEntityImpl.class);
// INTERFACE_TO_IMPL.put(MapUserLoginFailureEntity.class, MapUserLoginFailureEntityImpl.class);
// INTERFACE_TO_IMPL.put(MapUserEntity.class, MapUserEntityImpl.class);
// INTERFACE_TO_IMPL.put(MapUserSessionEntity.class, MapUserSessionEntityImpl.class);
//
// // authz
// INTERFACE_TO_IMPL.put(MapPermissionTicketEntity.class, MapPermissionTicketEntityImpl.class);
// INTERFACE_TO_IMPL.put(MapPolicyEntity.class, MapPolicyEntityImpl.class);
// INTERFACE_TO_IMPL.put(MapResourceServerEntity.class, MapResourceServerEntityImpl.class);
// INTERFACE_TO_IMPL.put(MapResourceEntity.class, MapResourceEntityImpl.class);
// INTERFACE_TO_IMPL.put(MapScopeEntity.class, MapScopeEntityImpl.class);
}
private static final Map<String, StringKeyConvertor> KEY_CONVERTORS = new HashMap<>(); private static final Map<String, StringKeyConvertor> KEY_CONVERTORS = new HashMap<>();
static { static {
KEY_CONVERTORS.put("uuid", StringKeyConvertor.UUIDKey.INSTANCE); KEY_CONVERTORS.put("uuid", StringKeyConvertor.UUIDKey.INSTANCE);
@ -195,7 +109,7 @@ public class ConcurrentHashMapStorageProviderFactory implements AmphibianProvide
final String keyType = config.get("keyType", "uuid"); final String keyType = config.get("keyType", "uuid");
defaultKeyConvertor = getKeyConvertor(keyType); defaultKeyConvertor = getKeyConvertor(keyType);
for (String name : MODEL_TO_NAME.values()) { for (String name : getModelNames()) {
keyConvertors.put(name, getKeyConvertor(config.get("keyType." + name, keyType))); keyConvertors.put(name, getKeyConvertor(config.get("keyType." + name, keyType)));
} }
@ -259,7 +173,7 @@ public class ConcurrentHashMapStorageProviderFactory implements AmphibianProvide
private <K, V extends AbstractEntity & UpdatableEntity, M> ConcurrentHashMapStorage<K, V, M> loadMap(String mapName, private <K, V extends AbstractEntity & UpdatableEntity, M> ConcurrentHashMapStorage<K, V, M> loadMap(String mapName,
Class<M> modelType, EnumSet<Flag> flags) { Class<M> modelType, EnumSet<Flag> flags) {
final StringKeyConvertor kc = keyConvertors.getOrDefault(mapName, defaultKeyConvertor); final StringKeyConvertor kc = keyConvertors.getOrDefault(mapName, defaultKeyConvertor);
Class<?> valueType = MODEL_TO_VALUE_TYPE.get(modelType); Class<?> valueType = ModelEntityUtil.getEntityType(modelType);
LOG.debugf("Initializing new map storage: %s", mapName); LOG.debugf("Initializing new map storage: %s", mapName);
ConcurrentHashMapStorage<K, V, M> store; ConcurrentHashMapStorage<K, V, M> store;
@ -285,7 +199,10 @@ public class ConcurrentHashMapStorageProviderFactory implements AmphibianProvide
if (f != null && f.exists()) { if (f != null && f.exists()) {
try { try {
LOG.debugf("Restoring contents from %s", f.getCanonicalPath()); LOG.debugf("Restoring contents from %s", f.getCanonicalPath());
Class<?> valueImplType = INTERFACE_TO_IMPL.getOrDefault(valueType, valueType); Class<?> valueImplType = CLONER.newInstanceType(valueType);
if (valueImplType == null) {
valueImplType = valueType;
}
JavaType type = Serialization.MAPPER.getTypeFactory().constructCollectionType(LinkedList.class, valueImplType); JavaType type = Serialization.MAPPER.getTypeFactory().constructCollectionType(LinkedList.class, valueImplType);
List<V> values = Serialization.MAPPER.readValue(f, type); List<V> values = Serialization.MAPPER.readValue(f, type);
@ -308,7 +225,7 @@ public class ConcurrentHashMapStorageProviderFactory implements AmphibianProvide
public <K, V extends AbstractEntity & UpdatableEntity, M> ConcurrentHashMapStorage<K, V, M> getStorage( public <K, V extends AbstractEntity & UpdatableEntity, M> ConcurrentHashMapStorage<K, V, M> getStorage(
Class<M> modelType, Flag... flags) { Class<M> modelType, Flag... flags) {
EnumSet<Flag> f = flags == null || flags.length == 0 ? EnumSet.noneOf(Flag.class) : EnumSet.of(flags[0], flags); EnumSet<Flag> f = flags == null || flags.length == 0 ? EnumSet.noneOf(Flag.class) : EnumSet.of(flags[0], flags);
String name = MODEL_TO_NAME.getOrDefault(modelType, modelType.getSimpleName()); String name = getModelName(modelType, modelType.getSimpleName());
/* From ConcurrentHashMapStorage.computeIfAbsent javadoc: /* From ConcurrentHashMapStorage.computeIfAbsent javadoc:
* *
* "... the computation [...] must not attempt to update any other mappings of this map." * "... the computation [...] must not attempt to update any other mappings of this map."