KEYCLOAK-12175 - Platform SPI

This commit is contained in:
Dmitry Telegin 2019-11-27 17:28:26 +03:00 committed by Stian Thorgersen
parent 30e024a3c9
commit e2144d6aec
16 changed files with 258 additions and 80 deletions

View file

@ -44,7 +44,7 @@
</context-param> </context-param>
<listener> <listener>
<listener-class>org.keycloak.services.listeners.KeycloakSessionDestroyListener</listener-class> <listener-class>org.keycloak.provider.wildfly.WildflyLifecycleListener</listener-class>
</listener> </listener>
<filter> <filter>

View file

@ -38,7 +38,7 @@
<module name="org.keycloak.keycloak-saml-core-public" services="import"/> <module name="org.keycloak.keycloak-saml-core-public" services="import"/>
<module name="org.keycloak.keycloak-saml-core" services="import"/> <module name="org.keycloak.keycloak-saml-core" services="import"/>
<module name="org.keycloak.keycloak-services" export="true" services="import"/> <module name="org.keycloak.keycloak-services" export="true" services="import"/>
<module name="org.keycloak.keycloak-wildfly-extensions" services="import"/> <module name="org.keycloak.keycloak-wildfly-extensions" export="true" services="import"/>
<!-- Authorization --> <!-- Authorization -->
<module name="org.keycloak.keycloak-authz-policy-common" services="import"/> <module name="org.keycloak.keycloak-authz-policy-common" services="import"/>

View file

@ -17,23 +17,31 @@
package org.keycloak.provider.quarkus; package org.keycloak.provider.quarkus;
import io.quarkus.runtime.ShutdownEvent;
import io.quarkus.runtime.StartupEvent; import io.quarkus.runtime.StartupEvent;
import javax.enterprise.context.ApplicationScoped; import javax.enterprise.context.ApplicationScoped;
import javax.enterprise.event.Observes; import javax.enterprise.event.Observes;
import org.keycloak.platform.Platform;
@ApplicationScoped @ApplicationScoped
public class QuarkusStartupObserver { public class QuarkusLifecycleObserver {
private Runnable command; private void onStartupEvent(@Observes StartupEvent event) {
Runnable startupHook = ((QuarkusPlatform) Platform.getPlatform()).startupHook;
if (startupHook != null)
startupHook.run();
public void setCommand(Runnable command) {
this.command = command;
} }
private void startupEvent(@Observes StartupEvent event) { private void onShutdownEvent(@Observes ShutdownEvent event) {
if (command != null) {
command.run(); Runnable shutdownHook = ((QuarkusPlatform) Platform.getPlatform()).shutdownHook;
}
if (shutdownHook != null)
shutdownHook.run();
} }
} }

View file

@ -0,0 +1,42 @@
/*
* Copyright 2019 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.quarkus;
import org.keycloak.platform.PlatformProvider;
public class QuarkusPlatform implements PlatformProvider {
Runnable startupHook;
Runnable shutdownHook;
@Override
public void onStartup(Runnable startupHook) {
this.startupHook = startupHook;
}
@Override
public void onShutdown(Runnable shutdownHook) {
this.shutdownHook = shutdownHook;
}
@Override
public void exit(Throwable cause) {
throw new RuntimeException(cause);
}
}

View file

@ -0,0 +1,18 @@
#
# Copyright 2019 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.
#
org.keycloak.provider.quarkus.QuarkusPlatform

View file

@ -42,10 +42,6 @@
<param-name>resteasy.disable.html.sanitizer</param-name> <param-name>resteasy.disable.html.sanitizer</param-name>
<param-value>true</param-value> <param-value>true</param-value>
</context-param> </context-param>
<context-param>
<param-name>keycloak.embedded</param-name>
<param-value>true</param-value>
</context-param>
<listener> <listener>
<listener-class> <listener-class>
@ -53,10 +49,6 @@
</listener-class> </listener-class>
</listener> </listener>
<listener>
<listener-class>org.keycloak.services.listeners.KeycloakSessionDestroyListener</listener-class>
</listener>
<filter> <filter>
<filter-name>Keycloak Session Management</filter-name> <filter-name>Keycloak Session Management</filter-name>
<filter-class>org.keycloak.services.filters.KeycloakSessionServletFilter</filter-class> <filter-class>org.keycloak.services.filters.KeycloakSessionServletFilter</filter-class>

View file

@ -0,0 +1,42 @@
/*
* Copyright 2019 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.platform;
import java.util.NoSuchElementException;
import java.util.ServiceLoader;
public class Platform {
private static PlatformProvider INSTANCE;
public static PlatformProvider getPlatform() {
if (INSTANCE == null) {
ServiceLoader<PlatformProvider> loader = ServiceLoader.load(PlatformProvider.class, Platform.class.getClassLoader());
try {
INSTANCE = loader.iterator().next();
} catch (NoSuchElementException e) {
throw new RuntimeException("No PlatformProvider found");
}
}
return INSTANCE;
}
}

View file

@ -0,0 +1,28 @@
/*
* Copyright 2019 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.platform;
public interface PlatformProvider {
void onStartup(Runnable runnable);
void onShutdown(Runnable runnable);
void exit(Throwable cause);
}

View file

@ -35,6 +35,8 @@ import org.keycloak.models.dblock.DBLockProvider;
import org.keycloak.models.utils.KeycloakModelUtils; import org.keycloak.models.utils.KeycloakModelUtils;
import org.keycloak.models.utils.PostMigrationEvent; import org.keycloak.models.utils.PostMigrationEvent;
import org.keycloak.models.utils.RepresentationToModel; import org.keycloak.models.utils.RepresentationToModel;
import org.keycloak.platform.Platform;
import org.keycloak.platform.PlatformProvider;
import org.keycloak.representations.idm.RealmRepresentation; import org.keycloak.representations.idm.RealmRepresentation;
import org.keycloak.representations.idm.UserRepresentation; import org.keycloak.representations.idm.UserRepresentation;
import org.keycloak.services.DefaultKeycloakSessionFactory; import org.keycloak.services.DefaultKeycloakSessionFactory;
@ -65,13 +67,11 @@ import java.io.FileNotFoundException;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.util.HashSet; import java.util.HashSet;
import java.util.Iterator;
import java.util.List; import java.util.List;
import java.util.NoSuchElementException; import java.util.NoSuchElementException;
import java.util.ServiceLoader; import java.util.ServiceLoader;
import java.util.Set; import java.util.Set;
import java.util.StringTokenizer; import java.util.StringTokenizer;
import java.util.concurrent.Executor;
import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicBoolean;
/** /**
@ -80,13 +80,11 @@ import java.util.concurrent.atomic.AtomicBoolean;
*/ */
public class KeycloakApplication extends Application { public class KeycloakApplication extends Application {
public static final String KEYCLOAK_EMBEDDED = "keycloak.embedded";
public static final AtomicBoolean BOOTSTRAP_ADMIN_USER = new AtomicBoolean(false); public static final AtomicBoolean BOOTSTRAP_ADMIN_USER = new AtomicBoolean(false);
private static final Logger logger = Logger.getLogger(KeycloakApplication.class); private static final Logger logger = Logger.getLogger(KeycloakApplication.class);
protected boolean embedded = false; protected final PlatformProvider platform = Platform.getPlatform();
protected Set<Object> singletons = new HashSet<Object>(); protected Set<Object> singletons = new HashSet<Object>();
protected Set<Class<?>> classes = new HashSet<Class<?>>(); protected Set<Class<?>> classes = new HashSet<Class<?>>();
@ -97,14 +95,11 @@ public class KeycloakApplication extends Application {
try { try {
logger.debugv("PlatformProvider: {0}", platform.getClass().getName());
logger.debugv("RestEasy provider: {0}", Resteasy.getProvider().getClass().getName()); logger.debugv("RestEasy provider: {0}", Resteasy.getProvider().getClass().getName());
ServletContext context = Resteasy.getContextData(ServletContext.class); ServletContext context = Resteasy.getContextData(ServletContext.class);
if ("true".equals(context.getInitParameter(KEYCLOAK_EMBEDDED))) {
embedded = true;
}
loadConfig(); loadConfig();
this.sessionFactory = createSessionFactory(); this.sessionFactory = createSessionFactory();
@ -125,26 +120,11 @@ public class KeycloakApplication extends Application {
singletons.add(new ObjectMapperResolver(Boolean.parseBoolean(System.getProperty("keycloak.jsonPrettyPrint", "false")))); singletons.add(new ObjectMapperResolver(Boolean.parseBoolean(System.getProperty("keycloak.jsonPrettyPrint", "false"))));
singletons.add(new WelcomeResource()); singletons.add(new WelcomeResource());
init(this::startup); platform.onStartup(this::startup);
platform.onShutdown(this::shutdown);
} catch (Throwable t) { } catch (Throwable t) {
if (!embedded) { platform.exit(t);
exit(1);
}
throw t;
}
}
private void init(Runnable function) {
ServiceLoader<Startup> loader = ServiceLoader.load(Startup.class);
Iterator<Startup> iterator = loader.iterator();
if (iterator.hasNext()) {
iterator.next().execute(function);
} else {
function.run();
} }
} }
@ -191,6 +171,11 @@ public class KeycloakApplication extends Application {
} }
protected void shutdown() {
if (sessionFactory != null)
sessionFactory.close();
}
// Migrate model, bootstrap master realm, import realms and create admin user. This is done with acquired dbLock // Migrate model, bootstrap master realm, import realms and create admin user. This is done with acquired dbLock
protected ExportImportManager migrateAndBootstrap() { protected ExportImportManager migrateAndBootstrap() {
ExportImportManager exportImportManager; ExportImportManager exportImportManager;
@ -270,7 +255,7 @@ public class KeycloakApplication extends Application {
try { try {
ConfigProviderFactory factory = loader.iterator().next(); ConfigProviderFactory factory = loader.iterator().next();
logger.infov("Using ConfigProvider: {0}", factory.getClass().getName()); logger.debugv("ConfigProvider: {0}", factory.getClass().getName());
Config.init(factory.create().orElseThrow(() -> new RuntimeException("Failed to load Keycloak configuration"))); Config.init(factory.create().orElseThrow(() -> new RuntimeException("Failed to load Keycloak configuration")));
} catch (NoSuchElementException e) { } catch (NoSuchElementException e) {
throw new RuntimeException("No valid ConfigProvider found"); throw new RuntimeException("No valid ConfigProvider found");
@ -432,16 +417,4 @@ public class KeycloakApplication extends Application {
} }
} }
private void exit(int status) {
new Thread() {
@Override
public void run() {
System.exit(status);
}
}.start();
}
public static interface Startup extends Executor {
}
} }

View file

@ -88,7 +88,6 @@ public class KeycloakOnUndertow implements DeployableContainer<KeycloakOnUnderto
di.setClassLoader(getClass().getClassLoader()); di.setClassLoader(getClass().getClassLoader());
di.setContextPath("/auth"); di.setContextPath("/auth");
di.setDeploymentName("Keycloak"); di.setDeploymentName("Keycloak");
di.addInitParameter(KeycloakApplication.KEYCLOAK_EMBEDDED, "true");
if (configuration.getKeycloakConfigPropertyOverridesMap() != null) { if (configuration.getKeycloakConfigPropertyOverridesMap() != null) {
try { try {
di.addInitParameter(JsonConfigProviderFactory.SERVER_CONTEXT_CONFIG_PROPERTY_OVERRIDES, di.addInitParameter(JsonConfigProviderFactory.SERVER_CONTEXT_CONFIG_PROPERTY_OVERRIDES,

View file

@ -399,8 +399,6 @@ public class KeycloakServer {
di.setDeploymentName("Keycloak"); di.setDeploymentName("Keycloak");
di.setDefaultEncoding("UTF-8"); di.setDefaultEncoding("UTF-8");
di.addInitParameter(KeycloakApplication.KEYCLOAK_EMBEDDED, "true");
di.setDefaultServletConfig(new DefaultServletConfig(true)); di.setDefaultServletConfig(new DefaultServletConfig(true));
ServletInfo restEasyDispatcher = Servlets.servlet("Keycloak REST Interface", HttpServlet30Dispatcher.class); ServletInfo restEasyDispatcher = Servlets.servlet("Keycloak REST Interface", HttpServlet30Dispatcher.class);

View file

@ -15,17 +15,24 @@
* limitations under the License. * limitations under the License.
*/ */
package org.keycloak.provider.quarkus; package org.keycloak.testsuite;
import io.quarkus.arc.Arc; import org.keycloak.platform.PlatformProvider;
import org.keycloak.services.resources.KeycloakApplication;
public class QuarkusStartup implements KeycloakApplication.Startup { public class TestPlatform implements PlatformProvider {
@Override @Override
public void execute(Runnable command) { public void onStartup(Runnable startupHook) {
QuarkusStartupObserver observer = Arc.container().instance(QuarkusStartupObserver.class).get(); startupHook.run();
observer.setCommand(command); }
@Override
public void onShutdown(Runnable shutdownHook) {
}
@Override
public void exit(Throwable cause) {
throw new RuntimeException(cause);
} }
} }

View file

@ -15,4 +15,4 @@
# limitations under the License. # limitations under the License.
# #
org.keycloak.provider.quarkus.QuarkusStartup org.keycloak.testsuite.TestPlatform

View file

@ -1,5 +1,5 @@
/* /*
* Copyright 2016 Red Hat, Inc. and/or its affiliates * Copyright 2019 Red Hat, Inc. and/or its affiliates
* and other contributors as indicated by the @author tags. * and other contributors as indicated by the @author tags.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
@ -15,17 +15,15 @@
* limitations under the License. * limitations under the License.
*/ */
package org.keycloak.services.listeners; package org.keycloak.provider.wildfly;
import org.keycloak.models.KeycloakSessionFactory;
import javax.servlet.ServletContextEvent; import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener; import javax.servlet.ServletContextListener;
import javax.servlet.annotation.WebListener;
import org.keycloak.platform.Platform;
/** @WebListener
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a> public class WildflyLifecycleListener implements ServletContextListener {
*/
public class KeycloakSessionDestroyListener implements ServletContextListener {
@Override @Override
public void contextInitialized(ServletContextEvent sce) { public void contextInitialized(ServletContextEvent sce) {
@ -33,10 +31,13 @@ public class KeycloakSessionDestroyListener implements ServletContextListener {
@Override @Override
public void contextDestroyed(ServletContextEvent sce) { public void contextDestroyed(ServletContextEvent sce) {
KeycloakSessionFactory sessionFactory = (KeycloakSessionFactory) sce.getServletContext().getAttribute(KeycloakSessionFactory.class.getName());
if (sessionFactory != null) { Runnable shutdownHook = ((WildflyPlatform) Platform.getPlatform()).shutdownHook;
sessionFactory.close();
if (shutdownHook != null) {
shutdownHook.run();
} }
} }
} }

View file

@ -0,0 +1,52 @@
/*
* Copyright 2019 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.wildfly;
import org.keycloak.platform.PlatformProvider;
import org.keycloak.services.ServicesLogger;
public class WildflyPlatform implements PlatformProvider {
Runnable shutdownHook;
@Override
public void onStartup(Runnable startupHook) {
startupHook.run();
}
@Override
public void onShutdown(Runnable shutdownHook) {
this.shutdownHook = shutdownHook;
}
@Override
public void exit(Throwable cause) {
ServicesLogger.LOGGER.fatal(cause);
exit(1);
}
private void exit(int status) {
new Thread() {
@Override
public void run() {
System.exit(status);
}
}.start();
}
}

View file

@ -0,0 +1,18 @@
#
# Copyright 2019 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.
#
org.keycloak.provider.wildfly.WildflyPlatform