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>
|
||||
<artifactId>keycloak-model-jpa</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<artifactId>keycloak-model-map</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<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>
|
||||
<module>jpa</module>
|
||||
<module>infinispan</module>
|
||||
<module>map</module>
|
||||
</modules>
|
||||
</project>
|
||||
|
|
5
pom.xml
5
pom.xml
|
@ -1134,6 +1134,11 @@
|
|||
<artifactId>keycloak-model-jpa</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<artifactId>keycloak-model-map</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<artifactId>keycloak-model-infinispan</artifactId>
|
||||
|
|
Loading…
Reference in a new issue