diff --git a/model/map-hot-rod/src/main/java/org/keycloak/models/map/storage/hotRod/HotRodMapStorageProviderFactory.java b/model/map-hot-rod/src/main/java/org/keycloak/models/map/storage/hotRod/HotRodMapStorageProviderFactory.java
index 67e7359fb7..b886b5c803 100644
--- a/model/map-hot-rod/src/main/java/org/keycloak/models/map/storage/hotRod/HotRodMapStorageProviderFactory.java
+++ b/model/map-hot-rod/src/main/java/org/keycloak/models/map/storage/hotRod/HotRodMapStorageProviderFactory.java
@@ -26,6 +26,7 @@ import org.keycloak.authorization.model.ResourceServer;
import org.keycloak.authorization.model.Scope;
import org.keycloak.common.Profile;
import org.keycloak.component.AmphibianProviderFactory;
+import org.keycloak.models.ActionTokenValueModel;
import org.keycloak.events.Event;
import org.keycloak.events.admin.AdminEvent;
import org.keycloak.models.AuthenticatedClientSessionModel;
@@ -66,6 +67,7 @@ import org.keycloak.models.map.realm.entity.MapRequiredActionProviderEntity;
import org.keycloak.models.map.realm.entity.MapRequiredCredentialEntity;
import org.keycloak.models.map.realm.entity.MapWebAuthnPolicyEntity;
import org.keycloak.models.map.role.MapRoleEntity;
+import org.keycloak.models.map.singleUseObject.MapSingleUseObjectEntity;
import org.keycloak.models.map.storage.hotRod.authSession.HotRodAuthenticationSessionEntityDelegate;
import org.keycloak.models.map.storage.hotRod.authSession.HotRodRootAuthenticationSessionEntity;
import org.keycloak.models.map.storage.hotRod.authSession.HotRodRootAuthenticationSessionEntityDelegate;
@@ -116,6 +118,8 @@ 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.HotRodRequiredCredentialEntityDelegate;
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.user.HotRodUserConsentEntityDelegate;
import org.keycloak.models.map.storage.hotRod.user.HotRodUserCredentialEntityDelegate;
import org.keycloak.models.map.storage.hotRod.user.HotRodUserEntity;
@@ -159,6 +163,8 @@ public class HotRodMapStorageProviderFactory implements AmphibianProviderFactory
.constructor(MapRoleEntity.class, HotRodRoleEntityDelegate::new)
+ .constructor(MapSingleUseObjectEntity.class, HotRodSingleUseObjectEntityDelegate::new)
+
.constructor(MapUserEntity.class, HotRodUserEntityDelegate::new)
.constructor(MapUserCredentialEntity.class, HotRodUserCredentialEntityDelegate::new)
.constructor(MapUserFederatedIdentityEntity.class, HotRodUserFederatedIdentityEntityDelegate::new)
@@ -242,6 +248,12 @@ public class HotRodMapStorageProviderFactory implements AmphibianProviderFactory
HotRodRealmEntity.class,
HotRodRealmEntityDelegate::new));
+ // single-use object storage descriptor
+ ENTITY_DESCRIPTOR_MAP.put(ActionTokenValueModel.class,
+ new HotRodEntityDescriptor<>(ActionTokenValueModel.class,
+ HotRodSingleUseObjectEntity.class,
+ HotRodSingleUseObjectEntityDelegate::new));
+
// User sessions descriptor
ENTITY_DESCRIPTOR_MAP.put(UserSessionModel.class,
new HotRodEntityDescriptor<>(UserSessionModel.class,
@@ -339,6 +351,8 @@ public class HotRodMapStorageProviderFactory implements AmphibianProviderFactory
HotRodMapStorage clientSessionStore = getHotRodStorage(session, AuthenticatedClientSessionModel.class);
return new HotRodUserSessionMapStorage(clientSessionStore, connectionProvider.getRemoteCache(entityDescriptor.getCacheName()), StringKeyConverter.StringKey.INSTANCE, entityDescriptor, CLONER);
+ } else if (modelType == ActionTokenValueModel.class) {
+ return new SingleUseObjectHotRodMapStorage(connectionProvider.getRemoteCache(entityDescriptor.getCacheName()), StringKeyConverter.StringKey.INSTANCE, entityDescriptor, CLONER);
}
return new HotRodMapStorage<>(connectionProvider.getRemoteCache(entityDescriptor.getCacheName()), StringKeyConverter.StringKey.INSTANCE, entityDescriptor, CLONER);
}
diff --git a/model/map-hot-rod/src/main/java/org/keycloak/models/map/storage/hotRod/SingleUseObjectHotRodMapStorage.java b/model/map-hot-rod/src/main/java/org/keycloak/models/map/storage/hotRod/SingleUseObjectHotRodMapStorage.java
new file mode 100644
index 0000000000..6dba4a1eca
--- /dev/null
+++ b/model/map-hot-rod/src/main/java/org/keycloak/models/map/storage/hotRod/SingleUseObjectHotRodMapStorage.java
@@ -0,0 +1,109 @@
+/*
+ * 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;
+
+import org.infinispan.client.hotrod.RemoteCache;
+import org.keycloak.models.ActionTokenValueModel;
+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.StringKeyConverter;
+import org.keycloak.models.map.storage.chm.MapModelCriteriaBuilder;
+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.chm.MapFieldPredicates;
+import org.keycloak.models.map.storage.chm.SingleUseObjectModelCriteriaBuilder;
+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.singleUseObject.HotRodSingleUseObjectEntity;
+import org.keycloak.models.map.storage.hotRod.singleUseObject.HotRodSingleUseObjectEntityDelegate;
+import org.keycloak.storage.SearchableModelField;
+
+import java.util.Map;
+import java.util.stream.Stream;
+
+
+/**
+ * @author Martin Kanis
+ */
+public class SingleUseObjectHotRodMapStorage & AbstractEntity, M>
+ extends HotRodMapStorage {
+
+ private final StringKeyConverter keyConverter;
+ private final HotRodEntityDescriptor storedEntityDescriptor;
+ private final DeepCloner cloner;
+
+ public SingleUseObjectHotRodMapStorage(RemoteCache remoteCache, StringKeyConverter keyConverter,
+ HotRodEntityDescriptor storedEntityDescriptor,
+ DeepCloner cloner) {
+ super(remoteCache, keyConverter, storedEntityDescriptor, cloner);
+ this.keyConverter = keyConverter;
+ this.storedEntityDescriptor = storedEntityDescriptor;
+ this.cloner = cloner;
+ }
+
+ @Override
+ public MapKeycloakTransaction createTransaction(KeycloakSession session) {
+ MapKeycloakTransaction transaction = session.getAttribute("map-transaction-" + hashCode(), MapKeycloakTransaction.class);
+
+ if (transaction == null) {
+ Map, MapModelCriteriaBuilder.UpdatePredicatesFunc> fieldPredicates =
+ MapFieldPredicates.getPredicates((Class) storedEntityDescriptor.getModelTypeClass());
+ transaction = new SingleUseObjectKeycloakTransaction(this, keyConverter, cloner, fieldPredicates);
+ session.setAttribute("map-transaction-" + hashCode(), transaction);
+ }
+
+ return transaction;
+ }
+
+ @Override
+ public HotRodSingleUseObjectEntityDelegate create(HotRodSingleUseObjectEntityDelegate value) {
+ if (value.getId() == null) {
+ if (value.getUserId() != null && value.getActionId() != null && value.getActionVerificationNonce() != null) {
+ value.setId(value.getUserId() + ":" + value.getActionId() + ":" + value.getActionVerificationNonce());
+ }
+ }
+ return super.create(value);
+ }
+
+ @Override
+ public Stream read(QueryParameters queryParameters) {
+ DefaultModelCriteria criteria = queryParameters.getModelCriteriaBuilder();
+
+ if (criteria == null) {
+ return Stream.empty();
+ }
+
+ SingleUseObjectModelCriteriaBuilder mcb = criteria.flashToModelCriteriaBuilder(createSingleUseObjectCriteriaBuilder());
+ if (mcb.isValid()) {
+ HotRodSingleUseObjectEntityDelegate value = read(mcb.getKey());
+
+ return value != null && value.getHotRodEntity() != null ? Stream.of(value) : Stream.empty();
+ }
+
+ return super.read(queryParameters);
+ }
+
+ private SingleUseObjectModelCriteriaBuilder createSingleUseObjectCriteriaBuilder() {
+ return new SingleUseObjectModelCriteriaBuilder();
+ }
+
+}
diff --git a/model/map-hot-rod/src/main/java/org/keycloak/models/map/storage/hotRod/common/ProtoSchemaInitializer.java b/model/map-hot-rod/src/main/java/org/keycloak/models/map/storage/hotRod/common/ProtoSchemaInitializer.java
index 7da80cf3a0..abe8c90931 100644
--- a/model/map-hot-rod/src/main/java/org/keycloak/models/map/storage/hotRod/common/ProtoSchemaInitializer.java
+++ b/model/map-hot-rod/src/main/java/org/keycloak/models/map/storage/hotRod/common/ProtoSchemaInitializer.java
@@ -54,6 +54,7 @@ import org.keycloak.models.map.storage.hotRod.realm.entity.HotRodRequiredCredent
import org.keycloak.models.map.storage.hotRod.realm.entity.HotRodRequirement;
import org.keycloak.models.map.storage.hotRod.realm.entity.HotRodWebAuthnPolicyEntity;
import org.keycloak.models.map.storage.hotRod.role.HotRodRoleEntity;
+import org.keycloak.models.map.storage.hotRod.singleUseObject.HotRodSingleUseObjectEntity;
import org.keycloak.models.map.storage.hotRod.user.HotRodUserConsentEntity;
import org.keycloak.models.map.storage.hotRod.user.HotRodUserCredentialEntity;
import org.keycloak.models.map.storage.hotRod.user.HotRodUserEntity;
@@ -110,6 +111,9 @@ import org.keycloak.models.map.storage.hotRod.userSession.HotRodUserSessionEntit
HotRodWebAuthnPolicyEntity.class,
HotRodRealmEntity.class,
+ // single-use objects
+ HotRodSingleUseObjectEntity.class,
+
// User sessions
HotRodUserSessionEntity.class,
HotRodSessionState.class,
diff --git a/model/map-hot-rod/src/main/java/org/keycloak/models/map/storage/hotRod/singleUseObject/HotRodSingleUseObjectEntity.java b/model/map-hot-rod/src/main/java/org/keycloak/models/map/storage/hotRod/singleUseObject/HotRodSingleUseObjectEntity.java
new file mode 100644
index 0000000000..1b2c1938fc
--- /dev/null
+++ b/model/map-hot-rod/src/main/java/org/keycloak/models/map/storage/hotRod/singleUseObject/HotRodSingleUseObjectEntity.java
@@ -0,0 +1,85 @@
+/*
+ * 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.singleUseObject;
+
+import org.infinispan.protostream.annotations.ProtoDoc;
+import org.infinispan.protostream.annotations.ProtoField;
+import org.keycloak.models.map.annotations.GenerateHotRodEntityImplementation;
+import org.keycloak.models.map.singleUseObject.MapSingleUseObjectEntity;
+import org.keycloak.models.map.storage.hotRod.common.AbstractHotRodEntity;
+import org.keycloak.models.map.storage.hotRod.common.HotRodPair;
+import org.keycloak.models.map.storage.hotRod.common.UpdatableHotRodEntityDelegateImpl;
+
+import java.util.Set;
+
+@GenerateHotRodEntityImplementation(
+ implementInterface = "org.keycloak.models.map.singleUseObject.MapSingleUseObjectEntity",
+ inherits = "org.keycloak.models.map.storage.hotRod.singleUseObject.HotRodSingleUseObjectEntity.AbstractHotRodSingleUseObjectEntityDelegate"
+)
+@ProtoDoc("@Indexed")
+public class HotRodSingleUseObjectEntity extends AbstractHotRodEntity {
+
+ @ProtoDoc("@Field(index = Index.YES, store = Store.YES)")
+ @ProtoField(number = 1)
+ public Integer entityVersion = 1;
+
+ @ProtoField(number = 2)
+ public String id;
+
+ @ProtoField(number = 3)
+ public String userId;
+
+ @ProtoField(number = 4)
+ public String actionId;
+
+ @ProtoField(number = 5)
+ public String actionVerificationNonce;
+
+ @ProtoField(number = 6)
+ public Long expiration;
+
+ @ProtoField(number = 7)
+ public Set> notes;
+
+ public static abstract class AbstractHotRodSingleUseObjectEntityDelegate extends UpdatableHotRodEntityDelegateImpl implements MapSingleUseObjectEntity {
+
+ @Override
+ public String getId() {
+ HotRodSingleUseObjectEntity hotRodEntity = getHotRodEntity();
+ return hotRodEntity != null ? hotRodEntity.id : null;
+ }
+
+ @Override
+ public void setId(String id) {
+ HotRodSingleUseObjectEntity entity = getHotRodEntity();
+ if (entity.id != null) throw new IllegalStateException("Id cannot be changed");
+ entity.id = id;
+ entity.updated |= id != null;
+ }
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ return HotRodSingleUseObjectEntityDelegate.entityEquals(this, o);
+ }
+
+ @Override
+ public int hashCode() {
+ return HotRodSingleUseObjectEntityDelegate.entityHashCode(this);
+ }
+}
diff --git a/model/map-hot-rod/src/main/resources/config/cacheConfig.xml b/model/map-hot-rod/src/main/resources/config/cacheConfig.xml
index b77ea94787..51807208ba 100644
--- a/model/map-hot-rod/src/main/resources/config/cacheConfig.xml
+++ b/model/map-hot-rod/src/main/resources/config/cacheConfig.xml
@@ -116,5 +116,13 @@
+
+
+
+ kc.HotRodSingleUseObjectEntity
+
+
+
+
diff --git a/model/map-hot-rod/src/main/resources/config/infinispan.xml b/model/map-hot-rod/src/main/resources/config/infinispan.xml
index 02c48c787a..ab351a748e 100644
--- a/model/map-hot-rod/src/main/resources/config/infinispan.xml
+++ b/model/map-hot-rod/src/main/resources/config/infinispan.xml
@@ -118,5 +118,13 @@
+
+
+
+ kc.HotRodSingleUseObjectEntity
+
+
+
+
diff --git a/model/map/src/main/java/org/keycloak/models/map/storage/chm/SingleUseObjectConcurrentHashMapStorage.java b/model/map/src/main/java/org/keycloak/models/map/storage/chm/SingleUseObjectConcurrentHashMapStorage.java
index b218b700eb..275d37fcc7 100644
--- a/model/map/src/main/java/org/keycloak/models/map/storage/chm/SingleUseObjectConcurrentHashMapStorage.java
+++ b/model/map/src/main/java/org/keycloak/models/map/storage/chm/SingleUseObjectConcurrentHashMapStorage.java
@@ -26,9 +26,7 @@ import org.keycloak.models.map.singleUseObject.MapSingleUseObjectEntity;
import org.keycloak.models.map.storage.MapKeycloakTransaction;
import org.keycloak.models.map.storage.QueryParameters;
import org.keycloak.models.map.storage.criteria.DefaultModelCriteria;
-import org.keycloak.storage.SearchableModelField;
-import java.util.Map;
import java.util.stream.Stream;
/**
@@ -44,7 +42,13 @@ public class SingleUseObjectConcurrentHashMapStorage createTransaction(KeycloakSession session) {
MapKeycloakTransaction actionTokenTransaction = session.getAttribute("map-transaction-" + hashCode(), MapKeycloakTransaction.class);
- return actionTokenTransaction == null ? new SingleUseObjectConcurrentHashMapStorage.Transaction(getKeyConverter(), cloner, fieldPredicates) : actionTokenTransaction;
+
+ if (actionTokenTransaction == null) {
+ actionTokenTransaction = new SingleUseObjectKeycloakTransaction(this, keyConverter, cloner, fieldPredicates);
+ session.setAttribute("map-transaction-" + hashCode(), actionTokenTransaction);
+ }
+
+ return actionTokenTransaction;
}
@Override
@@ -78,21 +82,4 @@ public class SingleUseObjectConcurrentHashMapStorage {
-
- public Transaction(StringKeyConverter keyConverter, DeepCloner cloner,
- Map, MapModelCriteriaBuilder.UpdatePredicatesFunc> fieldPredicates) {
- super(SingleUseObjectConcurrentHashMapStorage.this, keyConverter, cloner, fieldPredicates);
- }
-
- @Override
- public MapSingleUseObjectEntity create(MapSingleUseObjectEntity value) {
- if (value.getId() == null) {
- if (value.getUserId() != null && value.getActionId() != null && value.getActionVerificationNonce() != null) {
- value.setId(value.getUserId() + ":" + value.getActionId() + ":" + value.getActionVerificationNonce());
- }
- }
- return super.create(value);
- }
- }
}
diff --git a/model/map/src/main/java/org/keycloak/models/map/storage/chm/SingleUseObjectKeycloakTransaction.java b/model/map/src/main/java/org/keycloak/models/map/storage/chm/SingleUseObjectKeycloakTransaction.java
new file mode 100644
index 0000000000..c811a1a760
--- /dev/null
+++ b/model/map/src/main/java/org/keycloak/models/map/storage/chm/SingleUseObjectKeycloakTransaction.java
@@ -0,0 +1,50 @@
+/*
+ * 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.chm;
+
+import org.keycloak.models.ActionTokenValueModel;
+import org.keycloak.models.map.common.DeepCloner;
+import org.keycloak.models.map.common.StringKeyConverter;
+import org.keycloak.models.map.singleUseObject.MapSingleUseObjectEntity;
+import org.keycloak.storage.SearchableModelField;
+
+import java.util.Map;
+
+/**
+ * @author Martin Kanis
+ */
+public class SingleUseObjectKeycloakTransaction extends ConcurrentHashMapKeycloakTransaction {
+
+ public SingleUseObjectKeycloakTransaction(ConcurrentHashMapCrudOperations map,
+ StringKeyConverter keyConverter,
+ DeepCloner cloner,
+ Map,
+ MapModelCriteriaBuilder.UpdatePredicatesFunc> fieldPredicates) {
+ super(map, keyConverter, cloner, fieldPredicates);
+ }
+
+ @Override
+ public MapSingleUseObjectEntity create(MapSingleUseObjectEntity value) {
+ if (value.getId() == null) {
+ if (value.getUserId() != null && value.getActionId() != null && value.getActionVerificationNonce() != null) {
+ value.setId(value.getUserId() + ":" + value.getActionId() + ":" + value.getActionVerificationNonce());
+ }
+ }
+ return super.create(value);
+ }
+}
diff --git a/testsuite/integration-arquillian/tests/pom.xml b/testsuite/integration-arquillian/tests/pom.xml
index 88ffc42f4d..f6c6417688 100755
--- a/testsuite/integration-arquillian/tests/pom.xml
+++ b/testsuite/integration-arquillian/tests/pom.xml
@@ -1507,6 +1507,8 @@
hotrod
hotrod
hotrod
+ hotrod
+ hotrod
diff --git a/testsuite/model/src/main/resources/hotrod/infinispan.xml b/testsuite/model/src/main/resources/hotrod/infinispan.xml
index 39203384d2..966f772a4d 100644
--- a/testsuite/model/src/main/resources/hotrod/infinispan.xml
+++ b/testsuite/model/src/main/resources/hotrod/infinispan.xml
@@ -114,5 +114,13 @@
+
+
+
+ kc.HotRodSingleUseObjectEntity
+
+
+
+
diff --git a/testsuite/model/src/test/java/org/keycloak/testsuite/model/parameters/HotRodMapStorage.java b/testsuite/model/src/test/java/org/keycloak/testsuite/model/parameters/HotRodMapStorage.java
index fbf6b9fac3..044d83de33 100644
--- a/testsuite/model/src/test/java/org/keycloak/testsuite/model/parameters/HotRodMapStorage.java
+++ b/testsuite/model/src/test/java/org/keycloak/testsuite/model/parameters/HotRodMapStorage.java
@@ -76,8 +76,8 @@ public class HotRodMapStorage extends KeycloakModelParameters {
@Override
public void updateConfig(Config cf) {
cf.spi(AuthenticationSessionSpi.PROVIDER_ID).provider(MapRootAuthenticationSessionProviderFactory.PROVIDER_ID).config(STORAGE_CONFIG, HotRodMapStorageProviderFactory.PROVIDER_ID)
- .spi(ActionTokenStoreSpi.NAME).provider(MapSingleUseObjectProviderFactory.PROVIDER_ID).config(STORAGE_CONFIG, ConcurrentHashMapStorageProviderFactory.PROVIDER_ID)
- .spi(SingleUseObjectSpi.NAME).provider(MapSingleUseObjectProviderFactory.PROVIDER_ID).config(STORAGE_CONFIG, ConcurrentHashMapStorageProviderFactory.PROVIDER_ID)
+ .spi(ActionTokenStoreSpi.NAME).provider(MapSingleUseObjectProviderFactory.PROVIDER_ID).config(STORAGE_CONFIG, HotRodMapStorageProviderFactory.PROVIDER_ID)
+ .spi(SingleUseObjectSpi.NAME).provider(MapSingleUseObjectProviderFactory.PROVIDER_ID).config(STORAGE_CONFIG, HotRodMapStorageProviderFactory.PROVIDER_ID)
.spi("client").provider(MapClientProviderFactory.PROVIDER_ID).config(STORAGE_CONFIG, HotRodMapStorageProviderFactory.PROVIDER_ID)
.spi("clientScope").provider(MapClientScopeProviderFactory.PROVIDER_ID).config(STORAGE_CONFIG, HotRodMapStorageProviderFactory.PROVIDER_ID)
.spi("group").provider(MapGroupProviderFactory.PROVIDER_ID).config(STORAGE_CONFIG, HotRodMapStorageProviderFactory.PROVIDER_ID)