fix: combining / removing static and initialization logic (#46918)

closes: #46917

Signed-off-by: Steve Hawkins <shawkins@redhat.com>
Signed-off-by: Steven Hawkins <shawkins@redhat.com>
This commit is contained in:
Steven Hawkins 2026-04-07 12:52:37 -04:00 committed by GitHub
parent a2db1bb43e
commit bb10a2c81c
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
22 changed files with 123 additions and 234 deletions

View file

@ -45,6 +45,7 @@ import java.util.jar.JarEntry;
import java.util.jar.JarFile;
import java.util.logging.Handler;
import jakarta.inject.Singleton;
import jakarta.persistence.Entity;
import jakarta.persistence.PersistenceUnitTransactionType;
@ -93,6 +94,7 @@ import org.keycloak.quarkus.runtime.configuration.PropertyMappingInterceptor;
import org.keycloak.quarkus.runtime.configuration.mappers.PropertyMapper;
import org.keycloak.quarkus.runtime.configuration.mappers.PropertyMappers;
import org.keycloak.quarkus.runtime.configuration.mappers.WildcardPropertyMapper;
import org.keycloak.quarkus.runtime.integration.QuarkusKeycloakSessionFactory;
import org.keycloak.quarkus.runtime.integration.resteasy.KeycloakHandlerChainCustomizer;
import org.keycloak.quarkus.runtime.integration.resteasy.KeycloakTracingCustomizer;
import org.keycloak.quarkus.runtime.logging.ClearMappedDiagnosticContextFilter;
@ -127,6 +129,7 @@ import io.quarkus.agroal.spi.JdbcDataSourceBuildItem;
import io.quarkus.agroal.spi.JdbcDriverBuildItem;
import io.quarkus.arc.deployment.AnnotationsTransformerBuildItem;
import io.quarkus.arc.deployment.BuildTimeConditionBuildItem;
import io.quarkus.arc.deployment.SyntheticBeanBuildItem;
import io.quarkus.bootstrap.logging.InitialConfigurator;
import io.quarkus.datasource.deployment.spi.DevServicesDatasourceResultBuildItem;
import io.quarkus.datasource.runtime.DataSourcesBuildTimeConfig;
@ -681,7 +684,7 @@ class KeycloakProcessor {
@Consume(ConfigBuildItem.class)
@Consume(CryptoProviderInitBuildItem.class)
@Produce(KeycloakSessionFactoryPreInitBuildItem.class)
void configureKeycloakSessionFactory(KeycloakRecorder recorder, List<PersistenceXmlDescriptorBuildItem> descriptors) {
SyntheticBeanBuildItem configureKeycloakSessionFactory(KeycloakRecorder recorder, List<PersistenceXmlDescriptorBuildItem> descriptors) {
Map<Spi, Map<Class<? extends Provider>, Map<String, Class<? extends ProviderFactory>>>> factories = new HashMap<>();
Map<Class<? extends Provider>, String> defaultProviders = new HashMap<>();
Map<String, ProviderFactory> preConfiguredProviders = new HashMap<>();
@ -709,7 +712,10 @@ class KeycloakProcessor {
}
}
recorder.configSessionFactory(factories, defaultProviders, preConfiguredProviders, loadThemesFromClassPath());
return SyntheticBeanBuildItem.configure(QuarkusKeycloakSessionFactory.class).scope(Singleton.class)
.unremovable()
.runtimeValue(recorder.createSessionFactory(factories, defaultProviders, preConfiguredProviders,
loadThemesFromClassPath())).done();
}
private List<ClasspathThemeProviderFactory.ThemesRepresentation> loadThemesFromClassPath() {

View file

@ -33,6 +33,7 @@ import org.keycloak.quarkus.runtime.cli.command.DryRunMixin;
import org.keycloak.quarkus.runtime.configuration.Configuration;
import org.keycloak.quarkus.runtime.configuration.PersistedConfigSource;
import org.keycloak.quarkus.runtime.configuration.mappers.PropertyMappers;
import org.keycloak.quarkus.runtime.integration.QuarkusKeycloakSessionFactory;
import org.keycloak.quarkus.runtime.integration.jaxrs.QuarkusKeycloakApplication;
import io.quarkus.arc.Arc;
@ -146,7 +147,8 @@ public class KeycloakMain implements QuarkusApplication {
public int run(String... args) throws Exception {
if (COMMAND != null) {
QuarkusKeycloakApplication application = Arc.container().instance(QuarkusKeycloakApplication.class).get();
COMMAND.onStart(application);
QuarkusKeycloakSessionFactory sessionFactory = Arc.container().instance(QuarkusKeycloakSessionFactory.class).get();
COMMAND.onStart(application, sessionFactory);
}
if (isTestLaunchMode() || isNonServerMode()) {
// in test mode we exit immediately

View file

@ -58,6 +58,7 @@ import io.quarkus.agroal.DataSource;
import io.quarkus.arc.Arc;
import io.quarkus.arc.InstanceHandle;
import io.quarkus.hibernate.orm.runtime.integration.HibernateOrmIntegrationRuntimeInitListener;
import io.quarkus.runtime.RuntimeValue;
import io.quarkus.runtime.annotations.Recorder;
import io.vertx.core.Handler;
import io.vertx.ext.web.RoutingContext;
@ -156,12 +157,12 @@ public class KeycloakRecorder {
}
}
public void configSessionFactory(
public RuntimeValue<QuarkusKeycloakSessionFactory> createSessionFactory(
Map<Spi, Map<Class<? extends Provider>, Map<String, Class<? extends ProviderFactory>>>> factories,
Map<Class<? extends Provider>, String> defaultProviders,
Map<String, ProviderFactory> preConfiguredProviders,
List<ClasspathThemeProviderFactory.ThemesRepresentation> themes) {
QuarkusKeycloakSessionFactory.setInstance(new QuarkusKeycloakSessionFactory(factories, defaultProviders, preConfiguredProviders, themes));
return new RuntimeValue<QuarkusKeycloakSessionFactory>(new QuarkusKeycloakSessionFactory(factories, defaultProviders, preConfiguredProviders, themes));
}
public void setDefaultUserProfileConfiguration(UPConfig configuration) {

View file

@ -21,6 +21,7 @@ import java.util.EnumSet;
import org.keycloak.common.util.Environment;
import org.keycloak.config.OptionCategory;
import org.keycloak.quarkus.runtime.integration.QuarkusKeycloakSessionFactory;
import org.keycloak.quarkus.runtime.integration.jaxrs.QuarkusKeycloakApplication;
import picocli.CommandLine;
@ -44,7 +45,7 @@ public abstract class AbstractNonServerCommand extends AbstractAutoBuildCommand
return super.isHiddenCategory(category) || hidden.contains(category);
}
public void onStart(QuarkusKeycloakApplication application) {
public void onStart(QuarkusKeycloakApplication application, QuarkusKeycloakSessionFactory sessionFactory) {
}
@Override

View file

@ -20,11 +20,10 @@ package org.keycloak.quarkus.runtime.cli.command;
import org.keycloak.common.util.IoUtils;
import org.keycloak.config.BootstrapAdminOptions;
import org.keycloak.config.OptionCategory;
import org.keycloak.models.KeycloakSessionFactory;
import org.keycloak.models.utils.KeycloakModelUtils;
import org.keycloak.quarkus.runtime.cli.PropertyException;
import org.keycloak.quarkus.runtime.integration.QuarkusKeycloakSessionFactory;
import org.keycloak.quarkus.runtime.integration.jaxrs.QuarkusKeycloakApplication;
import org.keycloak.services.resources.KeycloakApplication;
import picocli.CommandLine.ArgGroup;
import picocli.CommandLine.Command;
@ -99,9 +98,8 @@ public class BootstrapAdminService extends AbstractNonServerCommand {
}
@Override
public void onStart(QuarkusKeycloakApplication application) {
public void onStart(QuarkusKeycloakApplication application, QuarkusKeycloakSessionFactory sessionFactory) {
//BootstrapAdmin bootstrap = spec.commandLine().getParent().getCommand();
KeycloakSessionFactory sessionFactory = KeycloakApplication.getSessionFactory();
KeycloakModelUtils.runJobInTransaction(sessionFactory, session -> application
.createTemporaryMasterRealmAdminService(clientId, clientSecret, /* bootstrap.expiration, */ session));
}

View file

@ -20,11 +20,10 @@ package org.keycloak.quarkus.runtime.cli.command;
import org.keycloak.common.util.IoUtils;
import org.keycloak.config.BootstrapAdminOptions;
import org.keycloak.config.OptionCategory;
import org.keycloak.models.KeycloakSessionFactory;
import org.keycloak.models.utils.KeycloakModelUtils;
import org.keycloak.quarkus.runtime.cli.PropertyException;
import org.keycloak.quarkus.runtime.integration.QuarkusKeycloakSessionFactory;
import org.keycloak.quarkus.runtime.integration.jaxrs.QuarkusKeycloakApplication;
import org.keycloak.services.resources.KeycloakApplication;
import picocli.CommandLine.ArgGroup;
import picocli.CommandLine.Command;
@ -99,9 +98,8 @@ public class BootstrapAdminUser extends AbstractNonServerCommand {
}
@Override
public void onStart(QuarkusKeycloakApplication application) {
public void onStart(QuarkusKeycloakApplication application, QuarkusKeycloakSessionFactory sessionFactory) {
//BootstrapAdmin bootstrap = spec.commandLine().getParent().getCommand();
KeycloakSessionFactory sessionFactory = KeycloakApplication.getSessionFactory();
KeycloakModelUtils.runJobInTransaction(sessionFactory, session -> application
.createTemporaryMasterRealmAdminUser(username, password, /* bootstrap.expiration, */ session));
}

View file

@ -25,36 +25,19 @@ import org.keycloak.Config;
import org.keycloak.models.KeycloakSession;
import org.keycloak.provider.Provider;
import org.keycloak.provider.ProviderFactory;
import org.keycloak.provider.ProviderManagerRegistry;
import org.keycloak.provider.Spi;
import org.keycloak.quarkus.runtime.themes.QuarkusJarThemeProviderFactory;
import org.keycloak.services.DefaultKeycloakSessionFactory;
import org.keycloak.services.resources.admin.fgap.AdminPermissions;
import org.keycloak.theme.ClasspathThemeProviderFactory;
public final class QuarkusKeycloakSessionFactory extends DefaultKeycloakSessionFactory {
public static QuarkusKeycloakSessionFactory getInstance() {
if (INSTANCE == null) {
INSTANCE = new QuarkusKeycloakSessionFactory();
}
return INSTANCE;
}
public static void setInstance(QuarkusKeycloakSessionFactory instance) {
INSTANCE = instance;
}
private static QuarkusKeycloakSessionFactory INSTANCE;
public QuarkusKeycloakSessionFactory(
Map<Spi, Map<Class<? extends Provider>, Map<String, Class<? extends ProviderFactory>>>> factories,
Map<Class<? extends Provider>, String> defaultProviders,
Map<String, ProviderFactory> preConfiguredProviders,
List<ClasspathThemeProviderFactory.ThemesRepresentation> themes) {
this.provider = defaultProviders;
serverStartupTimestamp = System.currentTimeMillis();
spis = factories.keySet();
for (Spi spi : spis) {
@ -79,17 +62,6 @@ public final class QuarkusKeycloakSessionFactory extends DefaultKeycloakSessionF
}
}
private QuarkusKeycloakSessionFactory() {
}
@Override
public void init() {
initProviderFactories();
AdminPermissions.registerListener(this);
// make the session factory ready for hot deployment
ProviderManagerRegistry.SINGLETON.setDeployer(this);
}
private ProviderFactory lookupProviderFactory(Class<? extends ProviderFactory> factoryClazz) {
ProviderFactory factory;

View file

@ -20,8 +20,10 @@ package org.keycloak.quarkus.runtime.integration.cdi;
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.enterprise.context.RequestScoped;
import jakarta.enterprise.inject.Disposes;
import jakarta.inject.Inject;
import org.keycloak.models.KeycloakSession;
import org.keycloak.quarkus.runtime.integration.QuarkusKeycloakSessionFactory;
import org.keycloak.quarkus.runtime.transaction.TransactionalSessionHandler;
import org.keycloak.utils.KeycloakSessionUtil;
@ -31,9 +33,12 @@ import io.quarkus.arc.Unremovable;
@Unremovable
public class KeycloakBeanProducer implements TransactionalSessionHandler {
@Inject
QuarkusKeycloakSessionFactory factory;
@RequestScoped
public KeycloakSession getKeycloakSession() {
return create();
return factory.create();
}
void dispose(@Disposes KeycloakSession session) {

View file

@ -30,11 +30,13 @@ import org.keycloak.quarkus.runtime.configuration.MicroProfileConfigProvider;
import org.keycloak.quarkus.runtime.configuration.PropertyMappingInterceptor;
import org.keycloak.quarkus.runtime.integration.QuarkusKeycloakSessionFactory;
import org.keycloak.quarkus.runtime.storage.database.jpa.QuarkusJpaConnectionProviderFactory;
import org.keycloak.services.DefaultKeycloakSessionFactory;
import org.keycloak.services.ServicesLogger;
import org.keycloak.services.managers.ApplianceBootstrap;
import org.keycloak.services.resources.KeycloakApplication;
import org.keycloak.utils.StringUtil;
import io.quarkus.arc.Arc;
import io.quarkus.runtime.Quarkus;
import io.quarkus.runtime.ShutdownEvent;
import io.quarkus.runtime.StartupEvent;
@ -43,10 +45,11 @@ import org.jboss.logging.Logger;
import static org.keycloak.common.util.Environment.isDevMode;
import static org.keycloak.common.util.Environment.isNonServerMode;
import static org.keycloak.quarkus.runtime.Environment.isTestLaunchMode;
@ApplicationPath("/")
@Blocking
public class QuarkusKeycloakApplication extends KeycloakApplication<QuarkusKeycloakSessionFactory> {
public class QuarkusKeycloakApplication extends KeycloakApplication {
private static final String KEYCLOAK_ADMIN_ENV_VAR = "KEYCLOAK_ADMIN";
private static final String KEYCLOAK_ADMIN_PASSWORD_ENV_VAR = "KEYCLOAK_ADMIN_PASSWORD";
@ -75,13 +78,8 @@ public class QuarkusKeycloakApplication extends KeycloakApplication<QuarkusKeycl
}
@Override
public QuarkusKeycloakSessionFactory createSessionFactory() {
return QuarkusKeycloakSessionFactory.getInstance();
}
@Override
protected void initKeycloakSessionFactory(QuarkusKeycloakSessionFactory quarkusKeycloakSessionFactory) {
quarkusKeycloakSessionFactory.init();
public DefaultKeycloakSessionFactory createSessionFactory() {
return Arc.container().instance(QuarkusKeycloakSessionFactory.class).get();
}
@Override
@ -116,11 +114,11 @@ public class QuarkusKeycloakApplication extends KeycloakApplication<QuarkusKeycl
.map(Boolean::parseBoolean)
.orElse(Boolean.TRUE);
// skip async bootstrap in dev and non-server mode
return !isDevMode() && !isNonServerMode() && asyncBootstrap;
return !isDevMode() && !isNonServerMode() && !isTestLaunchMode() && asyncBootstrap;
}
@Override
protected int getTransactionTimeout(QuarkusKeycloakSessionFactory sessionFactory) {
protected int getTransactionTimeout(DefaultKeycloakSessionFactory sessionFactory) {
return ((QuarkusJpaConnectionProviderFactory) sessionFactory.getProviderFactory(JpaConnectionProvider.class)).getMigrationTransactionTimeout();
}

View file

@ -1,12 +1,13 @@
package org.keycloak.quarkus.runtime.services;
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.inject.Inject;
import jakarta.ws.rs.container.ContainerRequestContext;
import jakarta.ws.rs.core.HttpHeaders;
import jakarta.ws.rs.core.MediaType;
import jakarta.ws.rs.core.Response;
import org.keycloak.services.resources.KeycloakApplication;
import org.keycloak.quarkus.runtime.integration.QuarkusKeycloakSessionFactory;
import org.jboss.logging.Logger;
import org.jboss.resteasy.reactive.server.ServerRequestFilter;
@ -22,6 +23,9 @@ public class BootstrapFilter {
private boolean ready;
private volatile boolean warningLogged;
@Inject
QuarkusKeycloakSessionFactory factory;
public BootstrapFilter() {
startup = System.currentTimeMillis();
}
@ -32,7 +36,7 @@ public class BootstrapFilter {
// JVM branch prediction may optimize this code and saves on reading a static volatile field
return null;
}
if (KeycloakApplication.isBootstrapCompleted()) {
if (factory.isBootstrapCompleted()) {
// Return null to continue the request chain normally
ready = true;
return null;

View file

@ -18,8 +18,9 @@
package org.keycloak.quarkus.runtime.services.health;
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.inject.Inject;
import org.keycloak.services.resources.KeycloakApplication;
import org.keycloak.quarkus.runtime.integration.QuarkusKeycloakSessionFactory;
import io.smallrye.health.api.AsyncHealthCheck;
import io.smallrye.mutiny.Uni;
@ -37,16 +38,19 @@ public class BoostrapReadyHealthCheck implements AsyncHealthCheck {
private static final HealthCheckResponse UP = builder().up().build();
private boolean bootstrapCompleted;
@Inject
QuarkusKeycloakSessionFactory factory;
@Override
public Uni<HealthCheckResponse> call() {
// JVM branch prediction may optimize this code and saves on reading a static volatile field
if (bootstrapCompleted) {
return ready();
}
if (KeycloakApplication.isBootstrapCompleted()) {
if (factory.isBootstrapCompleted()) {
bootstrapCompleted = true;
return ready();
}
}
return Uni.createFrom().item(builder().down().build());
}

View file

@ -19,10 +19,11 @@ package org.keycloak.quarkus.runtime.services.health;
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.enterprise.context.Dependent;
import jakarta.enterprise.inject.Produces;
import jakarta.inject.Inject;
import org.keycloak.connections.infinispan.InfinispanConnectionProvider;
import org.keycloak.connections.infinispan.InfinispanConnectionProviderFactory;
import org.keycloak.services.resources.KeycloakApplication;
import org.keycloak.quarkus.runtime.integration.QuarkusKeycloakSessionFactory;
import io.smallrye.health.api.AsyncHealthCheck;
import org.eclipse.microprofile.health.Readiness;
@ -32,6 +33,8 @@ public class KeycloakClusterReadyHealthCheckProducer {
private AsyncHealthCheck instance;
private boolean ready;
@Inject
QuarkusKeycloakSessionFactory sessionFactory;
@Produces
@Readiness
@ -41,14 +44,13 @@ public class KeycloakClusterReadyHealthCheckProducer {
// JVM branch prediction may optimize this code and saves on reading a static volatile field
return instance;
}
if (!KeycloakApplication.isBootstrapCompleted()) {
if (!sessionFactory.isBootstrapCompleted()) {
return null;
}
synchronized (this) {
if (ready) {
return instance;
}
var sessionFactory = KeycloakApplication.getSessionFactory();
var factory = (InfinispanConnectionProviderFactory) sessionFactory.getProviderFactory(InfinispanConnectionProvider.class);
if (factory.isClusterHealthSupported()) {
instance = new KeycloakClusterReadyHealthCheck(factory);

View file

@ -18,8 +18,6 @@
package org.keycloak.quarkus.runtime.transaction;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.KeycloakSessionFactory;
import org.keycloak.quarkus.runtime.integration.QuarkusKeycloakSessionFactory;
/**
* <p>A {@link TransactionalSessionHandler} is responsible for managing transaction sessions and its lifecycle. Its subtypes
@ -28,16 +26,6 @@ import org.keycloak.quarkus.runtime.integration.QuarkusKeycloakSessionFactory;
*/
public interface TransactionalSessionHandler {
/**
* Creates a {@link KeycloakSession}.
*
* @return a keycloak session
*/
default KeycloakSession create() {
KeycloakSessionFactory sessionFactory = QuarkusKeycloakSessionFactory.getInstance();
return sessionFactory.create();
}
/**
* begin a transaction if possible
*

View file

@ -1,26 +0,0 @@
/*
* Copyright 2016 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.provider;
/**
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
* @version $Revision: 1 $
*/
public interface ProviderManagerDeployer {
void deploy(ProviderManager pm);
void undeploy(ProviderManager pm);
}

View file

@ -1,62 +0,0 @@
/*
* Copyright 2016 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.provider;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
import java.util.concurrent.atomic.AtomicReference;
/**
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
* @version $Revision: 1 $
*/
public class ProviderManagerRegistry {
public static final ProviderManagerRegistry SINGLETON = new ProviderManagerRegistry();
protected List<ProviderManager> preBoot = Collections.synchronizedList(new LinkedList<>());
protected AtomicReference<ProviderManagerDeployer> deployerRef = new AtomicReference<>();
public synchronized void setDeployer(ProviderManagerDeployer deployer) {
this.deployerRef.set(deployer);
}
public synchronized void deploy(ProviderManager pm) {
ProviderManagerDeployer deployer = getDeployer();
if (deployer == null) {
preBoot.add(pm);
} else {
deployer.deploy(pm);
}
}
public synchronized void undeploy(ProviderManager pm) {
preBoot.remove(pm);
ProviderManagerDeployer deployer = getDeployer();
if (deployer != null) {
deployer.undeploy(pm);
}
}
private ProviderManagerDeployer getDeployer() {
return deployerRef.get();
}
public List<ProviderManager> getPreBoot() {
return preBoot;
}
}

View file

@ -44,21 +44,18 @@ import org.keycloak.models.KeycloakSessionFactory;
import org.keycloak.models.ThemeManager;
import org.keycloak.provider.EnvironmentDependentProviderFactory;
import org.keycloak.provider.InvalidationHandler;
import org.keycloak.provider.KeycloakDeploymentInfo;
import org.keycloak.provider.Provider;
import org.keycloak.provider.ProviderEvent;
import org.keycloak.provider.ProviderEventListener;
import org.keycloak.provider.ProviderFactory;
import org.keycloak.provider.ProviderManager;
import org.keycloak.provider.ProviderManagerDeployer;
import org.keycloak.provider.ProviderManagerRegistry;
import org.keycloak.provider.Spi;
import org.keycloak.services.resources.admin.fgap.AdminPermissions;
import org.keycloak.theme.ThemeManagerFactory;
import org.jboss.logging.Logger;
public abstract class DefaultKeycloakSessionFactory implements KeycloakSessionFactory, ProviderManagerDeployer {
public abstract class DefaultKeycloakSessionFactory implements KeycloakSessionFactory {
private static final Logger logger = Logger.getLogger(DefaultKeycloakSessionFactory.class);
@ -68,10 +65,12 @@ public abstract class DefaultKeycloakSessionFactory implements KeycloakSessionFa
protected CopyOnWriteArrayList<ProviderEventListener> listeners = new CopyOnWriteArrayList<>();
// TODO: Likely should be changed to int and use Time.currentTime() to be compatible with all our "time" reps
protected long serverStartupTimestamp;
protected long serverStartupTimestamp = System.currentTimeMillis();
protected ComponentFactoryProviderFactory componentFactoryPF;
private volatile boolean bootstrapCompleted;
@Override
public void register(ProviderEventListener listener) {
listeners.add(listener);
@ -90,34 +89,7 @@ public abstract class DefaultKeycloakSessionFactory implements KeycloakSessionFa
}
public void init() {
serverStartupTimestamp = System.currentTimeMillis();
ProviderManager pm = new ProviderManager(KeycloakDeploymentInfo.create().services(), getClass().getClassLoader(), Config.scope().getArray("providers"));
for (Spi spi : pm.loadSpis()) {
if (spi.isEnabled()) {
spis.add(spi);
}
}
factoriesMap = loadFactories(pm);
synchronized (ProviderManagerRegistry.SINGLETON) {
for (ProviderManager manager : ProviderManagerRegistry.SINGLETON.getPreBoot()) {
Map<Class<? extends Provider>, Map<String, ProviderFactory>> factoryMap = loadFactories(manager);
for (Map.Entry<Class<? extends Provider>, Map<String, ProviderFactory>> entry : factoryMap.entrySet()) {
Map<String, ProviderFactory> factories = factoriesMap.get(entry.getKey());
if (factories == null) {
factoriesMap.put(entry.getKey(), entry.getValue());
} else {
factories.putAll(entry.getValue());
}
}
}
checkProvider();
initProviderFactories();
// make the session factory ready for hot deployment
ProviderManagerRegistry.SINGLETON.setDeployer(this);
}
initProviderFactories();
AdminPermissions.registerListener(this);
}
@ -180,7 +152,6 @@ public abstract class DefaultKeycloakSessionFactory implements KeycloakSessionFa
}
@Override
public void deploy(ProviderManager pm) {
registerNewSpis(pm);
@ -240,7 +211,6 @@ public abstract class DefaultKeycloakSessionFactory implements KeycloakSessionFa
}
}
@Override
public void undeploy(ProviderManager pm) {
logger.debug("undeploy");
// we make a copy to avoid concurrent access exceptions
@ -454,8 +424,6 @@ public abstract class DefaultKeycloakSessionFactory implements KeycloakSessionFa
@Override
public void close() {
ProviderManagerRegistry.SINGLETON.setDeployer(null);
// Create a tree-structure to represent reverse relation of ProviderFactory#dependsOn to Providers
Map<Class<? extends Provider>, Node<Set<ProviderFactory>>> nodes = new HashMap<>();
for (Map.Entry<Class<? extends Provider>, Map<String, ProviderFactory>> f : factoriesMap.entrySet()) {
@ -520,4 +488,12 @@ public abstract class DefaultKeycloakSessionFactory implements KeycloakSessionFa
this.componentFactoryPF = (ComponentFactoryProviderFactory) getProviderFactory(ComponentFactoryProvider.class);
}
public void setBootstrapCompleted() {
this.bootstrapCompleted = true;
}
public boolean isBootstrapCompleted() {
return this.bootstrapCompleted;
}
}

View file

@ -29,11 +29,11 @@ import org.keycloak.common.crypto.CryptoIntegration;
import org.keycloak.exportimport.ExportImportConfig;
import org.keycloak.exportimport.ExportImportManager;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.KeycloakSessionFactory;
import org.keycloak.models.dblock.DBLockManager;
import org.keycloak.models.dblock.DBLockProvider;
import org.keycloak.models.utils.KeycloakModelUtils;
import org.keycloak.models.utils.PostMigrationEvent;
import org.keycloak.services.DefaultKeycloakSessionFactory;
import org.keycloak.services.managers.ApplianceBootstrap;
import org.jboss.logging.Logger;
@ -43,15 +43,13 @@ import org.jboss.logging.Logger;
* @version $Revision: 1 $
*
*/
public abstract class KeycloakApplication<KSF extends KeycloakSessionFactory> extends Application {
public abstract class KeycloakApplication extends Application {
private static final String KC_TMPDIR = "kc.io.tmpdir";
private static final Logger logger = Logger.getLogger(KeycloakApplication.class);
private static volatile KeycloakSessionFactory sessionFactory;
// Set to true when bootstrap is completed. It never changes back to false.
private static volatile boolean bootstrapCompleted = false;
private static volatile DefaultKeycloakSessionFactory sessionFactory;
public KeycloakApplication() {
try {
@ -86,12 +84,11 @@ public abstract class KeycloakApplication<KSF extends KeycloakSessionFactory> ex
protected void startup() {
Profile.getInstance().logUnsupportedFeatures();
CryptoIntegration.init(KeycloakApplication.class.getClassLoader());
var ksf = createSessionFactory();
sessionFactory = ksf;
KeycloakApplication.sessionFactory = createSessionFactory();
if (supportsAsyncInitialization()) {
final var executor = Executors.newSingleThreadExecutor();
CompletableFuture.runAsync(() -> runBootstrap(ksf), executor)
CompletableFuture.runAsync(() -> runBootstrap(KeycloakApplication.sessionFactory), executor)
.exceptionally(throwable -> {
exit(throwable);
return null;
@ -100,18 +97,17 @@ public abstract class KeycloakApplication<KSF extends KeycloakSessionFactory> ex
return;
}
runBootstrap(ksf);
runBootstrap(KeycloakApplication.sessionFactory);
}
protected boolean supportsAsyncInitialization() {
return false;
}
// synchronized to prevent shutdown while running bootstrapping
private synchronized void runBootstrap(KSF keycloakSessionFactory) {
private synchronized void runBootstrap(DefaultKeycloakSessionFactory keycloakSessionFactory) {
var startTime = System.nanoTime();
initKeycloakSessionFactory(keycloakSessionFactory);
keycloakSessionFactory.init();
setTransactionTimeout(keycloakSessionFactory);
var exportImportManager = KeycloakModelUtils.runJobInTransactionWithResult(keycloakSessionFactory, session -> {
DBLockManager dbLockManager = new DBLockManager(session);
@ -131,14 +127,14 @@ public abstract class KeycloakApplication<KSF extends KeycloakSessionFactory> ex
}
resetTransactionTimeout(keycloakSessionFactory);
bootstrapCompleted = true;
keycloakSessionFactory.publish(new PostMigrationEvent(keycloakSessionFactory));
keycloakSessionFactory.setBootstrapCompleted();
var duration = Duration.ofNanos(System.nanoTime() - startTime);
logger.infof("Bootstrap completed in %f seconds", (double) duration.toMillis() / 1000);
}
protected int getTransactionTimeout(KSF sessionFactory) {
protected int getTransactionTimeout(DefaultKeycloakSessionFactory sessionFactory) {
return Math.toIntExact(TimeUnit.MINUTES.toSeconds(5));
}
@ -146,6 +142,7 @@ public abstract class KeycloakApplication<KSF extends KeycloakSessionFactory> ex
protected synchronized void shutdown() {
if (sessionFactory != null) {
sessionFactory.close();
sessionFactory = null;
}
}
@ -178,19 +175,13 @@ public abstract class KeycloakApplication<KSF extends KeycloakSessionFactory> ex
protected abstract void initAndStart();
protected abstract KSF createSessionFactory();
protected abstract DefaultKeycloakSessionFactory createSessionFactory();
protected abstract void initKeycloakSessionFactory(KSF ksf);
public static KeycloakSessionFactory getSessionFactory() {
public static DefaultKeycloakSessionFactory getSessionFactory() {
return sessionFactory;
}
public static boolean isBootstrapCompleted() {
return bootstrapCompleted;
}
private void setTransactionTimeout(KSF keycloakSessionFactory) {
private void setTransactionTimeout(DefaultKeycloakSessionFactory keycloakSessionFactory) {
try {
var transactionTimeoutSeconds = getTransactionTimeout(keycloakSessionFactory);
KeycloakModelUtils.setTransactionLimit(keycloakSessionFactory, transactionTimeoutSeconds);
@ -199,7 +190,7 @@ public abstract class KeycloakApplication<KSF extends KeycloakSessionFactory> ex
}
}
private void resetTransactionTimeout(KSF keycloakSessionFactory) {
private void resetTransactionTimeout(DefaultKeycloakSessionFactory keycloakSessionFactory) {
try {
KeycloakModelUtils.setTransactionLimit(keycloakSessionFactory, 0);
} catch (Exception e) {

View file

@ -27,6 +27,7 @@ import org.keycloak.common.util.MultiSiteUtils;
import org.keycloak.exportimport.ExportImportManager;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.utils.KeycloakModelUtils;
import org.keycloak.services.DefaultKeycloakSessionFactory;
import org.keycloak.services.error.KcUnrecognizedPropertyExceptionHandler;
import org.keycloak.services.error.KeycloakErrorHandler;
import org.keycloak.services.error.KeycloakMismatchedInputExceptionHandler;
@ -41,7 +42,7 @@ import org.keycloak.services.resources.WelcomeResource;
import org.keycloak.services.resources.admin.AdminRoot;
import org.keycloak.services.util.ObjectMapperResolver;
public class ResteasyKeycloakApplication extends KeycloakApplication<ResteasyKeycloakSessionFactory> {
public class ResteasyKeycloakApplication extends KeycloakApplication {
protected Set<Object> singletons = new HashSet<>();
protected Set<Class<?>> classes = new HashSet<>();
@ -90,15 +91,10 @@ public class ResteasyKeycloakApplication extends KeycloakApplication<ResteasyKey
}
@Override
protected ResteasyKeycloakSessionFactory createSessionFactory() {
protected DefaultKeycloakSessionFactory createSessionFactory() {
return new ResteasyKeycloakSessionFactory();
}
@Override
protected void initKeycloakSessionFactory(ResteasyKeycloakSessionFactory resteasyKeycloakSessionFactory) {
resteasyKeycloakSessionFactory.init();
}
@Override
protected void createTemporaryAdmin(KeycloakSession session) {
// do nothing

View file

@ -17,11 +17,30 @@
package org.keycloak.services.resteasy;
import org.keycloak.Config;
import org.keycloak.models.KeycloakSession;
import org.keycloak.provider.KeycloakDeploymentInfo;
import org.keycloak.provider.ProviderManager;
import org.keycloak.provider.Spi;
import org.keycloak.services.DefaultKeycloakSessionFactory;
public class ResteasyKeycloakSessionFactory extends DefaultKeycloakSessionFactory {
@Override
public void init() {
ProviderManager pm = new ProviderManager(KeycloakDeploymentInfo.create().services(), getClass().getClassLoader(), Config.scope().getArray("providers"));
for (Spi spi : pm.loadSpis()) {
if (spi.isEnabled()) {
spis.add(spi);
}
}
factoriesMap = loadFactories(pm);
checkProvider();
super.init();
}
@Override
public KeycloakSession create() {
return new ResteasyKeycloakSession(this);

View file

@ -33,9 +33,10 @@ import org.keycloak.provider.EnvironmentDependentProviderFactory;
import org.keycloak.provider.KeycloakDeploymentInfo;
import org.keycloak.provider.ProviderFactory;
import org.keycloak.provider.ProviderManager;
import org.keycloak.provider.ProviderManagerRegistry;
import org.keycloak.provider.Spi;
import org.keycloak.services.DefaultKeycloakSession;
import org.keycloak.services.DefaultKeycloakSessionFactory;
import org.keycloak.services.resources.KeycloakApplication;
import org.jboss.logging.Logger;
@ -77,7 +78,7 @@ public class FeatureDeployerUtil {
manager = new ProviderManager(di, FeatureDeployerUtil.class.getClassLoader(), Collections.singleton(new TestsuiteProviderLoader(di)));
deployersCache.put(feature, manager);
}
ProviderManagerRegistry.SINGLETON.deploy(manager);
deploy(manager);
}
public static void undeployFactoriesAfterFeatureDisabled(Profile.Feature feature) {
@ -95,7 +96,7 @@ public class FeatureDeployerUtil {
loadFactories(manager);
deployersCache.put(feature, manager);
}
ProviderManagerRegistry.SINGLETON.undeploy(manager);
undeploy(manager);
}
private static Map<ProviderFactory, Spi> getFactoriesDependentOnFeature(Map<ProviderFactory, Spi> factoriesDisabled, Map<ProviderFactory, Spi> factoriesEnabled) {
@ -153,4 +154,19 @@ public class FeatureDeployerUtil {
DefaultProviderLoader loader = new DefaultProviderLoader(di, classLoader);
loader.loadSpis().forEach(pm::load);
}
static void deploy(ProviderManager pm) {
DefaultKeycloakSessionFactory deployer = KeycloakApplication.getSessionFactory();
if (deployer == null) {
throw new IllegalStateException("No active KeycloakApplication");
}
deployer.deploy(pm);
}
static void undeploy(ProviderManager pm) {
DefaultKeycloakSessionFactory deployer = KeycloakApplication.getSessionFactory();
if (deployer != null) {
deployer.undeploy(pm);
}
}
}

View file

@ -213,7 +213,7 @@ public class KeycloakOnUndertow implements DeployableContainer<KeycloakOnUnderto
DeploymentInfo di = createAuthServerDeploymentInfo();
undertow.deploy(di);
sessionFactory = (DefaultKeycloakSessionFactory) KeycloakApplication.getSessionFactory();
sessionFactory = KeycloakApplication.getSessionFactory();
registerScriptProviders(sessionFactory);

View file

@ -457,7 +457,7 @@ public class KeycloakServer {
server.deploy(di);
sessionFactory = (DefaultKeycloakSessionFactory) KeycloakApplication.getSessionFactory();
sessionFactory = KeycloakApplication.getSessionFactory();
registerScriptProviders(sessionFactory);