From e46a5484c5888ecd1f467d3ba5a5a6cbba961a4a Mon Sep 17 00:00:00 2001 From: Hynek Mlnarik Date: Tue, 9 Feb 2021 12:23:32 +0100 Subject: [PATCH] KEYCLOAK-17695 Split MapStorage provider and provider factory --- ...tAuthenticationSessionProviderFactory.java | 3 +- .../MapAuthorizationStoreFactory.java | 4 +- .../map/client/MapClientProviderFactory.java | 3 +- .../map/group/MapGroupProviderFactory.java | 3 +- .../map/realm/MapRealmProviderFactory.java | 3 +- .../map/role/MapRoleProviderFactory.java | 3 +- .../map/storage/MapStorageProvider.java | 9 +- .../storage/MapStorageProviderFactory.java | 42 +++++ .../models/map/storage/MapStorageSpi.java | 2 +- .../chm/ConcurrentHashMapStorageProvider.java | 122 +------------- ...ncurrentHashMapStorageProviderFactory.java | 157 ++++++++++++++++++ .../map/user/MapUserProviderFactory.java | 3 +- .../MapUserSessionProviderFactory.java | 3 +- ...els.map.storage.MapStorageProviderFactory} | 2 +- .../sessions/AuthenticationSessionSpi.java | 4 +- .../parameters/ConcurrentHashMapStorage.java | 3 +- .../testsuite/model/parameters/Map.java | 8 +- 17 files changed, 234 insertions(+), 140 deletions(-) create mode 100644 model/map/src/main/java/org/keycloak/models/map/storage/MapStorageProviderFactory.java create mode 100644 model/map/src/main/java/org/keycloak/models/map/storage/chm/ConcurrentHashMapStorageProviderFactory.java rename model/map/src/main/resources/META-INF/services/{org.keycloak.models.map.storage.MapStorageProvider => org.keycloak.models.map.storage.MapStorageProviderFactory} (98%) diff --git a/model/map/src/main/java/org/keycloak/models/map/authSession/MapRootAuthenticationSessionProviderFactory.java b/model/map/src/main/java/org/keycloak/models/map/authSession/MapRootAuthenticationSessionProviderFactory.java index 26fe7e9f04..d7fc635c99 100644 --- a/model/map/src/main/java/org/keycloak/models/map/authSession/MapRootAuthenticationSessionProviderFactory.java +++ b/model/map/src/main/java/org/keycloak/models/map/authSession/MapRootAuthenticationSessionProviderFactory.java @@ -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); } diff --git a/model/map/src/main/java/org/keycloak/models/map/authorization/MapAuthorizationStoreFactory.java b/model/map/src/main/java/org/keycloak/models/map/authorization/MapAuthorizationStoreFactory.java index 30fe92d2a3..64fce0bf1a 100644 --- a/model/map/src/main/java/org/keycloak/models/map/authorization/MapAuthorizationStoreFactory.java +++ b/model/map/src/main/java/org/keycloak/models/map/authorization/MapAuthorizationStoreFactory.java @@ -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); diff --git a/model/map/src/main/java/org/keycloak/models/map/client/MapClientProviderFactory.java b/model/map/src/main/java/org/keycloak/models/map/client/MapClientProviderFactory.java index 24afd9bcb1..bde0364fdc 100644 --- a/model/map/src/main/java/org/keycloak/models/map/client/MapClientProviderFactory.java +++ b/model/map/src/main/java/org/keycloak/models/map/client/MapClientProviderFactory.java @@ -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 implements RealmProviderFactory { @@ -32,7 +33,7 @@ public class MapRealmProviderFactory extends AbstractMapProviderFactory implements RoleProviderFactory { @@ -32,7 +33,7 @@ public class MapRoleProviderFactory extends AbstractMapProviderFactory { +public interface MapStorageProvider extends Provider { - public enum Flag { - INITIALIZE_EMPTY, - LOCAL - } - /** * Returns a key-value storage * @param type of the primary key diff --git a/model/map/src/main/java/org/keycloak/models/map/storage/MapStorageProviderFactory.java b/model/map/src/main/java/org/keycloak/models/map/storage/MapStorageProviderFactory.java new file mode 100644 index 0000000000..797ad6e42e --- /dev/null +++ b/model/map/src/main/java/org/keycloak/models/map/storage/MapStorageProviderFactory.java @@ -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 { + + public enum Flag { + INITIALIZE_EMPTY, + LOCAL + } + + /** + * Returns a key-value storage + * @param type of the primary key + * @param type of the value + * @param name Name of the storage + * @param flags + * @return + */ + , M> MapStorage getStorage(String name, Class keyType, Class valueType, Class modelType, Flag... flags); +} diff --git a/model/map/src/main/java/org/keycloak/models/map/storage/MapStorageSpi.java b/model/map/src/main/java/org/keycloak/models/map/storage/MapStorageSpi.java index bf122bd094..685026716d 100644 --- a/model/map/src/main/java/org/keycloak/models/map/storage/MapStorageSpi.java +++ b/model/map/src/main/java/org/keycloak/models/map/storage/MapStorageSpi.java @@ -45,7 +45,7 @@ public class MapStorageSpi implements Spi { @Override public Class getProviderFactoryClass() { - return MapStorageProvider.class; + return MapStorageProviderFactory.class; } } diff --git a/model/map/src/main/java/org/keycloak/models/map/storage/chm/ConcurrentHashMapStorageProvider.java b/model/map/src/main/java/org/keycloak/models/map/storage/chm/ConcurrentHashMapStorageProvider.java index 07193f329a..90a6e3cd15 100644 --- a/model/map/src/main/java/org/keycloak/models/map/storage/chm/ConcurrentHashMapStorageProvider.java +++ b/model/map/src/main/java/org/keycloak/models/map/storage/chm/ConcurrentHashMapStorageProvider.java @@ -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> 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 , M> ConcurrentHashMapStorage loadMap(String fileName, - Class valueType, Class modelType, EnumSet flags) { - ConcurrentHashMapStorage 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 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 , M> ConcurrentHashMapStorage getStorage(String name, Class keyType, Class valueType, Class modelType, Flag... flags) { - EnumSet f = flags == null || flags.length == 0 ? EnumSet.noneOf(Flag.class) : EnumSet.of(flags[0], flags); - return (ConcurrentHashMapStorage) 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"); - } - } diff --git a/model/map/src/main/java/org/keycloak/models/map/storage/chm/ConcurrentHashMapStorageProviderFactory.java b/model/map/src/main/java/org/keycloak/models/map/storage/chm/ConcurrentHashMapStorageProviderFactory.java new file mode 100644 index 0000000000..e34dc39f09 --- /dev/null +++ b/model/map/src/main/java/org/keycloak/models/map/storage/chm/ConcurrentHashMapStorageProviderFactory.java @@ -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> 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 , M> ConcurrentHashMapStorage loadMap(String fileName, + Class valueType, Class modelType, EnumSet flags) { + ConcurrentHashMapStorage 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 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 , M> ConcurrentHashMapStorage getStorage(String name, + Class keyType, Class valueType, Class modelType, Flag... flags) { + EnumSet f = flags == null || flags.length == 0 ? EnumSet.noneOf(Flag.class) : EnumSet.of(flags[0], flags); + return (ConcurrentHashMapStorage) storages.computeIfAbsent(name, n -> loadMap(name, valueType, modelType, f)); + } + + private File getFile(String fileName) { + return storageDirectory == null + ? null + : new File(storageDirectory, "map-" + fileName + ".json"); + } + +} diff --git a/model/map/src/main/java/org/keycloak/models/map/user/MapUserProviderFactory.java b/model/map/src/main/java/org/keycloak/models/map/user/MapUserProviderFactory.java index d61b5e9959..fba1703131 100644 --- a/model/map/src/main/java/org/keycloak/models/map/user/MapUserProviderFactory.java +++ b/model/map/src/main/java/org/keycloak/models/map/user/MapUserProviderFactory.java @@ -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> ALLOWED_FACTORIES = ImmutableSet.>builder() - .add(ConcurrentHashMapStorageProvider.class) + .add(ConcurrentHashMapStorageProviderFactory.class) .build(); @Override diff --git a/testsuite/model/src/test/java/org/keycloak/testsuite/model/parameters/Map.java b/testsuite/model/src/test/java/org/keycloak/testsuite/model/parameters/Map.java index 94a3bcc1d5..5307794a97 100644 --- a/testsuite/model/src/test/java/org/keycloak/testsuite/model/parameters/Map.java +++ b/testsuite/model/src/test/java/org/keycloak/testsuite/model/parameters/Map.java @@ -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)