diff --git a/dependencies/server-all/pom.xml b/dependencies/server-all/pom.xml
index 16df7422ed..f216f51ce0 100755
--- a/dependencies/server-all/pom.xml
+++ b/dependencies/server-all/pom.xml
@@ -40,6 +40,10 @@
org.keycloak
keycloak-model-jpa
+
+ org.keycloak
+ keycloak-model-map
+
org.keycloak
keycloak-model-infinispan
diff --git a/model/map/pom.xml b/model/map/pom.xml
new file mode 100644
index 0000000000..d5b8ed8902
--- /dev/null
+++ b/model/map/pom.xml
@@ -0,0 +1,42 @@
+
+
+
+ keycloak-model-pom
+ org.keycloak
+ 11.0.0-SNAPSHOT
+
+ 4.0.0
+
+ keycloak-model-map
+ Keycloak Model Naive Map
+
+
+
+
+ org.bouncycastle
+ bcprov-jdk15on
+
+
+ org.keycloak
+ keycloak-core
+
+
+ org.keycloak
+ keycloak-server-spi
+
+
+ org.keycloak
+ keycloak-server-spi-private
+
+
+ org.keycloak
+ keycloak-services
+
+
+ junit
+ junit
+ test
+
+
+
+
\ No newline at end of file
diff --git a/model/map/src/main/java/org/keycloak/models/map/storage/MapKeycloakTransaction.java b/model/map/src/main/java/org/keycloak/models/map/storage/MapKeycloakTransaction.java
new file mode 100644
index 0000000000..7b24d3dd06
--- /dev/null
+++ b/model/map/src/main/java/org/keycloak/models/map/storage/MapKeycloakTransaction.java
@@ -0,0 +1,241 @@
+/*
+ * Copyright 2020 Red Hat, Inc. and/or its affiliates
+ * and other contributors as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.keycloak.models.map.storage;
+
+import org.keycloak.models.KeycloakTransaction;
+
+import java.util.Collection;
+import java.util.LinkedHashMap;
+import java.util.Map;
+import java.util.Objects;
+import java.util.function.Function;
+import java.util.function.Predicate;
+import java.util.stream.Stream;
+import org.jboss.logging.Logger;
+
+public class MapKeycloakTransaction implements KeycloakTransaction {
+
+ private final static Logger log = Logger.getLogger(MapKeycloakTransaction.class);
+
+ private enum MapOperation {
+ PUT {
+ @Override
+ protected MapTaskWithValue taskFor(K key, V value) {
+ return new MapTaskWithValue(value) {
+ @Override
+ public void execute(MapStorage map) {
+ map.put(key, getValue());
+ }
+ };
+ }
+ },
+ PUT_IF_ABSENT {
+ @Override
+ protected MapTaskWithValue taskFor(K key, V value) {
+ return new MapTaskWithValue(value) {
+ @Override
+ public void execute(MapStorage map) {
+ map.putIfAbsent(key, getValue());
+ }
+ };
+ }
+ },
+ REMOVE {
+ @Override
+ protected MapTaskWithValue taskFor(K key, V value) {
+ return new MapTaskWithValue(null) {
+ @Override
+ public void execute(MapStorage map) {
+ map.remove(key);
+ }
+ };
+ }
+ },
+ REPLACE {
+ @Override
+ protected MapTaskWithValue taskFor(K key, V value) {
+ return new MapTaskWithValue(value) {
+ @Override
+ public void execute(MapStorage map) {
+ map.replace(key, getValue());
+ }
+ };
+ }
+ },
+ ;
+
+ protected abstract MapTaskWithValue taskFor(K key, V value);
+
+ }
+
+ private boolean active;
+ private boolean rollback;
+ private final Map> tasks = new LinkedHashMap<>();
+ private final MapStorage map;
+
+ public MapKeycloakTransaction(MapStorage map) {
+ this.map = map;
+ }
+
+ @Override
+ public void begin() {
+ active = true;
+ }
+
+ @Override
+ public void commit() {
+ if (rollback) {
+ throw new RuntimeException("Rollback only!");
+ }
+
+ for (MapTaskWithValue value : tasks.values()) {
+ value.execute(map);
+ }
+ }
+
+ @Override
+ public void rollback() {
+ tasks.clear();
+ }
+
+ @Override
+ public void setRollbackOnly() {
+ rollback = true;
+ }
+
+ @Override
+ public boolean getRollbackOnly() {
+ return rollback;
+ }
+
+ @Override
+ public boolean isActive() {
+ return active;
+ }
+
+ /**
+ * Adds a given task if not exists for the given key
+ */
+ private void addTask(MapOperation op, K key, V value) {
+ log.tracev("Adding operation {0} for {1}", op, key);
+
+ K taskKey = key;
+ tasks.merge(taskKey, op.taskFor(key, value), MapTaskCompose::new);
+ }
+
+ // This is for possibility to lookup for session by id, which was created in this transaction
+ public V get(K key, Function defaultValueFunc) {
+ MapTaskWithValue current = tasks.get(key);
+ if (current != null) {
+ return current.getValue();
+ }
+
+ return defaultValueFunc.apply(key);
+ }
+
+ public V getUpdated(Map.Entry keyDefaultValue) {
+ MapTaskWithValue current = tasks.get(keyDefaultValue.getKey());
+ if (current != null) {
+ return current.getValue();
+ }
+
+ return keyDefaultValue.getValue();
+ }
+
+ public void put(K key, V value) {
+ addTask(MapOperation.PUT, key, value);
+ }
+
+ public void putIfAbsent(K key, V value) {
+ addTask(MapOperation.PUT_IF_ABSENT, key, value);
+ }
+
+ public void putIfChanged(K key, V value, Predicate shouldPut) {
+ log.tracev("Adding operation PUT_IF_CHANGED for {0}", key);
+
+ K taskKey = key;
+ MapTaskWithValue op = new MapTaskWithValue(value) {
+ @Override
+ public void execute(MapStorage map) {
+ if (shouldPut.test(getValue())) {
+ map.put(key, getValue());
+ }
+ }
+ };
+ tasks.merge(taskKey, op, MapTaskCompose::new);
+ }
+
+ public void replace(K key, V value) {
+ addTask(MapOperation.REPLACE, key, value);
+ }
+
+ public void remove(K key) {
+ addTask(MapOperation.REMOVE, key, null);
+ }
+
+ public Stream valuesStream() {
+ return this.tasks.values().stream()
+ .map(MapTaskWithValue::getValue)
+ .filter(Objects::nonNull);
+ }
+
+ public Stream createdValuesStream(Collection existingKeys) {
+ return this.tasks.entrySet().stream()
+ .filter(me -> ! existingKeys.contains(me.getKey()))
+ .map(Map.Entry::getValue)
+ .map(MapTaskWithValue::getValue)
+ .filter(Objects::nonNull);
+ }
+
+ private static abstract class MapTaskWithValue {
+ protected final V value;
+
+ public MapTaskWithValue(V value) {
+ this.value = value;
+ }
+
+ public V getValue() {
+ return value;
+ }
+
+ public abstract void execute(MapStorage map);
+ }
+
+ private static class MapTaskCompose extends MapTaskWithValue {
+
+ private final MapTaskWithValue oldValue;
+ private final MapTaskWithValue newValue;
+
+ public MapTaskCompose(MapTaskWithValue oldValue, MapTaskWithValue newValue) {
+ super(null);
+ this.oldValue = oldValue;
+ this.newValue = newValue;
+ }
+
+ @Override
+ public void execute(MapStorage map) {
+ oldValue.execute(map);
+ newValue.execute(map);
+ }
+
+ @Override
+ public V getValue() {
+ return newValue.getValue();
+ }
+
+ }
+}
\ No newline at end of file
diff --git a/model/map/src/main/java/org/keycloak/models/map/storage/MapStorage.java b/model/map/src/main/java/org/keycloak/models/map/storage/MapStorage.java
new file mode 100644
index 0000000000..f39adc1143
--- /dev/null
+++ b/model/map/src/main/java/org/keycloak/models/map/storage/MapStorage.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright 2020 Red Hat, Inc. and/or its affiliates
+ * and other contributors as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.keycloak.models.map.storage;
+
+import java.util.Collection;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ *
+ * @author hmlnarik
+ */
+public interface MapStorage {
+
+ V get(K key);
+
+ V put(K key, V value);
+
+ V putIfAbsent(K key, V value);
+
+ V remove(K key);
+
+ V replace(K key, V value);
+
+ Set keySet();
+
+ Set> entrySet();
+
+ Collection values();
+
+}
diff --git a/model/pom.xml b/model/pom.xml
index 131cbcd942..0721a5a4f0 100755
--- a/model/pom.xml
+++ b/model/pom.xml
@@ -33,5 +33,6 @@
jpa
infinispan
+ map
diff --git a/pom.xml b/pom.xml
index abb888702c..48647ea2c4 100755
--- a/pom.xml
+++ b/pom.xml
@@ -1134,6 +1134,11 @@
keycloak-model-jpa
${project.version}
+
+ org.keycloak
+ keycloak-model-map
+ ${project.version}
+
org.keycloak
keycloak-model-infinispan