parent
d3ba2ecbed
commit
6fa62e47db
28 changed files with 508 additions and 47 deletions
|
@ -40,6 +40,8 @@ import org.keycloak.models.map.storage.chm.ConcurrentHashMapCrudOperations;
|
||||||
import org.keycloak.models.map.storage.chm.ConcurrentHashMapKeycloakTransaction;
|
import org.keycloak.models.map.storage.chm.ConcurrentHashMapKeycloakTransaction;
|
||||||
import org.keycloak.models.map.storage.chm.MapFieldPredicates;
|
import org.keycloak.models.map.storage.chm.MapFieldPredicates;
|
||||||
import org.keycloak.models.map.storage.chm.MapModelCriteriaBuilder;
|
import org.keycloak.models.map.storage.chm.MapModelCriteriaBuilder;
|
||||||
|
import org.keycloak.models.map.storage.hotRod.transaction.NoActionHotRodTransactionWrapper;
|
||||||
|
import org.keycloak.models.map.storage.hotRod.transaction.AllAreasHotRodTransactionsWrapper;
|
||||||
import org.keycloak.storage.SearchableModelField;
|
import org.keycloak.storage.SearchableModelField;
|
||||||
|
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
@ -64,14 +66,16 @@ public class HotRodMapStorage<K, E extends AbstractHotRodEntity, V extends Abstr
|
||||||
private final Function<E, V> delegateProducer;
|
private final Function<E, V> delegateProducer;
|
||||||
protected final DeepCloner cloner;
|
protected final DeepCloner cloner;
|
||||||
protected boolean isExpirableEntity;
|
protected boolean isExpirableEntity;
|
||||||
|
private final AllAreasHotRodTransactionsWrapper txWrapper;
|
||||||
|
|
||||||
public HotRodMapStorage(RemoteCache<K, E> remoteCache, StringKeyConverter<K> keyConverter, HotRodEntityDescriptor<E, V> storedEntityDescriptor, DeepCloner cloner) {
|
public HotRodMapStorage(RemoteCache<K, E> remoteCache, StringKeyConverter<K> keyConverter, HotRodEntityDescriptor<E, V> storedEntityDescriptor, DeepCloner cloner, AllAreasHotRodTransactionsWrapper txWrapper) {
|
||||||
this.remoteCache = remoteCache;
|
this.remoteCache = remoteCache;
|
||||||
this.keyConverter = keyConverter;
|
this.keyConverter = keyConverter;
|
||||||
this.storedEntityDescriptor = storedEntityDescriptor;
|
this.storedEntityDescriptor = storedEntityDescriptor;
|
||||||
this.cloner = cloner;
|
this.cloner = cloner;
|
||||||
this.delegateProducer = storedEntityDescriptor.getHotRodDelegateProvider();
|
this.delegateProducer = storedEntityDescriptor.getHotRodDelegateProvider();
|
||||||
this.isExpirableEntity = ExpirableEntity.class.isAssignableFrom(ModelEntityUtil.getEntityType(storedEntityDescriptor.getModelTypeClass()));
|
this.isExpirableEntity = ExpirableEntity.class.isAssignableFrom(ModelEntityUtil.getEntityType(storedEntityDescriptor.getModelTypeClass()));
|
||||||
|
this.txWrapper = txWrapper;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -228,13 +232,10 @@ public class HotRodMapStorage<K, E extends AbstractHotRodEntity, V extends Abstr
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public MapKeycloakTransaction<V, M> createTransaction(KeycloakSession session) {
|
public MapKeycloakTransaction<V, M> createTransaction(KeycloakSession session) {
|
||||||
MapKeycloakTransaction<V, M> sessionTransaction = session.getAttribute("map-transaction-" + hashCode(), MapKeycloakTransaction.class);
|
// Here we return transaction that has no action because the returned transaction is enlisted to different
|
||||||
|
// phase than we need. Instead of tx returned by this method txWrapper is enlisted and executes all changes
|
||||||
if (sessionTransaction == null) {
|
// performed by the returned transaction.
|
||||||
sessionTransaction = createTransactionInternal(session);
|
return new NoActionHotRodTransactionWrapper<>((ConcurrentHashMapKeycloakTransaction<K, V, M>) txWrapper.getOrCreateTxForModel(storedEntityDescriptor.getModelTypeClass(), () -> createTransactionInternal(session)));
|
||||||
session.setAttribute("map-transaction-" + hashCode(), sessionTransaction);
|
|
||||||
}
|
|
||||||
return sessionTransaction;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected MapKeycloakTransaction<V, M> createTransactionInternal(KeycloakSession session) {
|
protected MapKeycloakTransaction<V, M> createTransactionInternal(KeycloakSession session) {
|
||||||
|
|
|
@ -17,25 +17,54 @@
|
||||||
|
|
||||||
package org.keycloak.models.map.storage.hotRod;
|
package org.keycloak.models.map.storage.hotRod;
|
||||||
|
|
||||||
|
import org.infinispan.client.hotrod.RemoteCache;
|
||||||
import org.keycloak.models.KeycloakSession;
|
import org.keycloak.models.KeycloakSession;
|
||||||
import org.keycloak.models.map.common.AbstractEntity;
|
import org.keycloak.models.map.common.AbstractEntity;
|
||||||
import org.keycloak.models.map.storage.MapStorage;
|
import org.keycloak.models.map.storage.MapStorage;
|
||||||
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.storage.hotRod.common.HotRodEntityDescriptor;
|
||||||
|
import org.keycloak.models.map.storage.hotRod.connections.HotRodConnectionProvider;
|
||||||
|
import org.keycloak.models.map.storage.hotRod.transaction.HotRodRemoteTransactionWrapper;
|
||||||
|
import org.keycloak.models.map.storage.hotRod.transaction.AllAreasHotRodTransactionsWrapper;
|
||||||
|
|
||||||
public class HotRodMapStorageProvider implements MapStorageProvider {
|
public class HotRodMapStorageProvider implements MapStorageProvider {
|
||||||
|
|
||||||
private final KeycloakSession session;
|
private final KeycloakSession session;
|
||||||
private final HotRodMapStorageProviderFactory factory;
|
private final HotRodMapStorageProviderFactory factory;
|
||||||
|
private final String hotRodConfigurationIdentifier;
|
||||||
|
private final boolean jtaEnabled;
|
||||||
|
|
||||||
public HotRodMapStorageProvider(KeycloakSession session, HotRodMapStorageProviderFactory factory) {
|
public HotRodMapStorageProvider(KeycloakSession session, HotRodMapStorageProviderFactory factory, String hotRodConfigurationIdentifier, boolean jtaEnabled) {
|
||||||
this.session = session;
|
this.session = session;
|
||||||
this.factory = factory;
|
this.factory = factory;
|
||||||
|
this.hotRodConfigurationIdentifier = hotRodConfigurationIdentifier;
|
||||||
|
this.jtaEnabled = jtaEnabled;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public <V extends AbstractEntity, M> MapStorage<V, M> getStorage(Class<M> modelType, MapStorageProviderFactory.Flag... flags) {
|
public <V extends AbstractEntity, M> MapStorage<V, M> getStorage(Class<M> modelType, MapStorageProviderFactory.Flag... flags) {
|
||||||
return (MapStorage<V, M>) factory.getHotRodStorage(session, modelType, flags);
|
// Check if HotRod transaction was already initialized for this configuration within this session
|
||||||
|
AllAreasHotRodTransactionsWrapper txWrapper = session.getAttribute(this.hotRodConfigurationIdentifier, AllAreasHotRodTransactionsWrapper.class);
|
||||||
|
if (txWrapper == null) {
|
||||||
|
// If not create new AllAreasHotRodTransactionsWrapper and put it into session, so it is created only once
|
||||||
|
txWrapper = new AllAreasHotRodTransactionsWrapper();
|
||||||
|
session.setAttribute(this.hotRodConfigurationIdentifier, txWrapper);
|
||||||
|
|
||||||
|
// Enlist the wrapper into prepare phase so it is executed before HotRod client provided transaction
|
||||||
|
session.getTransactionManager().enlistPrepare(txWrapper);
|
||||||
|
|
||||||
|
if (!jtaEnabled) {
|
||||||
|
// If there is no JTA transaction enabled control HotRod client provided transaction manually using
|
||||||
|
// HotRodRemoteTransactionWrapper
|
||||||
|
HotRodConnectionProvider connectionProvider = session.getProvider(HotRodConnectionProvider.class);
|
||||||
|
HotRodEntityDescriptor<?, ?> entityDescriptor = factory.getEntityDescriptor(modelType);
|
||||||
|
RemoteCache<Object, Object> remoteCache = connectionProvider.getRemoteCache(entityDescriptor.getCacheName());
|
||||||
|
session.getTransactionManager().enlist(new HotRodRemoteTransactionWrapper(remoteCache.getTransactionManager()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return (MapStorage<V, M>) factory.getHotRodStorage(session, modelType, txWrapper, flags);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -94,7 +94,9 @@ import org.keycloak.models.map.storage.hotRod.realm.entity.HotRodOTPPolicyEntity
|
||||||
import org.keycloak.models.map.storage.hotRod.realm.entity.HotRodRequiredActionProviderEntityDelegate;
|
import org.keycloak.models.map.storage.hotRod.realm.entity.HotRodRequiredActionProviderEntityDelegate;
|
||||||
import org.keycloak.models.map.storage.hotRod.realm.entity.HotRodRequiredCredentialEntityDelegate;
|
import org.keycloak.models.map.storage.hotRod.realm.entity.HotRodRequiredCredentialEntityDelegate;
|
||||||
import org.keycloak.models.map.storage.hotRod.realm.entity.HotRodWebAuthnPolicyEntityDelegate;
|
import org.keycloak.models.map.storage.hotRod.realm.entity.HotRodWebAuthnPolicyEntityDelegate;
|
||||||
|
import org.keycloak.models.map.storage.hotRod.singleUseObject.HotRodSingleUseObjectEntity;
|
||||||
import org.keycloak.models.map.storage.hotRod.singleUseObject.HotRodSingleUseObjectEntityDelegate;
|
import org.keycloak.models.map.storage.hotRod.singleUseObject.HotRodSingleUseObjectEntityDelegate;
|
||||||
|
import org.keycloak.models.map.storage.hotRod.transaction.AllAreasHotRodTransactionsWrapper;
|
||||||
import org.keycloak.models.map.storage.hotRod.user.HotRodUserConsentEntityDelegate;
|
import org.keycloak.models.map.storage.hotRod.user.HotRodUserConsentEntityDelegate;
|
||||||
import org.keycloak.models.map.storage.hotRod.user.HotRodUserCredentialEntityDelegate;
|
import org.keycloak.models.map.storage.hotRod.user.HotRodUserCredentialEntityDelegate;
|
||||||
import org.keycloak.models.map.storage.hotRod.user.HotRodUserEntityDelegate;
|
import org.keycloak.models.map.storage.hotRod.user.HotRodUserEntityDelegate;
|
||||||
|
@ -111,14 +113,20 @@ import org.keycloak.models.map.userSession.MapAuthenticatedClientSessionEntity;
|
||||||
import org.keycloak.models.map.userSession.MapUserSessionEntity;
|
import org.keycloak.models.map.userSession.MapUserSessionEntity;
|
||||||
import org.keycloak.provider.EnvironmentDependentProviderFactory;
|
import org.keycloak.provider.EnvironmentDependentProviderFactory;
|
||||||
import org.keycloak.storage.SearchableModelField;
|
import org.keycloak.storage.SearchableModelField;
|
||||||
|
import org.keycloak.transaction.JtaTransactionManagerLookup;
|
||||||
|
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.concurrent.ConcurrentHashMap;
|
import java.util.concurrent.atomic.AtomicInteger;
|
||||||
|
|
||||||
public class HotRodMapStorageProviderFactory implements AmphibianProviderFactory<MapStorageProvider>, MapStorageProviderFactory, EnvironmentDependentProviderFactory {
|
public class HotRodMapStorageProviderFactory implements AmphibianProviderFactory<MapStorageProvider>, MapStorageProviderFactory, EnvironmentDependentProviderFactory {
|
||||||
|
|
||||||
public static final String PROVIDER_ID = "hotrod";
|
public static final String PROVIDER_ID = "hotrod";
|
||||||
private final Map<Class<?>, HotRodMapStorage> storages = new ConcurrentHashMap<>();
|
private static final String SESSION_TX_PREFIX = "hotrod-map-tx-";
|
||||||
|
private static final AtomicInteger ENUMERATOR = new AtomicInteger(0);
|
||||||
|
private final String sessionProviderKey;
|
||||||
|
private final String hotRodConfigurationIdentifier;
|
||||||
|
|
||||||
|
private boolean jtaEnabled;
|
||||||
|
|
||||||
private static final Map<SearchableModelField<AuthenticatedClientSessionModel>, MapModelCriteriaBuilder.UpdatePredicatesFunc<Object, AbstractEntity, AuthenticatedClientSessionModel>> clientSessionPredicates = MapFieldPredicates.basePredicates(HotRodAuthenticatedClientSessionEntity.ID);
|
private static final Map<SearchableModelField<AuthenticatedClientSessionModel>, MapModelCriteriaBuilder.UpdatePredicatesFunc<Object, AbstractEntity, AuthenticatedClientSessionModel>> clientSessionPredicates = MapFieldPredicates.basePredicates(HotRodAuthenticatedClientSessionEntity.ID);
|
||||||
|
|
||||||
|
@ -171,44 +179,65 @@ public class HotRodMapStorageProviderFactory implements AmphibianProviderFactory
|
||||||
|
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
|
public HotRodMapStorageProviderFactory() {
|
||||||
|
int index = ENUMERATOR.getAndIncrement();
|
||||||
|
// this identifier is used to create HotRodMapProvider only once per session per factory instance
|
||||||
|
this.sessionProviderKey = HotRodMapStorageProviderFactory.class.getName() + "-" + PROVIDER_ID + "-" + index;
|
||||||
|
|
||||||
|
// When there are more HotRod configurations available in Keycloak (for example, global/realm1/realm2 etc.)
|
||||||
|
// there will be more instances of this factory created where each holds one configuration.
|
||||||
|
// The following identifier can be used to uniquely identify instance of this factory.
|
||||||
|
// This can be later used, for example, to store provider/transaction instances inside session
|
||||||
|
// attributes without collisions between several configurations
|
||||||
|
this.hotRodConfigurationIdentifier = SESSION_TX_PREFIX + index;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public MapStorageProvider create(KeycloakSession session) {
|
public MapStorageProvider create(KeycloakSession session) {
|
||||||
return new HotRodMapStorageProvider(session, this);
|
HotRodMapStorageProvider provider = session.getAttribute(this.sessionProviderKey, HotRodMapStorageProvider.class);
|
||||||
|
if (provider == null) {
|
||||||
|
provider = new HotRodMapStorageProvider(session, this, this.hotRodConfigurationIdentifier, this.jtaEnabled);
|
||||||
|
session.setAttribute(this.sessionProviderKey, provider);
|
||||||
|
}
|
||||||
|
return provider;
|
||||||
}
|
}
|
||||||
|
|
||||||
public HotRodEntityDescriptor<?, ?> getEntityDescriptor(Class<?> c) {
|
public HotRodEntityDescriptor<?, ?> getEntityDescriptor(Class<?> c) {
|
||||||
return AutogeneratedHotRodDescriptors.ENTITY_DESCRIPTOR_MAP.get(c);
|
return AutogeneratedHotRodDescriptors.ENTITY_DESCRIPTOR_MAP.get(c);
|
||||||
}
|
}
|
||||||
|
|
||||||
public <E extends AbstractHotRodEntity, V extends HotRodEntityDelegate<E> & AbstractEntity, M> HotRodMapStorage<String, E, V, M> getHotRodStorage(KeycloakSession session, Class<M> modelType, MapStorageProviderFactory.Flag... flags) {
|
public <E extends AbstractHotRodEntity, V extends HotRodEntityDelegate<E> & AbstractEntity, M> HotRodMapStorage<String, E, V, M> getHotRodStorage(KeycloakSession session, Class<M> modelType, AllAreasHotRodTransactionsWrapper txWrapper, MapStorageProviderFactory.Flag... flags) {
|
||||||
// We need to preload client session store before we load user session store to avoid recursive update of storages map
|
// We need to preload client session store before we load user session store to avoid recursive update of storages map
|
||||||
if (modelType == UserSessionModel.class) getHotRodStorage(session, AuthenticatedClientSessionModel.class, flags);
|
if (modelType == UserSessionModel.class) getHotRodStorage(session, AuthenticatedClientSessionModel.class, txWrapper, flags);
|
||||||
|
|
||||||
return storages.computeIfAbsent(modelType, c -> createHotRodStorage(session, modelType, flags));
|
return createHotRodStorage(session, modelType, txWrapper, flags);
|
||||||
}
|
}
|
||||||
|
|
||||||
private <E extends AbstractHotRodEntity, V extends HotRodEntityDelegate<E> & AbstractEntity, M> HotRodMapStorage<String, E, V, M> createHotRodStorage(KeycloakSession session, Class<M> modelType, MapStorageProviderFactory.Flag... flags) {
|
private <E extends AbstractHotRodEntity, V extends HotRodEntityDelegate<E> & AbstractEntity, M> HotRodMapStorage<String, E, V, M> createHotRodStorage(KeycloakSession session, Class<M> modelType, AllAreasHotRodTransactionsWrapper txWrapper, MapStorageProviderFactory.Flag... flags) {
|
||||||
HotRodConnectionProvider connectionProvider = session.getProvider(HotRodConnectionProvider.class);
|
HotRodConnectionProvider connectionProvider = session.getProvider(HotRodConnectionProvider.class);
|
||||||
HotRodEntityDescriptor<E, V> entityDescriptor = (HotRodEntityDescriptor<E, V>) getEntityDescriptor(modelType);
|
HotRodEntityDescriptor<E, V> entityDescriptor = (HotRodEntityDescriptor<E, V>) getEntityDescriptor(modelType);
|
||||||
|
|
||||||
if (modelType == SingleUseObjectValueModel.class) {
|
if (modelType == SingleUseObjectValueModel.class) {
|
||||||
return new SingleUseObjectHotRodMapStorage(connectionProvider.getRemoteCache(entityDescriptor.getCacheName()), StringKeyConverter.StringKey.INSTANCE, entityDescriptor, CLONER);
|
return (HotRodMapStorage) new SingleUseObjectHotRodMapStorage(connectionProvider.getRemoteCache(entityDescriptor.getCacheName()), StringKeyConverter.StringKey.INSTANCE, (HotRodEntityDescriptor<HotRodSingleUseObjectEntity, HotRodSingleUseObjectEntityDelegate>) getEntityDescriptor(modelType), CLONER, txWrapper);
|
||||||
} if (modelType == AuthenticatedClientSessionModel.class) {
|
} if (modelType == AuthenticatedClientSessionModel.class) {
|
||||||
return new HotRodMapStorage(connectionProvider.getRemoteCache(entityDescriptor.getCacheName()),
|
return new HotRodMapStorage(connectionProvider.getRemoteCache(entityDescriptor.getCacheName()),
|
||||||
StringKeyConverter.StringKey.INSTANCE,
|
StringKeyConverter.StringKey.INSTANCE,
|
||||||
entityDescriptor,
|
entityDescriptor,
|
||||||
CLONER) {
|
CLONER, txWrapper) {
|
||||||
@Override
|
@Override
|
||||||
protected MapKeycloakTransaction createTransactionInternal(KeycloakSession session) {
|
protected MapKeycloakTransaction createTransactionInternal(KeycloakSession session) {
|
||||||
return new ConcurrentHashMapKeycloakTransaction(this, keyConverter, cloner, clientSessionPredicates);
|
return new ConcurrentHashMapKeycloakTransaction(this, keyConverter, cloner, clientSessionPredicates);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
} if (modelType == UserSessionModel.class) {
|
} if (modelType == UserSessionModel.class) {
|
||||||
HotRodMapStorage clientSessionStore = getHotRodStorage(session, AuthenticatedClientSessionModel.class);
|
// Precompute client session storage to avoid recursive initialization
|
||||||
|
HotRodMapStorage clientSessionStore = getHotRodStorage(session, AuthenticatedClientSessionModel.class, txWrapper);
|
||||||
|
clientSessionStore.createTransaction(session);
|
||||||
|
|
||||||
return new HotRodMapStorage(connectionProvider.getRemoteCache(entityDescriptor.getCacheName()),
|
return new HotRodMapStorage(connectionProvider.getRemoteCache(entityDescriptor.getCacheName()),
|
||||||
StringKeyConverter.StringKey.INSTANCE,
|
StringKeyConverter.StringKey.INSTANCE,
|
||||||
entityDescriptor,
|
entityDescriptor,
|
||||||
CLONER) {
|
CLONER, txWrapper) {
|
||||||
@Override
|
@Override
|
||||||
protected MapKeycloakTransaction createTransactionInternal(KeycloakSession session) {
|
protected MapKeycloakTransaction createTransactionInternal(KeycloakSession session) {
|
||||||
Map<SearchableModelField<? super UserSessionModel>, MapModelCriteriaBuilder.UpdatePredicatesFunc<String, MapUserSessionEntity, UserSessionModel>> fieldPredicates = MapFieldPredicates.getPredicates((Class<UserSessionModel>) storedEntityDescriptor.getModelTypeClass());
|
Map<SearchableModelField<? super UserSessionModel>, MapModelCriteriaBuilder.UpdatePredicatesFunc<String, MapUserSessionEntity, UserSessionModel>> fieldPredicates = MapFieldPredicates.getPredicates((Class<UserSessionModel>) storedEntityDescriptor.getModelTypeClass());
|
||||||
|
@ -216,7 +245,7 @@ public class HotRodMapStorageProviderFactory implements AmphibianProviderFactory
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
return new HotRodMapStorage<>(connectionProvider.getRemoteCache(entityDescriptor.getCacheName()), StringKeyConverter.StringKey.INSTANCE, entityDescriptor, CLONER);
|
return new HotRodMapStorage<>(connectionProvider.getRemoteCache(entityDescriptor.getCacheName()), StringKeyConverter.StringKey.INSTANCE, entityDescriptor, CLONER, txWrapper);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -226,7 +255,8 @@ public class HotRodMapStorageProviderFactory implements AmphibianProviderFactory
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void postInit(KeycloakSessionFactory factory) {
|
public void postInit(KeycloakSessionFactory factory) {
|
||||||
|
JtaTransactionManagerLookup jtaLookup = (JtaTransactionManagerLookup) factory.getProviderFactory(JtaTransactionManagerLookup.class);
|
||||||
|
jtaEnabled = jtaLookup != null && jtaLookup.getTransactionManager() != null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -20,21 +20,19 @@ package org.keycloak.models.map.storage.hotRod;
|
||||||
import org.infinispan.client.hotrod.RemoteCache;
|
import org.infinispan.client.hotrod.RemoteCache;
|
||||||
import org.keycloak.models.SingleUseObjectValueModel;
|
import org.keycloak.models.SingleUseObjectValueModel;
|
||||||
import org.keycloak.models.KeycloakSession;
|
import org.keycloak.models.KeycloakSession;
|
||||||
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.StringKeyConverter;
|
import org.keycloak.models.map.common.StringKeyConverter;
|
||||||
|
import org.keycloak.models.map.storage.MapKeycloakTransaction;
|
||||||
import org.keycloak.models.map.storage.chm.MapModelCriteriaBuilder;
|
import org.keycloak.models.map.storage.chm.MapModelCriteriaBuilder;
|
||||||
import org.keycloak.models.map.storage.chm.SingleUseObjectKeycloakTransaction;
|
import org.keycloak.models.map.storage.chm.SingleUseObjectKeycloakTransaction;
|
||||||
import org.keycloak.models.map.storage.MapKeycloakTransaction;
|
|
||||||
import org.keycloak.models.map.storage.QueryParameters;
|
import org.keycloak.models.map.storage.QueryParameters;
|
||||||
import org.keycloak.models.map.storage.chm.MapFieldPredicates;
|
import org.keycloak.models.map.storage.chm.MapFieldPredicates;
|
||||||
import org.keycloak.models.map.storage.chm.SingleUseObjectModelCriteriaBuilder;
|
import org.keycloak.models.map.storage.chm.SingleUseObjectModelCriteriaBuilder;
|
||||||
import org.keycloak.models.map.storage.criteria.DefaultModelCriteria;
|
import org.keycloak.models.map.storage.criteria.DefaultModelCriteria;
|
||||||
import org.keycloak.models.map.storage.hotRod.common.AbstractHotRodEntity;
|
|
||||||
import org.keycloak.models.map.storage.hotRod.common.HotRodEntityDelegate;
|
|
||||||
import org.keycloak.models.map.storage.hotRod.common.HotRodEntityDescriptor;
|
import org.keycloak.models.map.storage.hotRod.common.HotRodEntityDescriptor;
|
||||||
import org.keycloak.models.map.storage.hotRod.singleUseObject.HotRodSingleUseObjectEntity;
|
import org.keycloak.models.map.storage.hotRod.singleUseObject.HotRodSingleUseObjectEntity;
|
||||||
import org.keycloak.models.map.storage.hotRod.singleUseObject.HotRodSingleUseObjectEntityDelegate;
|
import org.keycloak.models.map.storage.hotRod.singleUseObject.HotRodSingleUseObjectEntityDelegate;
|
||||||
|
import org.keycloak.models.map.storage.hotRod.transaction.AllAreasHotRodTransactionsWrapper;
|
||||||
import org.keycloak.storage.SearchableModelField;
|
import org.keycloak.storage.SearchableModelField;
|
||||||
|
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
@ -44,7 +42,7 @@ import java.util.stream.Stream;
|
||||||
/**
|
/**
|
||||||
* @author <a href="mailto:mkanis@redhat.com">Martin Kanis</a>
|
* @author <a href="mailto:mkanis@redhat.com">Martin Kanis</a>
|
||||||
*/
|
*/
|
||||||
public class SingleUseObjectHotRodMapStorage<K, E extends AbstractHotRodEntity, V extends HotRodEntityDelegate<E> & AbstractEntity, M>
|
public class SingleUseObjectHotRodMapStorage
|
||||||
extends HotRodMapStorage<String, HotRodSingleUseObjectEntity, HotRodSingleUseObjectEntityDelegate, SingleUseObjectValueModel> {
|
extends HotRodMapStorage<String, HotRodSingleUseObjectEntity, HotRodSingleUseObjectEntityDelegate, SingleUseObjectValueModel> {
|
||||||
|
|
||||||
private final StringKeyConverter<String> keyConverter;
|
private final StringKeyConverter<String> keyConverter;
|
||||||
|
@ -53,8 +51,8 @@ public class SingleUseObjectHotRodMapStorage<K, E extends AbstractHotRodEntity,
|
||||||
|
|
||||||
public SingleUseObjectHotRodMapStorage(RemoteCache<String, HotRodSingleUseObjectEntity> remoteCache, StringKeyConverter<String> keyConverter,
|
public SingleUseObjectHotRodMapStorage(RemoteCache<String, HotRodSingleUseObjectEntity> remoteCache, StringKeyConverter<String> keyConverter,
|
||||||
HotRodEntityDescriptor<HotRodSingleUseObjectEntity, HotRodSingleUseObjectEntityDelegate> storedEntityDescriptor,
|
HotRodEntityDescriptor<HotRodSingleUseObjectEntity, HotRodSingleUseObjectEntityDelegate> storedEntityDescriptor,
|
||||||
DeepCloner cloner) {
|
DeepCloner cloner, AllAreasHotRodTransactionsWrapper txWrapper) {
|
||||||
super(remoteCache, keyConverter, storedEntityDescriptor, cloner);
|
super(remoteCache, keyConverter, storedEntityDescriptor, cloner, txWrapper);
|
||||||
this.keyConverter = keyConverter;
|
this.keyConverter = keyConverter;
|
||||||
this.storedEntityDescriptor = storedEntityDescriptor;
|
this.storedEntityDescriptor = storedEntityDescriptor;
|
||||||
this.cloner = cloner;
|
this.cloner = cloner;
|
||||||
|
@ -62,7 +60,7 @@ public class SingleUseObjectHotRodMapStorage<K, E extends AbstractHotRodEntity,
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected MapKeycloakTransaction<HotRodSingleUseObjectEntityDelegate, SingleUseObjectValueModel> createTransactionInternal(KeycloakSession session) {
|
protected MapKeycloakTransaction<HotRodSingleUseObjectEntityDelegate, SingleUseObjectValueModel> createTransactionInternal(KeycloakSession session) {
|
||||||
Map<SearchableModelField<? super SingleUseObjectValueModel>, MapModelCriteriaBuilder.UpdatePredicatesFunc<K, HotRodSingleUseObjectEntityDelegate, SingleUseObjectValueModel>> fieldPredicates =
|
Map<SearchableModelField<? super SingleUseObjectValueModel>, MapModelCriteriaBuilder.UpdatePredicatesFunc<String, HotRodSingleUseObjectEntityDelegate, SingleUseObjectValueModel>> fieldPredicates =
|
||||||
MapFieldPredicates.getPredicates((Class<SingleUseObjectValueModel>) storedEntityDescriptor.getModelTypeClass());
|
MapFieldPredicates.getPredicates((Class<SingleUseObjectValueModel>) storedEntityDescriptor.getModelTypeClass());
|
||||||
return new SingleUseObjectKeycloakTransaction(this, keyConverter, cloner, fieldPredicates);
|
return new SingleUseObjectKeycloakTransaction(this, keyConverter, cloner, fieldPredicates);
|
||||||
}
|
}
|
||||||
|
|
|
@ -22,7 +22,9 @@ import org.infinispan.client.hotrod.RemoteCacheManagerAdmin;
|
||||||
import org.infinispan.client.hotrod.configuration.ClientIntelligence;
|
import org.infinispan.client.hotrod.configuration.ClientIntelligence;
|
||||||
import org.infinispan.client.hotrod.configuration.ConfigurationBuilder;
|
import org.infinispan.client.hotrod.configuration.ConfigurationBuilder;
|
||||||
import org.infinispan.client.hotrod.configuration.NearCacheMode;
|
import org.infinispan.client.hotrod.configuration.NearCacheMode;
|
||||||
|
import org.infinispan.client.hotrod.configuration.TransactionMode;
|
||||||
import org.infinispan.commons.marshall.ProtoStreamMarshaller;
|
import org.infinispan.commons.marshall.ProtoStreamMarshaller;
|
||||||
|
import org.infinispan.commons.tx.lookup.TransactionManagerLookup;
|
||||||
import org.infinispan.protostream.GeneratedSchema;
|
import org.infinispan.protostream.GeneratedSchema;
|
||||||
import org.infinispan.query.remote.client.ProtobufMetadataManagerConstants;
|
import org.infinispan.query.remote.client.ProtobufMetadataManagerConstants;
|
||||||
import org.jboss.logging.Logger;
|
import org.jboss.logging.Logger;
|
||||||
|
@ -33,6 +35,7 @@ import org.keycloak.models.map.storage.hotRod.locking.HotRodLocksUtils;
|
||||||
import org.keycloak.models.map.storage.hotRod.common.HotRodEntityDescriptor;
|
import org.keycloak.models.map.storage.hotRod.common.HotRodEntityDescriptor;
|
||||||
import org.keycloak.models.map.storage.hotRod.common.CommonPrimitivesProtoSchemaInitializer;
|
import org.keycloak.models.map.storage.hotRod.common.CommonPrimitivesProtoSchemaInitializer;
|
||||||
import org.keycloak.models.map.storage.hotRod.common.HotRodVersionUtils;
|
import org.keycloak.models.map.storage.hotRod.common.HotRodVersionUtils;
|
||||||
|
import org.keycloak.models.map.storage.hotRod.transaction.HotRodTransactionManagerLookup;
|
||||||
import org.keycloak.provider.EnvironmentDependentProviderFactory;
|
import org.keycloak.provider.EnvironmentDependentProviderFactory;
|
||||||
|
|
||||||
import java.net.URI;
|
import java.net.URI;
|
||||||
|
@ -63,12 +66,14 @@ public class DefaultHotRodConnectionProviderFactory implements HotRodConnectionP
|
||||||
|
|
||||||
private volatile RemoteCacheManager remoteCacheManager;
|
private volatile RemoteCacheManager remoteCacheManager;
|
||||||
|
|
||||||
|
private TransactionManagerLookup transactionManagerLookup;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public HotRodConnectionProvider create(KeycloakSession session) {
|
public HotRodConnectionProvider create(KeycloakSession session) {
|
||||||
if (remoteCacheManager == null) {
|
if (remoteCacheManager == null) {
|
||||||
synchronized (this) {
|
synchronized (this) {
|
||||||
if (remoteCacheManager == null) {
|
if (remoteCacheManager == null) {
|
||||||
lazyInit();
|
lazyInit(session);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -98,8 +103,10 @@ public class DefaultHotRodConnectionProviderFactory implements HotRodConnectionP
|
||||||
this.config = config;
|
this.config = config;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void lazyInit() {
|
public void lazyInit(KeycloakSession session) {
|
||||||
LOG.debugf("Initializing HotRod client connection to Infinispan server.");
|
LOG.debugf("Initializing HotRod client connection to Infinispan server.");
|
||||||
|
transactionManagerLookup = new HotRodTransactionManagerLookup(session);
|
||||||
|
|
||||||
ConfigurationBuilder remoteBuilder = new ConfigurationBuilder();
|
ConfigurationBuilder remoteBuilder = new ConfigurationBuilder();
|
||||||
remoteBuilder.addServer()
|
remoteBuilder.addServer()
|
||||||
.host(config.get("host", "localhost"))
|
.host(config.get("host", "localhost"))
|
||||||
|
@ -265,6 +272,8 @@ public class DefaultHotRodConnectionProviderFactory implements HotRodConnectionP
|
||||||
LOG.debugf("Configuring cache %s", cacheName);
|
LOG.debugf("Configuring cache %s", cacheName);
|
||||||
builder.remoteCache(cacheName)
|
builder.remoteCache(cacheName)
|
||||||
.configurationURI(getCacheConfigUri(cacheName))
|
.configurationURI(getCacheConfigUri(cacheName))
|
||||||
|
.transactionMode(TransactionMode.FULL_XA)
|
||||||
|
.transactionManagerLookup(transactionManagerLookup)
|
||||||
.nearCacheMode(config.scope(cacheName).getBoolean("nearCacheEnabled", config.getBoolean("nearCacheEnabled", true)) ? NearCacheMode.INVALIDATED : NearCacheMode.DISABLED)
|
.nearCacheMode(config.scope(cacheName).getBoolean("nearCacheEnabled", config.getBoolean("nearCacheEnabled", true)) ? NearCacheMode.INVALIDATED : NearCacheMode.DISABLED)
|
||||||
.nearCacheMaxEntries(config.scope(cacheName).getInt("nearCacheMaxEntries", config.getInt("nearCacheMaxEntries", 10000)))
|
.nearCacheMaxEntries(config.scope(cacheName).getInt("nearCacheMaxEntries", config.getInt("nearCacheMaxEntries", 10000)))
|
||||||
.nearCacheUseBloomFilter(config.scope(cacheName).getBoolean("nearCacheBloomFilter", config.getBoolean("nearCacheBloomFilter", false)));
|
.nearCacheUseBloomFilter(config.scope(cacheName).getBoolean("nearCacheBloomFilter", config.getBoolean("nearCacheBloomFilter", false)));
|
||||||
|
|
|
@ -0,0 +1,59 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2022 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.hotRod.transaction;
|
||||||
|
|
||||||
|
import org.keycloak.models.AbstractKeycloakTransaction;
|
||||||
|
import org.keycloak.models.map.storage.MapKeycloakTransaction;
|
||||||
|
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
import java.util.function.Supplier;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This wrapper encapsulates transactions from all areas. This is needed because we need to control when the changes
|
||||||
|
* from each area are applied to make sure it is performed before the HotRod client provided transaction is committed.
|
||||||
|
*/
|
||||||
|
public class AllAreasHotRodTransactionsWrapper extends AbstractKeycloakTransaction {
|
||||||
|
|
||||||
|
private final Map<Class<?>, MapKeycloakTransaction<?, ?>> MapKeycloakTransactionsMap = new ConcurrentHashMap<>();
|
||||||
|
|
||||||
|
public MapKeycloakTransaction<?, ?> getOrCreateTxForModel(Class<?> modelType, Supplier<MapKeycloakTransaction<?,?>> supplier) {
|
||||||
|
MapKeycloakTransaction<?, ?> tx = MapKeycloakTransactionsMap.computeIfAbsent(modelType, t -> supplier.get());
|
||||||
|
if (!tx.isActive()) {
|
||||||
|
tx.begin();
|
||||||
|
}
|
||||||
|
|
||||||
|
return tx;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void commitImpl() {
|
||||||
|
MapKeycloakTransactionsMap.values().forEach(MapKeycloakTransaction::commit);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void rollbackImpl() {
|
||||||
|
MapKeycloakTransactionsMap.values().forEach(MapKeycloakTransaction::rollback);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setRollbackOnly() {
|
||||||
|
super.setRollbackOnly();
|
||||||
|
MapKeycloakTransactionsMap.values().forEach(MapKeycloakTransaction::setRollbackOnly);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,105 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2022 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.hotRod.transaction;
|
||||||
|
|
||||||
|
import org.keycloak.models.KeycloakTransaction;
|
||||||
|
|
||||||
|
import javax.transaction.HeuristicMixedException;
|
||||||
|
import javax.transaction.HeuristicRollbackException;
|
||||||
|
import javax.transaction.NotSupportedException;
|
||||||
|
import javax.transaction.RollbackException;
|
||||||
|
import javax.transaction.Status;
|
||||||
|
import javax.transaction.SystemException;
|
||||||
|
import javax.transaction.TransactionManager;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* When no JTA transaction is present in the runtime this wrapper is used
|
||||||
|
* to enlist HotRod client provided transaction to our
|
||||||
|
* {@link KeycloakTransactionManager}. If JTA transaction is present this should
|
||||||
|
* not be used.
|
||||||
|
*/
|
||||||
|
public class HotRodRemoteTransactionWrapper implements KeycloakTransaction {
|
||||||
|
|
||||||
|
private final TransactionManager transactionManager;
|
||||||
|
|
||||||
|
public HotRodRemoteTransactionWrapper(TransactionManager transactionManager) {
|
||||||
|
this.transactionManager = transactionManager;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void begin() {
|
||||||
|
try {
|
||||||
|
if (transactionManager.getStatus() == Status.STATUS_NO_TRANSACTION) {
|
||||||
|
transactionManager.begin();
|
||||||
|
}
|
||||||
|
} catch (NotSupportedException | SystemException e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void commit() {
|
||||||
|
try {
|
||||||
|
if (transactionManager.getStatus() == Status.STATUS_ACTIVE) {
|
||||||
|
transactionManager.commit();
|
||||||
|
}
|
||||||
|
} catch (HeuristicRollbackException | SystemException | HeuristicMixedException | RollbackException e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void rollback() {
|
||||||
|
try {
|
||||||
|
if (transactionManager.getStatus() == Status.STATUS_ACTIVE) {
|
||||||
|
transactionManager.rollback();
|
||||||
|
}
|
||||||
|
} catch (SystemException e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setRollbackOnly() {
|
||||||
|
try {
|
||||||
|
if (transactionManager.getStatus() == Status.STATUS_ACTIVE) {
|
||||||
|
transactionManager.setRollbackOnly();
|
||||||
|
}
|
||||||
|
} catch (SystemException e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean getRollbackOnly() {
|
||||||
|
try {
|
||||||
|
return transactionManager.getStatus() == Status.STATUS_MARKED_ROLLBACK;
|
||||||
|
} catch (SystemException e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isActive() {
|
||||||
|
try {
|
||||||
|
return transactionManager.getStatus() == Status.STATUS_ACTIVE;
|
||||||
|
} catch (SystemException e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,51 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2022 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.hotRod.transaction;
|
||||||
|
|
||||||
|
import org.infinispan.client.hotrod.transaction.manager.RemoteTransactionManager;
|
||||||
|
import org.infinispan.commons.tx.lookup.TransactionManagerLookup;
|
||||||
|
import org.keycloak.models.KeycloakSession;
|
||||||
|
import org.keycloak.transaction.JtaTransactionManagerLookup;
|
||||||
|
|
||||||
|
import javax.transaction.TransactionManager;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* HotRod client provides its own {@link org.infinispan.client.hotrod.transaction.lookup.GenericTransactionManagerLookup}
|
||||||
|
* that is able to locate variety of JTA transaction implementation present
|
||||||
|
* in the runtime. We need to make sure we use JTA only when it is detected
|
||||||
|
* by other parts of Keycloak (such as {@link org.keycloak.models.KeycloakTransactionManager}),
|
||||||
|
* therefore we implemented this custom TransactionManagerLookup that locates
|
||||||
|
* JTA transaction using {@link JtaTransactionManagerLookup} provider
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
public class HotRodTransactionManagerLookup implements TransactionManagerLookup {
|
||||||
|
|
||||||
|
private final TransactionManager transactionManager;
|
||||||
|
|
||||||
|
public HotRodTransactionManagerLookup(KeycloakSession session) {
|
||||||
|
JtaTransactionManagerLookup jtaLookup = session.getProvider(JtaTransactionManagerLookup.class);
|
||||||
|
TransactionManager txManager = jtaLookup != null ? jtaLookup.getTransactionManager() : null;
|
||||||
|
transactionManager = txManager != null ? txManager : RemoteTransactionManager.getInstance();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public TransactionManager getTransactionManager() throws Exception {
|
||||||
|
return transactionManager;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,101 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2022 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.hotRod.transaction;
|
||||||
|
|
||||||
|
import org.keycloak.models.map.common.AbstractEntity;
|
||||||
|
import org.keycloak.models.map.common.UpdatableEntity;
|
||||||
|
import org.keycloak.models.map.storage.MapKeycloakTransaction;
|
||||||
|
import org.keycloak.models.map.storage.QueryParameters;
|
||||||
|
import org.keycloak.models.map.storage.chm.ConcurrentHashMapKeycloakTransaction;
|
||||||
|
|
||||||
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This is used to return ConcurrentHashMapTransaction (used for operating
|
||||||
|
* RemoteCache) functionality to providers but not enlist actualTx the way
|
||||||
|
* we need: in prepare phase.
|
||||||
|
*/
|
||||||
|
public class NoActionHotRodTransactionWrapper<K, V extends AbstractEntity & UpdatableEntity, M> implements MapKeycloakTransaction<V, M> {
|
||||||
|
|
||||||
|
|
||||||
|
private final ConcurrentHashMapKeycloakTransaction<K, V, M> actualTx;
|
||||||
|
|
||||||
|
public NoActionHotRodTransactionWrapper(ConcurrentHashMapKeycloakTransaction<K, V, M> actualTx) {
|
||||||
|
this.actualTx = actualTx;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public V create(V value) {
|
||||||
|
return actualTx.create(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public V read(String key) {
|
||||||
|
return actualTx.read(key);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Stream<V> read(QueryParameters<M> queryParameters) {
|
||||||
|
return actualTx.read(queryParameters);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public long getCount(QueryParameters<M> queryParameters) {
|
||||||
|
return actualTx.getCount(queryParameters);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean delete(String key) {
|
||||||
|
return actualTx.delete(key);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public long delete(QueryParameters<M> queryParameters) {
|
||||||
|
return actualTx.delete(queryParameters);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void begin() {
|
||||||
|
// Does nothing
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void commit() {
|
||||||
|
// Does nothing
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void rollback() {
|
||||||
|
// Does nothing
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setRollbackOnly() {
|
||||||
|
actualTx.setRollbackOnly();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean getRollbackOnly() {
|
||||||
|
return actualTx.getRollbackOnly();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isActive() {
|
||||||
|
return actualTx.isActive();
|
||||||
|
}
|
||||||
|
}
|
|
@ -16,6 +16,8 @@
|
||||||
-->
|
-->
|
||||||
<distributed-cache name="admin-events" mode="SYNC">
|
<distributed-cache name="admin-events" mode="SYNC">
|
||||||
<encoding media-type="application/x-protostream"/>
|
<encoding media-type="application/x-protostream"/>
|
||||||
|
<locking isolation="REPEATABLE_READ" />
|
||||||
|
<transaction mode="FULL_XA"/>
|
||||||
<indexing>
|
<indexing>
|
||||||
<indexed-entities>
|
<indexed-entities>
|
||||||
<indexed-entity>kc.HotRodAdminEventEntity</indexed-entity>
|
<indexed-entity>kc.HotRodAdminEventEntity</indexed-entity>
|
||||||
|
|
|
@ -16,6 +16,8 @@
|
||||||
-->
|
-->
|
||||||
<distributed-cache name="auth-events" mode="SYNC">
|
<distributed-cache name="auth-events" mode="SYNC">
|
||||||
<encoding media-type="application/x-protostream"/>
|
<encoding media-type="application/x-protostream"/>
|
||||||
|
<locking isolation="REPEATABLE_READ" />
|
||||||
|
<transaction mode="FULL_XA"/>
|
||||||
<indexing>
|
<indexing>
|
||||||
<indexed-entities>
|
<indexed-entities>
|
||||||
<indexed-entity>kc.HotRodAuthEventEntity</indexed-entity>
|
<indexed-entity>kc.HotRodAuthEventEntity</indexed-entity>
|
||||||
|
|
|
@ -15,10 +15,12 @@
|
||||||
~ limitations under the License.
|
~ limitations under the License.
|
||||||
-->
|
-->
|
||||||
<distributed-cache name="auth-sessions" mode="SYNC">
|
<distributed-cache name="auth-sessions" mode="SYNC">
|
||||||
|
<encoding media-type="application/x-protostream"/>
|
||||||
|
<locking isolation="REPEATABLE_READ" />
|
||||||
|
<transaction mode="FULL_XA"/>
|
||||||
<indexing>
|
<indexing>
|
||||||
<indexed-entities>
|
<indexed-entities>
|
||||||
<indexed-entity>kc.HotRodRootAuthenticationSessionEntity</indexed-entity>
|
<indexed-entity>kc.HotRodRootAuthenticationSessionEntity</indexed-entity>
|
||||||
</indexed-entities>
|
</indexed-entities>
|
||||||
</indexing>
|
</indexing>
|
||||||
<encoding media-type="application/x-protostream"/>
|
|
||||||
</distributed-cache>
|
</distributed-cache>
|
||||||
|
|
|
@ -16,6 +16,8 @@
|
||||||
-->
|
-->
|
||||||
<distributed-cache name="authz" mode="SYNC">
|
<distributed-cache name="authz" mode="SYNC">
|
||||||
<encoding media-type="application/x-protostream"/>
|
<encoding media-type="application/x-protostream"/>
|
||||||
|
<locking isolation="REPEATABLE_READ" />
|
||||||
|
<transaction mode="FULL_XA"/>
|
||||||
<indexing>
|
<indexing>
|
||||||
<indexed-entities>
|
<indexed-entities>
|
||||||
<indexed-entity>kc.HotRodResourceServerEntity</indexed-entity>
|
<indexed-entity>kc.HotRodResourceServerEntity</indexed-entity>
|
||||||
|
|
|
@ -15,10 +15,12 @@
|
||||||
~ limitations under the License.
|
~ limitations under the License.
|
||||||
-->
|
-->
|
||||||
<distributed-cache name="client-scopes" mode="SYNC">
|
<distributed-cache name="client-scopes" mode="SYNC">
|
||||||
|
<encoding media-type="application/x-protostream"/>
|
||||||
|
<locking isolation="REPEATABLE_READ" />
|
||||||
|
<transaction mode="FULL_XA"/>
|
||||||
<indexing>
|
<indexing>
|
||||||
<indexed-entities>
|
<indexed-entities>
|
||||||
<indexed-entity>kc.HotRodClientScopeEntity</indexed-entity>
|
<indexed-entity>kc.HotRodClientScopeEntity</indexed-entity>
|
||||||
</indexed-entities>
|
</indexed-entities>
|
||||||
</indexing>
|
</indexing>
|
||||||
<encoding media-type="application/x-protostream"/>
|
|
||||||
</distributed-cache>
|
</distributed-cache>
|
||||||
|
|
|
@ -15,10 +15,12 @@
|
||||||
~ limitations under the License.
|
~ limitations under the License.
|
||||||
-->
|
-->
|
||||||
<distributed-cache name="clients" mode="SYNC">
|
<distributed-cache name="clients" mode="SYNC">
|
||||||
|
<encoding media-type="application/x-protostream"/>
|
||||||
|
<locking isolation="REPEATABLE_READ" />
|
||||||
|
<transaction mode="FULL_XA"/>
|
||||||
<indexing>
|
<indexing>
|
||||||
<indexed-entities>
|
<indexed-entities>
|
||||||
<indexed-entity>kc.HotRodClientEntity</indexed-entity>
|
<indexed-entity>kc.HotRodClientEntity</indexed-entity>
|
||||||
</indexed-entities>
|
</indexed-entities>
|
||||||
</indexing>
|
</indexing>
|
||||||
<encoding media-type="application/x-protostream"/>
|
|
||||||
</distributed-cache>
|
</distributed-cache>
|
||||||
|
|
|
@ -15,10 +15,12 @@
|
||||||
~ limitations under the License.
|
~ limitations under the License.
|
||||||
-->
|
-->
|
||||||
<distributed-cache name="groups" mode="SYNC">
|
<distributed-cache name="groups" mode="SYNC">
|
||||||
|
<encoding media-type="application/x-protostream"/>
|
||||||
|
<locking isolation="REPEATABLE_READ" />
|
||||||
|
<transaction mode="FULL_XA"/>
|
||||||
<indexing>
|
<indexing>
|
||||||
<indexed-entities>
|
<indexed-entities>
|
||||||
<indexed-entity>kc.HotRodGroupEntity</indexed-entity>
|
<indexed-entity>kc.HotRodGroupEntity</indexed-entity>
|
||||||
</indexed-entities>
|
</indexed-entities>
|
||||||
</indexing>
|
</indexing>
|
||||||
<encoding media-type="application/x-protostream"/>
|
|
||||||
</distributed-cache>
|
</distributed-cache>
|
||||||
|
|
|
@ -15,5 +15,7 @@
|
||||||
~ limitations under the License.
|
~ limitations under the License.
|
||||||
-->
|
-->
|
||||||
<replicated-cache name="locks" mode="SYNC">
|
<replicated-cache name="locks" mode="SYNC">
|
||||||
|
<locking isolation="REPEATABLE_READ" />
|
||||||
|
<transaction mode="NON_XA"/>
|
||||||
<encoding media-type="text/plain"/>
|
<encoding media-type="text/plain"/>
|
||||||
</replicated-cache>
|
</replicated-cache>
|
||||||
|
|
|
@ -15,12 +15,14 @@
|
||||||
~ limitations under the License.
|
~ limitations under the License.
|
||||||
-->
|
-->
|
||||||
<distributed-cache name="realms" mode="SYNC">
|
<distributed-cache name="realms" mode="SYNC">
|
||||||
|
<encoding media-type="application/x-protostream"/>
|
||||||
|
<locking isolation="REPEATABLE_READ" />
|
||||||
|
<transaction mode="FULL_XA"/>
|
||||||
<indexing>
|
<indexing>
|
||||||
<indexed-entities>
|
<indexed-entities>
|
||||||
<indexed-entity>kc.HotRodRealmEntity</indexed-entity>
|
<indexed-entity>kc.HotRodRealmEntity</indexed-entity>
|
||||||
</indexed-entities>
|
</indexed-entities>
|
||||||
</indexing>
|
</indexing>
|
||||||
<encoding media-type="application/x-protostream"/>
|
|
||||||
</distributed-cache>
|
</distributed-cache>
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -15,11 +15,13 @@
|
||||||
~ limitations under the License.
|
~ limitations under the License.
|
||||||
-->
|
-->
|
||||||
<distributed-cache name="roles" mode="SYNC">
|
<distributed-cache name="roles" mode="SYNC">
|
||||||
|
<encoding media-type="application/x-protostream"/>
|
||||||
|
<locking isolation="REPEATABLE_READ" />
|
||||||
|
<transaction mode="FULL_XA"/>
|
||||||
<indexing>
|
<indexing>
|
||||||
<indexed-entities>
|
<indexed-entities>
|
||||||
<indexed-entity>kc.HotRodRoleEntity</indexed-entity>
|
<indexed-entity>kc.HotRodRoleEntity</indexed-entity>
|
||||||
</indexed-entities>
|
</indexed-entities>
|
||||||
</indexing>
|
</indexing>
|
||||||
<encoding media-type="application/x-protostream"/>
|
|
||||||
</distributed-cache>
|
</distributed-cache>
|
||||||
|
|
||||||
|
|
|
@ -15,12 +15,14 @@
|
||||||
~ limitations under the License.
|
~ limitations under the License.
|
||||||
-->
|
-->
|
||||||
<distributed-cache name="single-use-objects" mode="SYNC">
|
<distributed-cache name="single-use-objects" mode="SYNC">
|
||||||
|
<encoding media-type="application/x-protostream"/>
|
||||||
|
<locking isolation="REPEATABLE_READ" />
|
||||||
|
<transaction mode="FULL_XA"/>
|
||||||
<indexing>
|
<indexing>
|
||||||
<indexed-entities>
|
<indexed-entities>
|
||||||
<indexed-entity>kc.HotRodSingleUseObjectEntity</indexed-entity>
|
<indexed-entity>kc.HotRodSingleUseObjectEntity</indexed-entity>
|
||||||
</indexed-entities>
|
</indexed-entities>
|
||||||
</indexing>
|
</indexing>
|
||||||
<encoding media-type="application/x-protostream"/>
|
|
||||||
</distributed-cache>
|
</distributed-cache>
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -15,11 +15,13 @@
|
||||||
~ limitations under the License.
|
~ limitations under the License.
|
||||||
-->
|
-->
|
||||||
<distributed-cache name="user-login-failures" mode="SYNC">
|
<distributed-cache name="user-login-failures" mode="SYNC">
|
||||||
|
<encoding media-type="application/x-protostream"/>
|
||||||
|
<locking isolation="REPEATABLE_READ" />
|
||||||
|
<transaction mode="FULL_XA"/>
|
||||||
<indexing>
|
<indexing>
|
||||||
<indexed-entities>
|
<indexed-entities>
|
||||||
<indexed-entity>kc.HotRodUserLoginFailureEntity</indexed-entity>
|
<indexed-entity>kc.HotRodUserLoginFailureEntity</indexed-entity>
|
||||||
</indexed-entities>
|
</indexed-entities>
|
||||||
</indexing>
|
</indexing>
|
||||||
<encoding media-type="application/x-protostream"/>
|
|
||||||
</distributed-cache>
|
</distributed-cache>
|
||||||
|
|
||||||
|
|
|
@ -15,12 +15,14 @@
|
||||||
~ limitations under the License.
|
~ limitations under the License.
|
||||||
-->
|
-->
|
||||||
<distributed-cache name="user-sessions" mode="SYNC">
|
<distributed-cache name="user-sessions" mode="SYNC">
|
||||||
|
<encoding media-type="application/x-protostream"/>
|
||||||
|
<locking isolation="REPEATABLE_READ" />
|
||||||
|
<transaction mode="FULL_XA"/>
|
||||||
<indexing>
|
<indexing>
|
||||||
<indexed-entities>
|
<indexed-entities>
|
||||||
<indexed-entity>kc.HotRodUserSessionEntity</indexed-entity>
|
<indexed-entity>kc.HotRodUserSessionEntity</indexed-entity>
|
||||||
<indexed-entity>kc.HotRodAuthenticatedClientSessionEntity</indexed-entity>
|
<indexed-entity>kc.HotRodAuthenticatedClientSessionEntity</indexed-entity>
|
||||||
</indexed-entities>
|
</indexed-entities>
|
||||||
</indexing>
|
</indexing>
|
||||||
<encoding media-type="application/x-protostream"/>
|
|
||||||
</distributed-cache>
|
</distributed-cache>
|
||||||
|
|
||||||
|
|
|
@ -15,11 +15,13 @@
|
||||||
~ limitations under the License.
|
~ limitations under the License.
|
||||||
-->
|
-->
|
||||||
<distributed-cache name="users" mode="SYNC">
|
<distributed-cache name="users" mode="SYNC">
|
||||||
|
<encoding media-type="application/x-protostream"/>
|
||||||
|
<locking isolation="REPEATABLE_READ" />
|
||||||
|
<transaction mode="FULL_XA"/>
|
||||||
<indexing>
|
<indexing>
|
||||||
<indexed-entities>
|
<indexed-entities>
|
||||||
<indexed-entity>kc.HotRodUserEntity</indexed-entity>
|
<indexed-entity>kc.HotRodUserEntity</indexed-entity>
|
||||||
</indexed-entities>
|
</indexed-entities>
|
||||||
</indexing>
|
</indexing>
|
||||||
<encoding media-type="application/x-protostream"/>
|
|
||||||
</distributed-cache>
|
</distributed-cache>
|
||||||
|
|
||||||
|
|
|
@ -259,7 +259,14 @@ public class JpaMapStorageProviderFactory implements
|
||||||
|
|
||||||
public JpaMapStorageProviderFactory() {
|
public JpaMapStorageProviderFactory() {
|
||||||
int index = ENUMERATOR.getAndIncrement();
|
int index = ENUMERATOR.getAndIncrement();
|
||||||
|
// this identifier is used to create HotRodMapProvider only once per session per factory instance
|
||||||
this.sessionProviderKey = PROVIDER_ID + "-" + index;
|
this.sessionProviderKey = PROVIDER_ID + "-" + index;
|
||||||
|
|
||||||
|
// When there are more JPA configurations available in Keycloak (for example, global/realm1/realm2 etc.)
|
||||||
|
// there will be more instances of this factory created where each holds one configuration.
|
||||||
|
// The following identifier can be used to uniquely identify instance of this factory.
|
||||||
|
// This can be later used, for example, to store provider/transaction instances inside session
|
||||||
|
// attributes without collisions between several configurations
|
||||||
this.sessionTxKey = SESSION_TX_PREFIX + index;
|
this.sessionTxKey = SESSION_TX_PREFIX + index;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -26,6 +26,8 @@ import org.keycloak.provider.Spi;
|
||||||
*/
|
*/
|
||||||
public class RealmSpi implements Spi {
|
public class RealmSpi implements Spi {
|
||||||
|
|
||||||
|
public static final String NAME = "realm";
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean isInternal() {
|
public boolean isInternal() {
|
||||||
return true;
|
return true;
|
||||||
|
@ -33,7 +35,7 @@ public class RealmSpi implements Spi {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getName() {
|
public String getName() {
|
||||||
return "realm";
|
return NAME;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -17,12 +17,18 @@
|
||||||
|
|
||||||
package org.keycloak.testsuite.admin;
|
package org.keycloak.testsuite.admin;
|
||||||
|
|
||||||
|
import org.junit.Assume;
|
||||||
|
import org.keycloak.Config;
|
||||||
import org.keycloak.admin.client.resource.ComponentResource;
|
import org.keycloak.admin.client.resource.ComponentResource;
|
||||||
import org.junit.Before;
|
import org.junit.Before;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
import org.keycloak.admin.client.resource.ComponentsResource;
|
import org.keycloak.admin.client.resource.ComponentsResource;
|
||||||
import org.keycloak.admin.client.resource.RealmResource;
|
import org.keycloak.admin.client.resource.RealmResource;
|
||||||
import org.keycloak.common.util.MultivaluedHashMap;
|
import org.keycloak.common.util.MultivaluedHashMap;
|
||||||
|
import org.keycloak.models.RealmSpi;
|
||||||
|
import org.keycloak.models.map.common.AbstractMapProviderFactory;
|
||||||
|
import org.keycloak.models.map.realm.MapRealmProviderFactory;
|
||||||
|
import org.keycloak.models.map.storage.hotRod.HotRodMapStorageProviderFactory;
|
||||||
import org.keycloak.models.utils.KeycloakModelUtils;
|
import org.keycloak.models.utils.KeycloakModelUtils;
|
||||||
import org.keycloak.representations.idm.*;
|
import org.keycloak.representations.idm.*;
|
||||||
import org.keycloak.testsuite.components.TestProvider;
|
import org.keycloak.testsuite.components.TestProvider;
|
||||||
|
@ -39,6 +45,7 @@ import java.util.function.BiConsumer;
|
||||||
import org.apache.commons.lang3.concurrent.BasicThreadFactory;
|
import org.apache.commons.lang3.concurrent.BasicThreadFactory;
|
||||||
import org.hamcrest.Matchers;
|
import org.hamcrest.Matchers;
|
||||||
import static org.hamcrest.Matchers.contains;
|
import static org.hamcrest.Matchers.contains;
|
||||||
|
import static org.hamcrest.Matchers.equalTo;
|
||||||
import static org.junit.Assert.*;
|
import static org.junit.Assert.*;
|
||||||
import static org.hamcrest.Matchers.containsString;
|
import static org.hamcrest.Matchers.containsString;
|
||||||
import static org.hamcrest.Matchers.not;
|
import static org.hamcrest.Matchers.not;
|
||||||
|
@ -50,8 +57,19 @@ public class ComponentsTest extends AbstractAdminTest {
|
||||||
|
|
||||||
private ComponentsResource components;
|
private ComponentsResource components;
|
||||||
|
|
||||||
|
private String realmProvider;
|
||||||
|
|
||||||
@Before
|
@Before
|
||||||
public void before() throws Exception {
|
public void before() throws Exception {
|
||||||
|
// realmProvider is used only to prevent tests from running in certain configs, should be removed once GHI #15410 is resolved.
|
||||||
|
realmProvider = testingClient.server().fetch(session -> Config.getProvider(RealmSpi.NAME), String.class);
|
||||||
|
if (realmProvider.equals(MapRealmProviderFactory.PROVIDER_ID)) {
|
||||||
|
// append the storage provider in case of map
|
||||||
|
String mapStorageProvider = testingClient.server().fetch(session -> Config.scope(RealmSpi.NAME,
|
||||||
|
MapRealmProviderFactory.PROVIDER_ID, AbstractMapProviderFactory.CONFIG_STORAGE).get("provider"), String.class);
|
||||||
|
if (mapStorageProvider != null) realmProvider = realmProvider + "-" + mapStorageProvider;
|
||||||
|
}
|
||||||
|
|
||||||
components = adminClient.realm(REALM_NAME).components();
|
components = adminClient.realm(REALM_NAME).components();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -81,6 +99,11 @@ public class ComponentsTest extends AbstractAdminTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testConcurrencyWithoutChildren() throws InterruptedException {
|
public void testConcurrencyWithoutChildren() throws InterruptedException {
|
||||||
|
// remove this restriction once GHI #15410 is resolved.
|
||||||
|
Assume.assumeThat("Test does not run with HotRod after HotRod client transaction was enabled. This will be removed with pessimistic locking introduction.",
|
||||||
|
realmProvider,
|
||||||
|
not(equalTo(MapRealmProviderFactory.PROVIDER_ID + "-" + HotRodMapStorageProviderFactory.PROVIDER_ID)));
|
||||||
|
|
||||||
testConcurrency((s, i) -> s.submit(new CreateAndDeleteComponent(s, i)));
|
testConcurrency((s, i) -> s.submit(new CreateAndDeleteComponent(s, i)));
|
||||||
|
|
||||||
// Data consistency is not guaranteed with concurrent access to entities in map store.
|
// Data consistency is not guaranteed with concurrent access to entities in map store.
|
||||||
|
@ -91,6 +114,11 @@ public class ComponentsTest extends AbstractAdminTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testConcurrencyWithChildren() throws InterruptedException {
|
public void testConcurrencyWithChildren() throws InterruptedException {
|
||||||
|
// remove this restriction once GHI #15410 is resolved.
|
||||||
|
Assume.assumeThat("Test does not run with HotRod after HotRod client transaction was enabled. This will be removed with pessimistic locking introduction.",
|
||||||
|
realmProvider,
|
||||||
|
not(equalTo(MapRealmProviderFactory.PROVIDER_ID + "-" + HotRodMapStorageProviderFactory.PROVIDER_ID)));
|
||||||
|
|
||||||
testConcurrency((s, i) -> s.submit(new CreateAndDeleteComponentWithFlatChildren(s, i)));
|
testConcurrency((s, i) -> s.submit(new CreateAndDeleteComponentWithFlatChildren(s, i)));
|
||||||
|
|
||||||
// Data consistency is not guaranteed with concurrent access to entities in map store.
|
// Data consistency is not guaranteed with concurrent access to entities in map store.
|
||||||
|
|
|
@ -53,7 +53,7 @@ import org.keycloak.admin.client.resource.RealmResource;
|
||||||
import org.keycloak.jose.jws.JWSInput;
|
import org.keycloak.jose.jws.JWSInput;
|
||||||
import org.keycloak.models.UserSessionSpi;
|
import org.keycloak.models.UserSessionSpi;
|
||||||
import org.keycloak.models.map.common.AbstractMapProviderFactory;
|
import org.keycloak.models.map.common.AbstractMapProviderFactory;
|
||||||
import org.keycloak.models.map.storage.MapStorageProviderFactory;
|
import org.keycloak.models.map.storage.hotRod.HotRodMapStorageProviderFactory;
|
||||||
import org.keycloak.models.map.storage.jpa.JpaMapStorageProviderFactory;
|
import org.keycloak.models.map.storage.jpa.JpaMapStorageProviderFactory;
|
||||||
import org.keycloak.models.map.userSession.MapUserSessionProviderFactory;
|
import org.keycloak.models.map.userSession.MapUserSessionProviderFactory;
|
||||||
import org.keycloak.models.sessions.infinispan.InfinispanUserSessionProviderFactory;
|
import org.keycloak.models.sessions.infinispan.InfinispanUserSessionProviderFactory;
|
||||||
|
@ -76,6 +76,7 @@ import org.apache.http.impl.client.BasicCookieStore;
|
||||||
import org.hamcrest.Matchers;
|
import org.hamcrest.Matchers;
|
||||||
import org.keycloak.util.JsonSerialization;
|
import org.keycloak.util.JsonSerialization;
|
||||||
|
|
||||||
|
import static org.hamcrest.CoreMatchers.not;
|
||||||
import static org.hamcrest.Matchers.containsString;
|
import static org.hamcrest.Matchers.containsString;
|
||||||
import static org.hamcrest.Matchers.equalTo;
|
import static org.hamcrest.Matchers.equalTo;
|
||||||
import static org.keycloak.testsuite.util.ServerURLs.AUTH_SERVER_SSL_REQUIRED;
|
import static org.keycloak.testsuite.util.ServerURLs.AUTH_SERVER_SSL_REQUIRED;
|
||||||
|
@ -173,6 +174,11 @@ public class ConcurrentLoginTest extends AbstractConcurrencyTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void concurrentLoginSingleUserSingleClient() throws Throwable {
|
public void concurrentLoginSingleUserSingleClient() throws Throwable {
|
||||||
|
// remove this restriction once GHI #15410 is resolved.
|
||||||
|
Assume.assumeThat("Test does not run with HotRod after HotRod client transaction was enabled. This will be removed with pessimistic locking introduction.",
|
||||||
|
userSessionProvider,
|
||||||
|
not(equalTo(MapUserSessionProviderFactory.PROVIDER_ID + "-" + HotRodMapStorageProviderFactory.PROVIDER_ID)));
|
||||||
|
|
||||||
log.info("*********************************************");
|
log.info("*********************************************");
|
||||||
long start = System.currentTimeMillis();
|
long start = System.currentTimeMillis();
|
||||||
|
|
||||||
|
|
|
@ -104,9 +104,13 @@ public class SingleUseObjectModelTest extends KeycloakModelTest {
|
||||||
Map<String, String> notes = singleUseObjectProvider.get(key.serializeKey());
|
Map<String, String> notes = singleUseObjectProvider.get(key.serializeKey());
|
||||||
Assert.assertNotNull(notes);
|
Assert.assertNotNull(notes);
|
||||||
Assert.assertEquals("bar", notes.get("foo"));
|
Assert.assertEquals("bar", notes.get("foo"));
|
||||||
|
});
|
||||||
|
|
||||||
setTimeOffset(70);
|
setTimeOffset(70);
|
||||||
|
|
||||||
|
inComittedTransaction(session -> {
|
||||||
|
SingleUseObjectProvider singleUseObjectProvider = session.getProvider(SingleUseObjectProvider.class);
|
||||||
|
Map<String, String> notes = singleUseObjectProvider.get(key.serializeKey());
|
||||||
notes = singleUseObjectProvider.get(key.serializeKey());
|
notes = singleUseObjectProvider.get(key.serializeKey());
|
||||||
Assert.assertNull(notes);
|
Assert.assertNull(notes);
|
||||||
});
|
});
|
||||||
|
@ -155,9 +159,12 @@ public class SingleUseObjectModelTest extends KeycloakModelTest {
|
||||||
SingleUseObjectProvider singleUseStore = session.getProvider(SingleUseObjectProvider.class);
|
SingleUseObjectProvider singleUseStore = session.getProvider(SingleUseObjectProvider.class);
|
||||||
Map<String, String> actualNotes = singleUseStore.get(key);
|
Map<String, String> actualNotes = singleUseStore.get(key);
|
||||||
assertThat(actualNotes, Matchers.anEmptyMap());
|
assertThat(actualNotes, Matchers.anEmptyMap());
|
||||||
|
});
|
||||||
|
|
||||||
setTimeOffset(70);
|
setTimeOffset(70);
|
||||||
|
|
||||||
|
inComittedTransaction(session -> {
|
||||||
|
SingleUseObjectProvider singleUseStore = session.getProvider(SingleUseObjectProvider.class);
|
||||||
Assert.assertNull(singleUseStore.get(key));
|
Assert.assertNull(singleUseStore.get(key));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue