ModelEntityUtil.java
/*
* 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.events.Event;
import org.keycloak.events.admin.AdminEvent;
import org.keycloak.models.SingleUseObjectValueModel;
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.lock.MapLockEntity;
import org.keycloak.models.map.singleUseObject.MapSingleUseObjectEntity;
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.common.DeepCloner;
import org.keycloak.models.map.common.EntityField;
import org.keycloak.models.map.common.UpdatableEntity;
import org.keycloak.models.map.common.delegate.EntityFieldDelegate;
import org.keycloak.models.map.events.MapAdminEventEntity;
import org.keycloak.models.map.events.MapAuthEventEntity;
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.role.MapRoleEntityFields;
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.IdentityHashMap;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import static org.keycloak.models.map.common.AutogeneratedClasses.ENTITY_FIELDS;
/**
* 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 IdentityHashMap<>();
static {
MODEL_TO_NAME.put(SingleUseObjectValueModel.class, "single-use-objects");
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");
// events
MODEL_TO_NAME.put(AdminEvent.class, "admin-events");
MODEL_TO_NAME.put(Event.class, "auth-events");
// locks
MODEL_TO_NAME.put(MapLockEntity.class, "locks");
}
private static final Map<String, Class<?>> NAME_TO_MODEL = MODEL_TO_NAME.entrySet().stream().collect(Collectors.toUnmodifiableMap(Entry::getValue, Entry::getKey));
private static final Map<Class<?>, Class<? extends AbstractEntity>> MODEL_TO_ENTITY_TYPE = new IdentityHashMap<>();
static {
MODEL_TO_ENTITY_TYPE.put(SingleUseObjectValueModel.class, MapSingleUseObjectEntity.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);
MODEL_TO_ENTITY_TYPE.put(AuthenticatedClientSessionModel.class, MapAuthenticatedClientSessionEntity.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);
// events
MODEL_TO_ENTITY_TYPE.put(AdminEvent.class, MapAdminEventEntity.class);
MODEL_TO_ENTITY_TYPE.put(Event.class, MapAuthEventEntity.class);
}
private static final Map<Class<?>, Class<?>> ENTITY_TO_MODEL_TYPE = MODEL_TO_ENTITY_TYPE.entrySet().stream().collect(Collectors.toUnmodifiableMap(Entry::getValue, Entry::getKey));
private static final String ID_FIELD_NAME = MapRoleEntityFields.ID.getName();
private static final Map<Class<?>, EntityField<?>> ENTITY_TO_ID_FIELD = ENTITY_FIELDS.entrySet().stream()
.filter(me -> Stream.of(me.getValue()).anyMatch(e -> ID_FIELD_NAME.equals(e.getName())))
.map(me -> Map.entry(me.getKey(), Stream.of(me.getValue()).filter(e -> ID_FIELD_NAME.equals(e.getName())).findAny().orElse(null)))
.filter(me -> me.getValue() != null)
.collect(Collectors.toUnmodifiableMap(Entry::getKey, Entry::getValue));
private static final String REALM_ID_FIELD_NAME = MapRoleEntityFields.REALM_ID.getName();
private static final Map<Class<?>, EntityField<?>> ENTITY_TO_REALM_ID_FIELD = ENTITY_FIELDS.entrySet().stream()
.filter(me -> Stream.of(me.getValue()).anyMatch(e -> REALM_ID_FIELD_NAME.equals(e.getName())))
.map(me -> Map.entry(me.getKey(), Stream.of(me.getValue()).filter(e -> REALM_ID_FIELD_NAME.equals(e.getName())).findAny().orElse(null)))
.filter(me -> me.getValue() != null)
.collect(Collectors.toUnmodifiableMap(Entry::getKey, Entry::getValue));
@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);
}
@SuppressWarnings("unchecked")
public static boolean entityFieldsKnown(Class<?> entityClass) {
return ENTITY_FIELDS.containsKey(entityClass);
}
@SuppressWarnings("unchecked")
public static <V> Stream<EntityField<V>> getEntityFields(Class<V> entityClass) {
EntityField<V>[] values = (EntityField<V>[]) ENTITY_FIELDS.get(entityClass);
return values == null ? Stream.empty() : Stream.of(values);
}
public static <V extends AbstractEntity> Optional<EntityField<V>> getEntityField(Class<V> entityClass, String fieldNameCamelCase) {
final Stream<EntityField<V>> s = getEntityFields(entityClass);
return s
.filter(ef -> fieldNameCamelCase.equals(ef.getNameCamelCase()))
.findAny();
}
@SuppressWarnings("unchecked")
public static <V extends AbstractEntity> EntityField<V> getIdField(Class<V> targetEntityClass) {
return (EntityField<V>) ENTITY_TO_ID_FIELD.get(targetEntityClass);
}
@SuppressWarnings("unchecked")
public static <V extends AbstractEntity> EntityField<V> getRealmIdField(Class<V> targetEntityClass) {
return (EntityField<V>) ENTITY_TO_REALM_ID_FIELD.get(targetEntityClass);
}
public static <T extends AbstractEntity & UpdatableEntity> T supplyReadOnlyFieldValueIfUnset(T entity, EntityField<T> entityField, Object value) {
if (entity == null || Objects.equals(entityField.get(entity), value)) {
return entity;
}
return DeepCloner.DUMB_CLONER.entityFieldDelegate(entity, new EntityFieldDelegate.WithEntity<>(entity) {
@Override
public <EF extends java.lang.Enum<? extends EntityField<T>> & EntityField<T>> Object get(EF field) {
if (field == entityField) {
return value;
}
return super.get(field);
}
@Override
public <V, EF extends java.lang.Enum<? extends EntityField<T>> & EntityField<T>> void set(EF field, V value) {
if (field != entityField) {
super.set(field, value);
}
}
@Override
public String toString() {
return super.toString() + " [fixed " + entityField + "=" + value + "]";
}
});
}
}