8000 JPA map storage: User / client session no-downtime store by vramik · Pull Request #12241 · keycloak/keycloak · GitHub
[go: up one dir, main page]

Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,6 @@
*/
package org.keycloak.models.map.storage.jpa;

import javax.persistence.criteria.CriteriaBuilder;

public interface Constants {
public static final Integer CURRENT_SCHEMA_VERSION_ADMIN_EVENT = 1;
public static final Integer CURRENT_SCHEMA_VERSION_AUTH_EVENT = 1;
Expand All @@ -28,6 +26,7 @@ public interface Constants {
public static final Integer CURRENT_SCHEMA_VERSION_AUTHZ_RESOURCE_SERVER = 1;
public static final Integer CURRENT_SCHEMA_VERSION_AUTHZ_SCOPE = 1;
public static final Integer CURRENT_SCHEMA_VERSION_CLIENT = 1;
public static final Integer CURRENT_SCHEMA_VERSION_CLIENT_SESSION = 1;
public static final Integer CURRENT_SCHEMA_VERSION_CLIENT_SCOPE = 1;
public static final Integer CURRENT_SCHEMA_VERSION_COMPONENT = 1;
public static final Integer CURRENT_SCHEMA_VERSION_GROUP = 1;
Expand All @@ -39,5 +38,5 @@ public interface Constants {
public static final Integer CURRENT_SCHEMA_VERSION_USER = 1;
public static final Integer CURRENT_SCHEMA_VERSION_USER_CONSENT = 1;
public static final Integer CURRENT_SCHEMA_VERSION_USER_FEDERATED_IDENTITY = 1;

public static final Integer CURRENT_SCHEMA_VERSION_USER_SESSION = 1;
}
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@
import java.util.UUID;
import java.util.stream.Stream;
import javax.persistence.EntityManager;
import javax.persistence.LockModeType;
import javax.persistence.TypedQuery;
import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.CriteriaDelete;
import javax.persistence.criteria.CriteriaQuery;
Expand All @@ -32,6 +34,7 @@

import org.jboss.logging.Logger;
import org.keycloak.common.util.Time;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.map.common.AbstractEntity;
import org.keycloak.models.map.common.ExpirableEntity;
import org.keycloak.models.map.common.StringKeyConverter;
Expand All @@ -40,7 +43,7 @@
import org.keycloak.models.map.storage.QueryParameters;
import org.keycloak.models.map.storage.chm.MapFieldPredicates;
import org.keycloak.models.map.storage.chm.MapModelCriteriaBuilder;
import org.keycloak.models.map.storage.jpa.role.JpaPredicateFunction;
import org.keycloak.utils.LockObjectsForModification;

import static org.keycloak.models.map.common.ExpirationUtils.isExpired;
import static org.keycloak.models.map.storage.jpa.JpaMapStorageProviderFactory.CLONER;
Expand All @@ -50,12 +53,14 @@
public abstract class JpaMapKeycloakTransaction<RE extends JpaRootEntity, E extends AbstractEntity, M> implements MapKeycloakTransaction<E, M> {

private static final Logger logger = Logger.getLogger(JpaMapKeycloakTransaction.class);
private final KeycloakSession session;
private final Class<RE> entityType;
private final Class<M> modelType;
private final boolean isExpirableEntity;
protected EntityManager em;

public JpaMapKeycloakTransaction(Class<RE> entityType, Class<M> modelType, EntityManager em) {
public JpaMapKeycloakTransaction(KeycloakSession session, Class<RE> entityType, Class<M> modelType, EntityManager em) {
this.session = session;
this.em = em;
this.entityType = entityType;
this.modelType = modelType;
Expand All @@ -67,6 +72,15 @@ public JpaMapKeycloakTransaction(Class<RE> entityType, Class<M> modelType, Entit
protected abstract JpaModelCriteriaBuilder createJpaModelCriteriaBuilder();
protected abstract E mapToEntityDelegate(RE original);

/**
* Indicates of pessimistic locking should be allowed for this entity. This should be enabled only for those entities
* where there is no expected contention from different callers. For UserSessions and ClientSessions this should be possible.
* The locking on Clients would be problematic as such a lock affects multiple callers.
*/
protected boolean lockingSupportedForEntity() {
return false;
}

private final HashMap<String, E> cacheWithinSession = new HashMap<>();

/**
Expand Down Expand Up @@ -101,7 +115,11 @@ public E read(String key) {
if (key == null) return null;
UUID uuid = StringKeyConverter.UUIDKey.INSTANCE.fromStringSafe(key);
if (uuid == null) return null;
E e = mapToEntityDelegateUnique(em.find(entityType, uuid));
E e = mapToEntityDelegateUnique(
lockingSupportedForEntity() && LockObjectsForModification.isEnabled(session) ?
em.find(entityType, uuid, LockModeType.PESSIMISTIC_WRITE) :
em.find(entityType, uuid)
);
return e != null && isExpirableEntity && isExpired((ExpirableEntity) e, true) ? null : e;
}

Expand Down Expand Up @@ -142,7 +160,11 @@ public Stream<E> read(QueryParameters<M> queryParameters) {
}
if (predicateFunc != null) query.where(predicateFunc.apply(cb, query::subquery, root));

return closing(paginateQuery(em.createQuery(query), queryParameters.getOffset(), queryParameters.getLimit()).getResultStream())
TypedQuery<RE> emQuery = em.createQuery(query);
if (lockingSupportedForEntity() && LockObjectsForModification.isEnabled(session)) {
emQuery = emQuery.setLockMode(LockModeType.PESSIMISTIC_WRITE);
}
return closing(paginateQuery(emQuery, queryParameters.getOffset(), queryParameters.getLimit()).getResultStream())
.map(this::mapToEntityDelegateUnique);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ public <V extends AbstractEntity, M> MapStorage<V, M> getStorage(Class<M> modelT
return new MapStorage<V, M>() {
@Override
public MapKeycloakTransaction<V, M> createTransaction(KeycloakSession session) {
return factory.createTransaction(modelType, em);
return factory.createTransaction(session, modelType, em);
}
};
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.Function;
import java.util.function.BiFunction;

import javax.naming.InitialContext;
import javax.naming.NamingException;
Expand Down Expand Up @@ -59,6 +59,7 @@
import org.keycloak.events.Event;
import org.keycloak.events.admin.AdminEvent;
import org.keycloak.models.ActionTokenValueModel;
import org.keycloak.models.AuthenticatedClientSessionModel;
import org.keycloak.models.ClientModel;
import org.keycloak.models.ClientScopeModel;
import org.keycloak.models.GroupModel;
Expand All @@ -68,6 +69,7 @@
import org.keycloak.models.RoleModel;
import org.keycloak.models.UserLoginFailureModel;
import org.keycloak.models.UserModel;
import org.keycloak.models.UserSessionModel;
import org.keycloak.models.dblock.DBLockProvider;
import org.keycloak.models.map.client.MapProtocolMapperEntity;
import org.keycloak.models.map.client.MapProtocolMapperEntityImpl;
Expand Down Expand Up @@ -131,6 +133,9 @@
import org.keycloak.models.map.storage.jpa.singleUseObject.JpaSingleUseObjectMapKeycloakTransaction;
import org.keycloak.models.map.storage.jpa.singleUseObject.entity.JpaSingleUseObjectEntity;
import org.keycloak.models.map.storage.jpa.updater.MapJpaUpdaterProvider;
import org.keycloak.models.map.storage.jpa.userSession.JpaUserSessionMapKeycloakTransaction;
import org.keycloak.models.map.storage.jpa.userSession.entity.JpaClientSessionEntity;
import org.keycloak.models.map.storage.jpa.userSession.entity.JpaUserSessionEntity;
import org.keycloak.models.map.storage.jpa.user.JpaUserMapKeycloakTransaction;
import org.keycloak.models.map.storage.jpa.user.entity.JpaUserConsentEntity;
import org.keycloak.models.map.storage.jpa.user.entity.JpaUserEntity;
Expand Down Expand Up @@ -203,9 +208,12 @@ public class JpaMapStorageProviderFactory implements
.constructor(JpaUserConsentEntity.class, JpaUserConsentEntity::new)
.constructor(JpaUserFederatedIdentityEntity.class, JpaUserFederatedIdentityEntity::new)
.constructor(MapUserCredentialEntity.class, MapUserCredentialEntityImpl::new)
//user/client session
.constructor(JpaClientSessionEntity.class, JpaClientSessionEntity::new)
.constructor(JpaUserSessionEntity.class, JpaUserSessionEntity::new)
.build();

private static final Map<Class<?>, Function<EntityManager, MapKeycloakTransaction>> MODEL_TO_TX = new HashMap<>();
private static final Map<Class<?>, BiFunction<KeycloakSession, EntityManager, MapKeycloakTransaction>> MODEL_TO_TX = new HashMap<>();
static {
//auth-sessions
MODEL_TO_TX.put(RootAuthenticationSessionModel.class, JpaRootAuthenticationSessionMapKeycloakTransaction::new);
Expand Down Expand Up @@ -234,6 +242,8 @@ public class JpaMapStorageProviderFactory implements
MODEL_TO_TX.put(UserLoginFailureModel.class, JpaUserLoginFailureMapKeycloakTransaction::new);
//users
MODEL_TO_TX.put(UserModel.class, JpaUserMapKeycloakTransaction::new);
//sessions
MODEL_TO_TX.put(UserSessionModel.class, JpaUserSessionMapKeycloakTransaction::new);
}

public JpaMapStorageProviderFactory() {
Expand All @@ -242,8 +252,8 @@ public JpaMapStorageProviderFactory() {
this.sessionTxKey = SESSION_TX_PREFIX + index;
}

public MapKeycloakTransaction createTransaction(Class<?> modelType, EntityManager em) {
return MODEL_TO_TX.get(modelType).apply(em);
public MapKeycloakTransaction createTransaction(KeycloakSession session, Class<?> modelType, EntityManager em) {
return MODEL_TO_TX.get(modelType).apply(session, em);
}

@Override
Expand Down Expand Up @@ -358,17 +368,6 @@ public void disintegrate(SessionFactoryImplementor sessionFactoryImplementor,
)
);

Integer isolation = config.getInt("isolation");
if (isolation != null) {
if (isolation < Connection.TRANSACTION_REPEATABLE_READ) {
logger.warn("Concurrent requests may not be reliable with transaction level lower than TRANSACTION_REPEATABLE_READ.");
}
properties.put(AvailableSettings.ISOLATION, String.valueOf(isolation));
} else {
// default value is TRANSACTION_READ_COMMITTED
logger.warn("Concurrent requests may not be reliable with transaction level lower than TRANSACTION_REPEATABLE_READ.");
}

logger.trace("Creating EntityManagerFactory");
this.emf = Persistence.createEntityManagerFactory(unitName, properties);
logger.trace("EntityManagerFactory created");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,6 @@
import org.keycloak.models.map.storage.CriterionNotSupportedException;
import org.keycloak.models.map.storage.ModelCriteriaBuilder;
import org.keycloak.models.map.storage.jpa.hibernate.jsonb.JsonbType;
import org.keycloak.models.map.storage.jpa.role.JpaPredicateFunction;
import org.keycloak.storage.SearchableModelField;

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
* limitations under the License.
*/

package org.keycloak.models.map.storage.jpa.role;
package org.keycloak.models.map.storage.jpa;

import org.keycloak.models.map.storage.jpa.JpaSubqueryProvider;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@
import javax.persistence.PersistenceException;

import org.jboss.logging.Logger;
import org.keycloak.connections.jpa.PersistenceExceptionConverter;
import org.keycloak.models.KeycloakTransaction;

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
* limitations under the License.
*/

package org.keycloak.connections.jpa;
package org.keycloak.models.map.storage.jpa;

import org.hibernate.exception.ConstraintViolationException;
import org.keycloak.models.Constants;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@
import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.Root;
import javax.persistence.criteria.Selection;

import org.keycloak.models.KeycloakSession;
import org.keycloak.models.map.authSession.MapRootAuthenticationSessionEntity;
import org.keycloak.models.map.authSession.MapRootAuthenticationSessionEntityDelegate;
import static org.keycloak.models.map.storage.jpa.Constants.CURRENT_SCHEMA_VERSION_AUTH_SESSION;
Expand All @@ -33,8 +35,8 @@
public class JpaRootAuthenticationSessionMapKeycloakTransaction extends JpaMapKeycloakTransaction<JpaRootAuthenticationSessionEntity, MapRootAuthenticationSessionEntity, RootAuthenticationSessionModel> {

@SuppressWarnings("unchecked")
public JpaRootAuthenticationSessionMapKeycloakTransaction(EntityManager em) {
super(JpaRootAuthenticationSessionEntity.class, RootAuthenticationSessionModel.class, em);
public JpaRootAuthenticationSessionMapKeycloakTransaction(KeycloakSession session, EntityManager em) {
super(session, JpaRootAuthenticationSessionEntity.class, RootAuthenticationSessionModel.class, em);
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,8 @@

import org.keycloak.models.map.storage.CriterionNotSupportedException;
import org.keycloak.models.map.storage.jpa.JpaModelCriteriaBuilder;
import org.keycloak.models.map.storage.jpa.JpaPredicateFunction;
import org.keycloak.models.map.storage.jpa.authSession.entity.JpaRootAuthenticationSessionEntity;
import org.keycloak.models.map.storage.jpa.role.JpaPredicateFunction;
import org.keycloak.sessions.RootAuthenticationSessionModel;
import org.keycloak.sessions.RootAuthenticationSessionModel.SearchableFields;
import org.keycloak.storage.SearchableModelField;
Expand Down
10BC8
Original file line number Diff line number Diff line change
Expand Up @@ -36,19 +36,12 @@
import org.keycloak.models.map.common.DeepCloner;
import org.keycloak.models.map.common.UpdatableEntity;
import static org.keycloak.models.map.storage.jpa.Constants.CURRENT_SCHEMA_VERSION_AUTH_SESSION;
import org.keycloak.models.map.storage.jpa.JpaRootEntity;
import org.keycloak.models.map.storage.jpa.JpaRootVersionedEntity;
import org.keycloak.models.map.storage.jpa.hibernate.jsonb.JsonbType;
import org.keycloak.sessions.CommonClientSessionModel;

/**
* Entity represents individual authentication session.
*
* It implements {@link JpaRootEntity} as it contains json field.
*
* Authentication session is modified from multiple transactions within one request,
* via {@code KeycloakModelUtils.runJobInTransaction}. Therefore it doesn't
* implement {@code JpaRootVersionedEntity} nor {@code JpaChildEntity}.
*/
@Entity
@Table(name = "kc_auth_session")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
import javax.persistence.criteria.Root;
import javax.persistence.criteria.Selection;
import org.keycloak.authorization.model.PermissionTicket;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.map.authorization.entity.MapPermissionTicketEntity;
import org.keycloak.models.map.authorization.entity.MapPermissionTicketEntityDelegate;
import org.keycloak.models.map.storage.jpa.Constants;
Expand All @@ -33,8 +34,8 @@
public class JpaPermissionMapKeycloakTransaction extends JpaMapKeycloakTransaction<JpaPermissionEntity, MapPermissionTicketEntity, PermissionTicket> {

@SuppressWarnings("unchecked")
public JpaPermissionMapKeycloakTransaction(EntityManager em) {
super(JpaPermissionEntity.class, PermissionTicket.class, em);
public JpaPermissionMapKeycloakTransaction(KeycloakSession session, EntityManager em) {
super(session, JpaPermissionEntity.class, PermissionTicket.class, em);
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,9 +27,9 @@
import org.keycloak.models.map.storage.CriterionNotSupportedException;
import org.keycloak.models.map.storage.ModelCriteriaBuilder;
import org.keycloak.models.map.storage.jpa.JpaModelCriteriaBuilder;
import org.keycloak.models.map.storage.jpa.JpaPredicateFunction;
import org.keycloak.models.map.storage.jpa.authorization.permission.entity.JpaPermissionEntity;
import org.keycloak.models.map.storage.jpa.hibernate.jsonb.JsonbType;
import org.keycloak.models.map.storage.jpa.role.JpaPredicateFunction;
import org.keycloak.storage.SearchableModelField;

public class JpaPermissionModelCriteriaBuilder extends JpaModelCriteriaBuilder<JpaPermissionEntity, PermissionTicket, JpaPermissionModelCriteriaBuilder> {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
import javax.persistence.criteria.Root;
import javax.persistence.criteria.Selection;
import org.keycloak.authorization.model.Policy;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.map.authorization.entity.MapPolicyEntity;
import org.keycloak.models.map.authorization.entity.MapPolicyEntityDelegate;
import org.keycloak.models.map.storage.jpa.Constants;
Expand All @@ -33,8 +34,8 @@
public class JpaPolicyMapKeycloakTransaction extends JpaMapKeycloakTransaction<JpaPolicyEntity, MapPolicyEntity, Policy> {

@SuppressWarnings("unchecked")
public JpaPolicyMapKeycloakTransaction(EntityManager em) {
super(JpaPolicyEntity.class, Policy.class, em);
public JpaPolicyMapKeycloakTransaction(KeycloakSession session, EntityManager em) {
super(session, JpaPolicyEntity.class, Policy.class, em);
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,10 +30,10 @@
import org.keycloak.models.map.storage.CriterionNotSupportedException;
import org.keycloak.models.map.storage.ModelCriteriaBuilder;
import org.keycloak.models.map.storage.jpa.JpaModelCriteriaBuilder;
import org.keycloak.models.map.storage.jpa.JpaPredicateFunction;
import org.keycloak.models.map.storage.jpa.authorization.policy.entity.JpaPolicyConfigEntity;
import org.keycloak.models.map.storage.jpa.authorization.policy.entity.JpaPolicyEntity;
import org.keycloak.models.map.storage.jpa.hibernate.jsonb.JsonbType;
import org.keycloak.models.map.storage.jpa.role.JpaPredicateFunction;
import org.keycloak.storage.SearchableModelField;

public class JpaPolicyModelCriteriaBuilder extends JpaModelCriteriaBuilder<JpaPolicyEntity, Policy, JpaPolicyModelCriteriaBuilder> {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
import javax.persistence.criteria.Root;
import javax.persistence.criteria.Selection;
import org.keycloak.authorization.model.Resource;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.map.authorization.entity.MapResourceEntity;
import org.keycloak.models.map.authorization.entity.MapResourceEntityDelegate;
import org.keycloak.models.map.storage.jpa.Constants;
Expand All @@ -33,8 +34,8 @@
public class JpaResourceMapKeycloakTransaction extends JpaMapKeycloakTransaction<JpaResourceEntity, MapResourceEntity, Resource> {

@SuppressWarnings("unchecked")
public JpaResourceMapKeycloakTransaction(EntityManager em) {
super(JpaResourceEntity.class, Resource.class, em);
public JpaResourceMapKeycloakTransaction(KeycloakSession session, EntityManager em) {
super(session, JpaResourceEntity.class, Resource.class, em);
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,8 @@
import org.keycloak.models.map.storage.CriterionNotSupportedException;
import org.keycloak.models.map.storage.ModelCriteriaBuilder;
import org.keycloak.models.map.storage.jpa.JpaModelCriteriaBuilder;
import org.keycloak.models.map.storage.jpa.JpaPredicateFunction;
import org.keycloak.models.map.storage.jpa.authorization.resource.entity.JpaResourceEntity;
import org.keycloak.models.map.storage.jpa.role.JpaPredicateFunction;
import org.keycloak.storage.SearchableModelField;

public class JpaResourceModelCriteriaBuilder extends JpaModelCriteriaBuilder<JpaResourceEntity, Resource, JpaResourceModelCriteriaBuilder> {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
import javax.persistence.criteria.Root;
import javax.persistence.criteria.Selection;
import org.keycloak.authorization.model.ResourceServer;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.map.authorization.entity.MapResourceServerEntity;
import org.keycloak.models.map.authorization.entity.MapResourceServerEntityDelegate;
import org.keycloak.models.map.storage.jpa.Constants;
Expand All @@ -33,8 +34,8 @@
public class JpaResourceServerMapKeycloakTransaction extends JpaMapKeycloakTransaction<JpaResourceServerEntity, MapResourceServerEntity, ResourceServer> {

@SuppressWarnings("unchecked")
public JpaResourceServerMapKeycloakTransaction(EntityManager em) {
super(JpaResourceServerEntity.class, ResourceServer.class, em);
public JpaResourceServerMapKeycloakTransaction(KeycloakSession session, EntityManager em) {
super(session, JpaResourceServerEntity.class, ResourceServer.class, em);
}

@Override
Expand Down
Loading
0