KEYCLOAK-17695 Split MapStorage provider and provider factory

This commit is contained in:
Hynek Mlnarik 2021-02-09 12:23:32 +01:00 committed by Hynek Mlnařík
parent 020dd530b9
commit e46a5484c5
17 changed files with 234 additions and 140 deletions

View file

@ -21,6 +21,7 @@ import org.keycloak.models.KeycloakSessionFactory;
import org.keycloak.models.map.common.AbstractMapProviderFactory;
import org.keycloak.models.map.storage.MapStorage;
import org.keycloak.models.map.storage.MapStorageProvider;
import org.keycloak.models.map.storage.MapStorageProviderFactory;
import org.keycloak.sessions.AuthenticationSessionProvider;
import org.keycloak.sessions.AuthenticationSessionProviderFactory;
@ -37,7 +38,7 @@ public class MapRootAuthenticationSessionProviderFactory extends AbstractMapProv
@Override
public void postInit(KeycloakSessionFactory factory) {
MapStorageProvider sp = (MapStorageProvider) factory.getProviderFactory(MapStorageProvider.class);
MapStorageProviderFactory sp = (MapStorageProviderFactory) factory.getProviderFactory(MapStorageProvider.class);
this.store = sp.getStorage("sessions", UUID.class, MapRootAuthenticationSessionEntity.class, RootAuthenticationSessionModel.class);
}

View file

@ -35,7 +35,7 @@ import org.keycloak.models.map.authorization.entity.MapResourceServerEntity;
import org.keycloak.models.map.authorization.entity.MapScopeEntity;
import org.keycloak.models.map.storage.MapStorage;
import org.keycloak.models.map.storage.MapStorageProvider;
import org.keycloak.models.map.storage.MapStorageProviderFactory;
import java.util.UUID;
/**
@ -71,7 +71,7 @@ public class MapAuthorizationStoreFactory implements AuthorizationStoreFactory {
public void postInit(KeycloakSessionFactory factory) {
AuthorizationStoreFactory.super.postInit(factory);
MapStorageProvider mapStorageProvider = (MapStorageProvider) factory.getProviderFactory(MapStorageProvider.class);
MapStorageProviderFactory mapStorageProvider = (MapStorageProviderFactory) factory.getProviderFactory(MapStorageProvider.class);
permissionTicketStore = mapStorageProvider.getStorage("authzPermissionTickets", UUID.class, MapPermissionTicketEntity.class, PermissionTicket.class);
policyStore = mapStorageProvider.getStorage("authzPolicies", UUID.class, MapPolicyEntity.class, Policy.class);
resourceServerStore = mapStorageProvider.getStorage("authzResourceServers", String.class, MapResourceServerEntity.class, ResourceServer.class);

View file

@ -33,6 +33,7 @@ import org.keycloak.models.map.storage.MapStorageProvider;
import org.keycloak.models.map.storage.MapStorage;
import org.keycloak.provider.ProviderEvent;
import org.keycloak.provider.ProviderEventListener;
import org.keycloak.models.map.storage.MapStorageProviderFactory;
/**
*
@ -48,7 +49,7 @@ public class MapClientProviderFactory extends AbstractMapProviderFactory<ClientP
@Override
public void postInit(KeycloakSessionFactory factory) {
MapStorageProvider sp = (MapStorageProvider) factory.getProviderFactory(MapStorageProvider.class);
MapStorageProviderFactory sp = (MapStorageProviderFactory) factory.getProviderFactory(MapStorageProvider.class);
this.store = sp.getStorage("clients", UUID.class, MapClientEntity.class, ClientModel.class);
factory.register(this);

View file

@ -33,6 +33,7 @@ import org.keycloak.models.map.storage.MapStorageProvider;
import org.keycloak.provider.ProviderEvent;
import org.keycloak.provider.ProviderEventListener;
import org.keycloak.models.map.storage.MapStorageProviderFactory;
import java.util.UUID;
/**
@ -47,7 +48,7 @@ public class MapGroupProviderFactory extends AbstractMapProviderFactory<GroupPro
@Override
public void postInit(KeycloakSessionFactory factory) {
MapStorageProvider sp = (MapStorageProvider) factory.getProviderFactory(MapStorageProvider.class);
MapStorageProviderFactory sp = (MapStorageProviderFactory) factory.getProviderFactory(MapStorageProvider.class);
this.store = sp.getStorage("groups", UUID.class, MapGroupEntity.class, GroupModel.class);
factory.register(this);

View file

@ -24,6 +24,7 @@ import org.keycloak.models.RealmProvider;
import org.keycloak.models.RealmProviderFactory;
import org.keycloak.models.map.storage.MapStorageProvider;
import org.keycloak.models.map.storage.MapStorage;
import org.keycloak.models.map.storage.MapStorageProviderFactory;
public class MapRealmProviderFactory extends AbstractMapProviderFactory<RealmProvider> implements RealmProviderFactory {
@ -32,7 +33,7 @@ public class MapRealmProviderFactory extends AbstractMapProviderFactory<RealmPro
@Override
public void postInit(KeycloakSessionFactory factory) {
MapStorageProvider sp = (MapStorageProvider) factory.getProviderFactory(MapStorageProvider.class);
MapStorageProviderFactory sp = (MapStorageProviderFactory) factory.getProviderFactory(MapStorageProvider.class);
this.store = sp.getStorage("realm", String.class, MapRealmEntity.class, RealmModel.class);
}

View file

@ -25,6 +25,7 @@ import org.keycloak.models.RoleProvider;
import org.keycloak.models.RoleProviderFactory;
import org.keycloak.models.map.storage.MapStorageProvider;
import org.keycloak.models.map.storage.MapStorage;
import org.keycloak.models.map.storage.MapStorageProviderFactory;
public class MapRoleProviderFactory extends AbstractMapProviderFactory<RoleProvider> implements RoleProviderFactory {
@ -32,7 +33,7 @@ public class MapRoleProviderFactory extends AbstractMapProviderFactory<RoleProvi
@Override
public void postInit(KeycloakSessionFactory factory) {
MapStorageProvider sp = (MapStorageProvider) factory.getProviderFactory(MapStorageProvider.class);
MapStorageProviderFactory sp = (MapStorageProviderFactory) factory.getProviderFactory(MapStorageProvider.class);
this.store = sp.getStorage("roles", UUID.class, MapRoleEntity.class, RoleModel.class);
}

View file

@ -17,19 +17,14 @@
package org.keycloak.models.map.storage;
import org.keycloak.models.map.common.AbstractEntity;
import org.keycloak.models.map.storage.MapStorageProviderFactory.Flag;
import org.keycloak.provider.Provider;
import org.keycloak.provider.ProviderFactory;
/**
*
* @author hmlnarik
*/
public interface MapStorageProvider extends Provider, ProviderFactory<MapStorageProvider> {
public enum Flag {
INITIALIZE_EMPTY,
LOCAL
}
public interface MapStorageProvider extends Provider {
/**
* Returns a key-value storage

View file

@ -0,0 +1,42 @@
/*
* 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.map.common.AbstractEntity;
import org.keycloak.provider.ProviderFactory;
/**
*
* @author hmlnarik
*/
public interface MapStorageProviderFactory extends ProviderFactory<MapStorageProvider> {
public enum Flag {
INITIALIZE_EMPTY,
LOCAL
}
/**
* Returns a key-value storage
* @param <K> type of the primary key
* @param <V> type of the value
* @param name Name of the storage
* @param flags
* @return
*/
<K, V extends AbstractEntity<K>, M> MapStorage<K, V, M> getStorage(String name, Class<K> keyType, Class<V> valueType, Class<M> modelType, Flag... flags);
}

View file

@ -45,7 +45,7 @@ public class MapStorageSpi implements Spi {
@Override
public Class<? extends ProviderFactory> getProviderFactoryClass() {
return MapStorageProvider.class;
return MapStorageProviderFactory.class;
}
}

View file

@ -16,25 +16,9 @@
*/
package org.keycloak.models.map.storage.chm;
import org.keycloak.Config.Scope;
import org.keycloak.models.AuthenticatedClientSessionModel;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.KeycloakSessionFactory;
import org.keycloak.models.UserSessionModel;
import org.keycloak.models.map.common.AbstractEntity;
import org.keycloak.models.map.common.Serialization;
import com.fasterxml.jackson.databind.JavaType;
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.util.EnumSet;
import java.util.List;
import java.util.concurrent.ConcurrentHashMap;
import org.jboss.logging.Logger;
import org.keycloak.models.map.storage.MapStorageProvider;
import org.keycloak.models.map.storage.ModelCriteriaBuilder;
import org.keycloak.models.map.userSession.MapAuthenticatedClientSessionEntity;
import java.util.UUID;
import org.keycloak.models.map.storage.MapStorageProviderFactory.Flag;
/**
*
@ -42,115 +26,19 @@ import java.util.UUID;
*/
public class ConcurrentHashMapStorageProvider implements MapStorageProvider {
public static final String PROVIDER_ID = "concurrenthashmap";
private final ConcurrentHashMapStorageProviderFactory factory;
private static final Logger LOG = Logger.getLogger(ConcurrentHashMapStorageProvider.class);
private final ConcurrentHashMap<String, ConcurrentHashMapStorage<?,?,?>> storages = new ConcurrentHashMap<>();
private File storageDirectory;
@Override
public MapStorageProvider create(KeycloakSession session) {
return this;
}
@Override
public void init(Scope config) {
final String dir = config.get("dir");
if (dir == null || dir.trim().isEmpty()) {
LOG.warn("No directory set, created objects will not survive server restart");
this.storageDirectory = null;
} else {
File f = new File(dir);
try {
Files.createDirectories(f.toPath());
if (f.exists()) {
this.storageDirectory = f;
} else {
LOG.warnf("Directory cannot be used, created objects will not survive server restart: %s", dir);
this.storageDirectory = null;
}
} catch (IOException ex) {
LOG.warnf("Directory cannot be used, created objects will not survive server restart: %s", dir);
this.storageDirectory = null;
}
}
}
@Override
public void postInit(KeycloakSessionFactory factory) {
public ConcurrentHashMapStorageProvider(ConcurrentHashMapStorageProviderFactory factory) {
this.factory = factory;
}
@Override
public void close() {
storages.forEach(this::storeMap);
}
private void storeMap(String fileName, ConcurrentHashMapStorage<?, ?, ?> store) {
if (fileName != null) {
File f = getFile(fileName);
try {
if (storageDirectory != null && storageDirectory.exists()) {
LOG.debugf("Storing contents to %s", f.getCanonicalPath());
@SuppressWarnings("unchecked")
final ModelCriteriaBuilder readAllCriteria = store.createCriteriaBuilder();
Serialization.MAPPER.writeValue(f, store.read(readAllCriteria));
} else {
LOG.debugf("Not storing contents of %s because directory %s does not exist", fileName, this.storageDirectory);
}
} catch (IOException ex) {
throw new RuntimeException(ex);
}
}
}
private <K, V extends AbstractEntity<K>, M> ConcurrentHashMapStorage<K, V, M> loadMap(String fileName,
Class<V> valueType, Class<M> modelType, EnumSet<Flag> flags) {
ConcurrentHashMapStorage<K, V, M> store;
if (modelType == UserSessionModel.class) {
ConcurrentHashMapStorage clientSessionStore =
getStorage("clientSessions", UUID.class, MapAuthenticatedClientSessionEntity.class, AuthenticatedClientSessionModel.class);
store = new UserSessionConcurrentHashMapStorage<>(clientSessionStore);
} else {
store = new ConcurrentHashMapStorage<>(modelType);
}
if (! flags.contains(Flag.INITIALIZE_EMPTY)) {
final File f = getFile(fileName);
if (f != null && f.exists()) {
try {
LOG.debugf("Restoring contents from %s", f.getCanonicalPath());
JavaType type = Serialization.MAPPER.getTypeFactory().constructCollectionType(List.class, valueType);
List<V> values = Serialization.MAPPER.readValue(f, type);
values.forEach((V mce) -> store.create(mce.getId(), mce));
} catch (IOException ex) {
throw new RuntimeException(ex);
}
}
}
return store;
}
@Override
public String getId() {
return PROVIDER_ID;
}
@Override
@SuppressWarnings("unchecked")
public <K, V extends AbstractEntity<K>, M> ConcurrentHashMapStorage<K, V, M> getStorage(String name,
Class<K> keyType, Class<V> valueType, Class<M> modelType, Flag... flags) {
EnumSet<Flag> f = flags == null || flags.length == 0 ? EnumSet.noneOf(Flag.class) : EnumSet.of(flags[0], flags);
return (ConcurrentHashMapStorage<K, V, M>) storages.computeIfAbsent(name, n -> loadMap(name, valueType, modelType, f));
return factory.getStorage(name, keyType, valueType, modelType, flags);
}
private File getFile(String fileName) {
return storageDirectory == null
? null
: new File(storageDirectory, "map-" + fileName + ".json");
}
}

View file

@ -0,0 +1,157 @@
/*
* 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.chm;
import org.keycloak.Config.Scope;
import org.keycloak.models.AuthenticatedClientSessionModel;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.KeycloakSessionFactory;
import org.keycloak.models.UserSessionModel;
import org.keycloak.models.map.common.AbstractEntity;
import org.keycloak.models.map.common.Serialization;
import com.fasterxml.jackson.databind.JavaType;
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.util.EnumSet;
import java.util.List;
import java.util.concurrent.ConcurrentHashMap;
import org.jboss.logging.Logger;
import org.keycloak.models.map.storage.MapStorageProvider;
import org.keycloak.models.map.storage.MapStorageProviderFactory;
import org.keycloak.models.map.storage.ModelCriteriaBuilder;
import org.keycloak.models.map.userSession.MapAuthenticatedClientSessionEntity;
import java.util.UUID;
/**
*
* @author hmlnarik
*/
public class ConcurrentHashMapStorageProviderFactory implements MapStorageProviderFactory {
public static final String PROVIDER_ID = "concurrenthashmap";
private static final Logger LOG = Logger.getLogger(ConcurrentHashMapStorageProviderFactory.class);
private final ConcurrentHashMap<String, ConcurrentHashMapStorage<?,?,?>> storages = new ConcurrentHashMap<>();
private File storageDirectory;
@Override
public MapStorageProvider create(KeycloakSession session) {
return new ConcurrentHashMapStorageProvider(this);
}
@Override
public void init(Scope config) {
final String dir = config.get("dir");
if (dir == null || dir.trim().isEmpty()) {
LOG.warn("No directory set, created objects will not survive server restart");
this.storageDirectory = null;
} else {
File f = new File(dir);
try {
Files.createDirectories(f.toPath());
if (f.exists()) {
this.storageDirectory = f;
} else {
LOG.warnf("Directory cannot be used, created objects will not survive server restart: %s", dir);
this.storageDirectory = null;
}
} catch (IOException ex) {
LOG.warnf("Directory cannot be used, created objects will not survive server restart: %s", dir);
this.storageDirectory = null;
}
}
}
@Override
public void postInit(KeycloakSessionFactory factory) {
}
@Override
public void close() {
storages.forEach(this::storeMap);
}
private void storeMap(String fileName, ConcurrentHashMapStorage<?, ?, ?> store) {
if (fileName != null) {
File f = getFile(fileName);
try {
if (storageDirectory != null && storageDirectory.exists()) {
LOG.debugf("Storing contents to %s", f.getCanonicalPath());
@SuppressWarnings("unchecked")
final ModelCriteriaBuilder readAllCriteria = store.createCriteriaBuilder();
Serialization.MAPPER.writeValue(f, store.read(readAllCriteria));
} else {
LOG.debugf("Not storing contents of %s because directory %s does not exist", fileName, this.storageDirectory);
}
} catch (IOException ex) {
throw new RuntimeException(ex);
}
}
}
private <K, V extends AbstractEntity<K>, M> ConcurrentHashMapStorage<K, V, M> loadMap(String fileName,
Class<V> valueType, Class<M> modelType, EnumSet<Flag> flags) {
ConcurrentHashMapStorage<K, V, M> store;
if (modelType == UserSessionModel.class) {
ConcurrentHashMapStorage clientSessionStore =
getStorage("clientSessions", UUID.class, MapAuthenticatedClientSessionEntity.class, AuthenticatedClientSessionModel.class);
store = new UserSessionConcurrentHashMapStorage<>(clientSessionStore);
} else {
store = new ConcurrentHashMapStorage<>(modelType);
}
if (! flags.contains(Flag.INITIALIZE_EMPTY)) {
final File f = getFile(fileName);
if (f != null && f.exists()) {
try {
LOG.debugf("Restoring contents from %s", f.getCanonicalPath());
JavaType type = Serialization.MAPPER.getTypeFactory().constructCollectionType(List.class, valueType);
List<V> values = Serialization.MAPPER.readValue(f, type);
values.forEach((V mce) -> store.create(mce.getId(), mce));
} catch (IOException ex) {
throw new RuntimeException(ex);
}
}
}
return store;
}
@Override
public String getId() {
return PROVIDER_ID;
}
@SuppressWarnings("unchecked")
@Override
public <K, V extends AbstractEntity<K>, M> ConcurrentHashMapStorage<K, V, M> getStorage(String name,
Class<K> keyType, Class<V> valueType, Class<M> modelType, Flag... flags) {
EnumSet<Flag> f = flags == null || flags.length == 0 ? EnumSet.noneOf(Flag.class) : EnumSet.of(flags[0], flags);
return (ConcurrentHashMapStorage<K, V, M>) storages.computeIfAbsent(name, n -> loadMap(name, valueType, modelType, f));
}
private File getFile(String fileName) {
return storageDirectory == null
? null
: new File(storageDirectory, "map-" + fileName + ".json");
}
}

View file

@ -26,6 +26,7 @@ import org.keycloak.models.map.common.AbstractMapProviderFactory;
import org.keycloak.models.map.storage.MapStorage;
import org.keycloak.models.map.storage.MapStorageProvider;
import org.keycloak.models.map.storage.MapStorageProviderFactory;
import java.util.UUID;
/**
@ -38,7 +39,7 @@ public class MapUserProviderFactory extends AbstractMapProviderFactory<UserProvi
@Override
public void postInit(KeycloakSessionFactory factory) {
MapStorageProvider sp = (MapStorageProvider) factory.getProviderFactory(MapStorageProvider.class);
MapStorageProviderFactory sp = (MapStorageProviderFactory) factory.getProviderFactory(MapStorageProvider.class);
this.store = sp.getStorage("users", UUID.class, MapUserEntity.class, UserModel.class);
}

View file

@ -26,6 +26,7 @@ import org.keycloak.models.UserSessionProviderFactory;
import org.keycloak.models.map.common.AbstractMapProviderFactory;
import org.keycloak.models.map.storage.MapStorage;
import org.keycloak.models.map.storage.MapStorageProvider;
import org.keycloak.models.map.storage.MapStorageProviderFactory;
import java.util.UUID;
@ -40,7 +41,7 @@ public class MapUserSessionProviderFactory extends AbstractMapProviderFactory<Us
@Override
public void postInit(KeycloakSessionFactory factory) {
MapStorageProvider sp = (MapStorageProvider) factory.getProviderFactory(MapStorageProvider.class);
MapStorageProviderFactory sp = (MapStorageProviderFactory) factory.getProviderFactory(MapStorageProvider.class);
userSessionStore = sp.getStorage("userSessions", UUID.class, MapUserSessionEntity.class, UserSessionModel.class);
clientSessionStore = sp.getStorage("clientSessions", UUID.class, MapAuthenticatedClientSessionEntity.class, AuthenticatedClientSessionModel.class);

View file

@ -15,4 +15,4 @@
# limitations under the License.
#
org.keycloak.models.map.storage.chm.ConcurrentHashMapStorageProvider
org.keycloak.models.map.storage.chm.ConcurrentHashMapStorageProviderFactory

View file

@ -26,6 +26,8 @@ import org.keycloak.provider.Spi;
*/
public class AuthenticationSessionSpi implements Spi {
public static final String PROVIDER_ID = "authenticationSessions";
@Override
public boolean isInternal() {
return true;
@ -33,7 +35,7 @@ public class AuthenticationSessionSpi implements Spi {
@Override
public String getName() {
return "authenticationSessions";
return PROVIDER_ID;
}
@Override

View file

@ -19,6 +19,7 @@ package org.keycloak.testsuite.model.parameters;
import org.keycloak.models.map.storage.MapStorageSpi;
import org.keycloak.testsuite.model.KeycloakModelParameters;
import org.keycloak.models.map.storage.chm.ConcurrentHashMapStorageProvider;
import org.keycloak.models.map.storage.chm.ConcurrentHashMapStorageProviderFactory;
import org.keycloak.provider.ProviderFactory;
import org.keycloak.provider.Spi;
import org.keycloak.testsuite.model.Config;
@ -35,7 +36,7 @@ public class ConcurrentHashMapStorage extends KeycloakModelParameters {
.build();
static final Set<Class<? extends ProviderFactory>> ALLOWED_FACTORIES = ImmutableSet.<Class<? extends ProviderFactory>>builder()
.add(ConcurrentHashMapStorageProvider.class)
.add(ConcurrentHashMapStorageProviderFactory.class)
.build();
@Override

View file

@ -26,11 +26,12 @@ import org.keycloak.models.map.clientscope.MapClientScopeProviderFactory;
import org.keycloak.models.map.group.MapGroupProviderFactory;
import org.keycloak.models.map.realm.MapRealmProviderFactory;
import org.keycloak.models.map.role.MapRoleProviderFactory;
import org.keycloak.models.map.storage.MapStorageProvider;
import org.keycloak.models.map.storage.MapStorageProviderFactory;
import org.keycloak.models.map.storage.MapStorageSpi;
import org.keycloak.models.map.user.MapUserProviderFactory;
import org.keycloak.provider.ProviderFactory;
import org.keycloak.provider.Spi;
import org.keycloak.sessions.AuthenticationSessionSpi;
import org.keycloak.testsuite.model.Config;
import com.google.common.collect.ImmutableSet;
import java.util.Set;
@ -53,7 +54,7 @@ public class Map extends KeycloakModelParameters {
.add(MapRealmProviderFactory.class)
.add(MapRoleProviderFactory.class)
.add(MapUserProviderFactory.class)
.add(MapStorageProvider.class)
.add(MapStorageProviderFactory.class)
.add(MapUserSessionProviderFactory.class)
.add(MapUserLoginFailureProviderFactory.class)
.build();
@ -64,7 +65,8 @@ public class Map extends KeycloakModelParameters {
@Override
public void updateConfig(Config cf) {
cf.spi("client").defaultProvider(MapClientProviderFactory.PROVIDER_ID)
cf.spi(AuthenticationSessionSpi.PROVIDER_ID).defaultProvider(MapClientProviderFactory.PROVIDER_ID)
.spi("client").defaultProvider(MapClientProviderFactory.PROVIDER_ID)
.spi("clientScope").defaultProvider(MapClientScopeProviderFactory.PROVIDER_ID)
.spi("group").defaultProvider(MapGroupProviderFactory.PROVIDER_ID)
.spi("realm").defaultProvider(MapRealmProviderFactory.PROVIDER_ID)