KEYCLOAK-14551 Map transaction

This commit is contained in:
Hynek Mlnarik 2020-06-22 22:17:49 +02:00 committed by Hynek Mlnařík
parent c5d5423cd3
commit 2c29c58af1
6 changed files with 338 additions and 0 deletions

View file

@ -40,6 +40,10 @@
<groupId>org.keycloak</groupId> <groupId>org.keycloak</groupId>
<artifactId>keycloak-model-jpa</artifactId> <artifactId>keycloak-model-jpa</artifactId>
</dependency> </dependency>
<dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-model-map</artifactId>
</dependency>
<dependency> <dependency>
<groupId>org.keycloak</groupId> <groupId>org.keycloak</groupId>
<artifactId>keycloak-model-infinispan</artifactId> <artifactId>keycloak-model-infinispan</artifactId>

42
model/map/pom.xml Normal file
View file

@ -0,0 +1,42 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>keycloak-model-pom</artifactId>
<groupId>org.keycloak</groupId>
<version>11.0.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>keycloak-model-map</artifactId>
<name>Keycloak Model Naive Map</name>
<description/>
<dependencies>
<dependency>
<groupId>org.bouncycastle</groupId>
<artifactId>bcprov-jdk15on</artifactId>
</dependency>
<dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-core</artifactId>
</dependency>
<dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-server-spi</artifactId>
</dependency>
<dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-server-spi-private</artifactId>
</dependency>
<dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-services</artifactId>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
</project>

View file

@ -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<K, V> implements KeycloakTransaction {
private final static Logger log = Logger.getLogger(MapKeycloakTransaction.class);
private enum MapOperation {
PUT {
@Override
protected <K, V> MapTaskWithValue<K, V> taskFor(K key, V value) {
return new MapTaskWithValue<K, V>(value) {
@Override
public void execute(MapStorage<K, V> map) {
map.put(key, getValue());
}
};
}
},
PUT_IF_ABSENT {
@Override
protected <K, V> MapTaskWithValue<K, V> taskFor(K key, V value) {
return new MapTaskWithValue<K, V>(value) {
@Override
public void execute(MapStorage<K, V> map) {
map.putIfAbsent(key, getValue());
}
};
}
},
REMOVE {
@Override
protected <K, V> MapTaskWithValue<K, V> taskFor(K key, V value) {
return new MapTaskWithValue<K, V>(null) {
@Override
public void execute(MapStorage<K, V> map) {
map.remove(key);
}
};
}
},
REPLACE {
@Override
protected <K, V> MapTaskWithValue<K, V> taskFor(K key, V value) {
return new MapTaskWithValue<K, V>(value) {
@Override
public void execute(MapStorage<K, V> map) {
map.replace(key, getValue());
}
};
}
},
;
protected abstract <K, V> MapTaskWithValue<K, V> taskFor(K key, V value);
}
private boolean active;
private boolean rollback;
private final Map<K, MapTaskWithValue<K, V>> tasks = new LinkedHashMap<>();
private final MapStorage<K, V> map;
public MapKeycloakTransaction(MapStorage<K, V> map) {
this.map = map;
}
@Override
public void begin() {
active = true;
}
@Override
public void commit() {
if (rollback) {
throw new RuntimeException("Rollback only!");
}
for (MapTaskWithValue<K, V> 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<K, V> defaultValueFunc) {
MapTaskWithValue<K, V> current = tasks.get(key);
if (current != null) {
return current.getValue();
}
return defaultValueFunc.apply(key);
}
public V getUpdated(Map.Entry<K, V> keyDefaultValue) {
MapTaskWithValue<K, V> 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<V> shouldPut) {
log.tracev("Adding operation PUT_IF_CHANGED for {0}", key);
K taskKey = key;
MapTaskWithValue<K, V> op = new MapTaskWithValue<K, V>(value) {
@Override
public void execute(MapStorage<K, V> 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<V> valuesStream() {
return this.tasks.values().stream()
.map(MapTaskWithValue<K,V>::getValue)
.filter(Objects::nonNull);
}
public Stream<V> createdValuesStream(Collection<K> existingKeys) {
return this.tasks.entrySet().stream()
.filter(me -> ! existingKeys.contains(me.getKey()))
.map(Map.Entry::getValue)
.map(MapTaskWithValue<K,V>::getValue)
.filter(Objects::nonNull);
}
private static abstract class MapTaskWithValue<K, V> {
protected final V value;
public MapTaskWithValue(V value) {
this.value = value;
}
public V getValue() {
return value;
}
public abstract void execute(MapStorage<K,V> map);
}
private static class MapTaskCompose<K, V> extends MapTaskWithValue<K, V> {
private final MapTaskWithValue<K, V> oldValue;
private final MapTaskWithValue<K, V> newValue;
public MapTaskCompose(MapTaskWithValue<K, V> oldValue, MapTaskWithValue<K, V> newValue) {
super(null);
this.oldValue = oldValue;
this.newValue = newValue;
}
@Override
public void execute(MapStorage<K, V> map) {
oldValue.execute(map);
newValue.execute(map);
}
@Override
public V getValue() {
return newValue.getValue();
}
}
}

View file

@ -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<K, V> {
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<K> keySet();
Set<Map.Entry<K,V>> entrySet();
Collection<V> values();
}

View file

@ -33,5 +33,6 @@
<modules> <modules>
<module>jpa</module> <module>jpa</module>
<module>infinispan</module> <module>infinispan</module>
<module>map</module>
</modules> </modules>
</project> </project>

View file

@ -1134,6 +1134,11 @@
<artifactId>keycloak-model-jpa</artifactId> <artifactId>keycloak-model-jpa</artifactId>
<version>${project.version}</version> <version>${project.version}</version>
</dependency> </dependency>
<dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-model-map</artifactId>
<version>${project.version}</version>
</dependency>
<dependency> <dependency>
<groupId>org.keycloak</groupId> <groupId>org.keycloak</groupId>
<artifactId>keycloak-model-infinispan</artifactId> <artifactId>keycloak-model-infinispan</artifactId>