KEYCLOAK-14551 Map transaction
This commit is contained in:
parent
c5d5423cd3
commit
2c29c58af1
6 changed files with 338 additions and 0 deletions
4
dependencies/server-all/pom.xml
vendored
4
dependencies/server-all/pom.xml
vendored
|
@ -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
42
model/map/pom.xml
Normal 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>
|
|
@ -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();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
|
@ -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();
|
||||||
|
|
||||||
|
}
|
|
@ -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>
|
||||||
|
|
5
pom.xml
5
pom.xml
|
@ -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>
|
||||||
|
|
Loading…
Reference in a new issue