mirror of
https://github.com/keycloak/keycloak.git
synced 2026-05-28 04:13:22 -04:00
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:
parent
a2db1bb43e
commit
bb10a2c81c
22 changed files with 123 additions and 234 deletions
|
|
@ -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() {
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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());
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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
|
||||
*
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
||||
|
|
|
|||
|
|
@ -457,7 +457,7 @@ public class KeycloakServer {
|
|||
|
||||
server.deploy(di);
|
||||
|
||||
sessionFactory = (DefaultKeycloakSessionFactory) KeycloakApplication.getSessionFactory();
|
||||
sessionFactory = KeycloakApplication.getSessionFactory();
|
||||
|
||||
registerScriptProviders(sessionFactory);
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue