Merge pull request #936 from stianst/master

Provider loaders
This commit is contained in:
Stian Thorgersen 2015-01-28 11:51:05 +01:00
commit 6ecd9fbde7
23 changed files with 619 additions and 110 deletions

View file

@ -1,70 +0,0 @@
package org.keycloak.util;
import java.util.Iterator;
import java.util.ServiceLoader;
/**
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
*/
public class ProviderLoader<T> implements Iterable<T> {
private final ServiceLoader<T> serviceLoader;
public static <T> Iterable<T> load(Class<T> service) {
ServiceLoader<T> providers = ServiceLoader.load(service);
return new ProviderLoader(providers);
}
private ProviderLoader(ServiceLoader<T> serviceLoader) {
this.serviceLoader = serviceLoader;
}
@Override
public Iterator iterator() {
return new ProviderIterator(serviceLoader.iterator());
}
private static class ProviderIterator<T> implements Iterator<T> {
private final Iterator<T> itr;
private T next;
private ProviderIterator(Iterator<T> itr) {
this.itr = itr;
setNext();
}
@Override
public boolean hasNext() {
return next != null;
}
@Override
public T next() {
T n = next;
setNext();
return n;
}
@Override
public void remove() {
throw new UnsupportedOperationException();
}
private void setNext() {
next = null;
while (itr.hasNext()) {
if (itr.hasNext()) {
T n = itr.next();
if (!System.getProperties().containsKey(n.getClass().getName() + ".disabled")) {
next = n;
return;
}
}
}
}
}
}

View file

@ -42,7 +42,8 @@
</includes>
</fileSet>
<fileSet>
<directory>${project.build.directory}/unpacked/deployments/auth-server.war/WEB-INF/classes/META-INF</directory>
<directory>${project.build.directory}/unpacked/deployments/auth-server.war/WEB-INF/classes/META-INF
</directory>
<outputDirectory>keycloak/standalone/configuration</outputDirectory>
<includes>
<include>keycloak-server.json</include>
@ -61,27 +62,18 @@
<outputDirectory>keycloak/welcome-content</outputDirectory>
<includes>
<include>*.*</include>
</includes>
</includes>
</fileSet>
</fileSets>
<!--
<dependencySets>
<dependencySet>
<unpack>false</unpack>
<useTransitiveDependencies>false</useTransitiveDependencies>
<includes>
<include>org.keycloak:keycloak-wildfly-adapter-dist:zip</include>
<include>org.keycloak:keycloak-as7-adapter-dist:zip</include>
<include>org.keycloak:keycloak-eap6-adapter-dist:zip</include>
<include>org.keycloak:keycloak-tomcat6-adapter-dist:zip</include>
<include>org.keycloak:keycloak-tomcat7-adapter-dist:zip</include>
<include>org.keycloak:keycloak-tomcat8-adapter-dist:zip</include>
<include>org.keycloak:keycloak-jetty81-adapter-dist:zip</include>
<include>org.keycloak:keycloak-jetty91-adapter-dist:zip</include>
<include>org.keycloak:keycloak-jetty92-adapter-dist:zip</include>
</includes>
<outputDirectory>adapters</outputDirectory>
</dependencySet>
</dependencySets>
-->
<files>
<file>
<source>src/main/themes/README.txt</source>
<outputDirectory>keycloak/standalone/configuration/themes</outputDirectory>
</file>
<file>
<source>src/main/providers/README.txt</source>
<outputDirectory>keycloak/standalone/configuration/providers</outputDirectory>
</file>
</files>
</assembly>

View file

@ -0,0 +1,2 @@
Any provider implementation jars and libraries in this folder will be loaded by Keycloak. See the providers
section in the documentation for more details.

View file

@ -0,0 +1,3 @@
Themes to configure the look and feel of login pages and account management console. It's not recommended to
modify existing the built-in themes, instead you should create a new theme that extends a built-in theme. See the theme
section in the documentation for more details.

View file

@ -4,6 +4,7 @@
<!ENTITY License SYSTEM "modules/License.xml">
<!ENTITY Overview SYSTEM "modules/Overview.xml">
<!ENTITY Installation SYSTEM "modules/server-installation.xml">
<!ENTITY Providers SYSTEM "modules/providers.xml">
<!ENTITY OpenShift SYSTEM "modules/openshift.xml">
<!ENTITY AdminPermissions SYSTEM "modules/admin-permissions.xml">
<!ENTITY PerRealmAdminPermissions SYSTEM "modules/per-realm-admin-permissions.xml">
@ -79,6 +80,7 @@ This one is short
&License;
&Overview;
&Installation;
&Providers;
&OpenShift;
&AdminPermissions;
&PerRealmAdminPermissions;

View file

@ -0,0 +1,276 @@
<chapter id="providers">
<title>Providers and SPIs</title>
<para>
Keycloak is designed to cover most use-cases without requiring custom code, but we also want it to be
customizable. To achive this Keycloak has a number of SPIs which you can implement your own providers for.
</para>
<section>
<title>Implementing a SPI</title>
<para>
To implement an SPI you need to implement it's ProviderFactory and Provider interfaces. You also need to
create a provider-configuration file. For example to implement the Event Listener SPI you need to implement
EventListenerProviderFactory and EventListenerProvider and also provide the file
<literal>META-INF/services/org.keycloak.events.EventListenerProviderFactory</literal>
</para>
<para>
For example to implement the Event Listener SPI you start by implementing EventListenerProviderFactory:
<programlisting><![CDATA[{
package org.acme.provider;
import ...
public class MyEventListenerProviderFactory implements EventListenerProviderFactory {
private List<Event> events;
public String getId() {
return "my-event-listener";
}
public void init(Config.Scope config) {
int max = config.getInt("max");
events = new MaxList(max);
}
public EventListenerProvider create(KeycloakSession session) {
return new MyEventListenerProvider(events);
}
public void close() {
events = null;
}
}
}]]></programlisting>
The example uses a MaxList which has a maximum size and is concurrency safe. When the maximum size is reached
and new entries are added the oldest entry is removed. Keycloak creates a single instance of
EventListenerProviderFactory which makes it possible to store state for multiple requests. EventListenerProvider
instances are created by calling create on the factory for each requests so these should be light-weight.
</para>
<para>
Next you would implement EventListenerProvider:
<programlisting><![CDATA[{
package org.acme.provider;
import ...
public class MyEventListenerProvider implements EventListenerProvider {
private List<Event> events;
public MyEventListenerProvider(List<Event> events) {
this.events = events;
}
@Override
public void onEvent(Event event) {
events.add(event);
}
@Override
public void close() {
}
}
}]]></programlisting>
</para>
<para>
The file <literal>META-INF/services/org.keycloak.events.EventListenerProviderFactory</literal> should
contain the full name of your ProviderFactory implementation:
<programlisting><![CDATA[{
org.acme.provider.MyEventListenerProviderFactory
}]]></programlisting>
</para>
</section>
<section>
<title>Registering provider implementations</title>
<para>
Keycloak loads provider implementations from the file-system. By default all JARs inside
<literal>standalone/configuration/providers</literal> are loaded. This is simple, but requires all providers
to share the same library. All provides also inherit all classes from the Keycloak class-loader. In the future
we'll add support to load providers from modules, which allows better control of class isolation.
</para>
<para>
To register your provider simply copy the JAR including the ProviderFactory and Provider classes and the
provider configuration file to <literal>standalone/configuration/providers</literal>.
</para>
<para>
You can also define multiple provider class-path if you want to create isolated class-loaders. To do this
edit keycloak-server.json and add more classpath entries to the providers array. For example:
<programlisting><![CDATA[{
"providers": [
"classpath:provider1.jar;lib-v1.jar",
"classpath:provider2.jar;lib-v2.jar"
]
}]]></programlisting>
The above example will create two separate class-loaders for providers. The classpath entries follow the
same syntax as Java classpath, with ';' separating multiple-entries. Wildcard is also supported allowing
loading all jars (files with .jar or .JAR extension) in a folder, for example:
<programlisting><![CDATA[{
"providers": [
"classpath:/home/user/providers/*"
]
}]]></programlisting>
</para>
</section>
<section>
<title>Available SPIs</title>
<para>
Here's a list of the available SPIs and a brief description. For more details on each SPI refer to
individual
sections.
<variablelist>
<varlistentry>
<term>Account</term>
<listitem>
Provides the account manage console pages. The default implementation uses FreeMarker templates.
</listitem>
</varlistentry>
<varlistentry>
<term>Connections Infinispan</term>
<listitem>
Loads and configures Infinispan connections. The default implementation can load connections
from
the Infinispan subsystem, or alternatively can be manually configured in keycloak-server.json.
</listitem>
</varlistentry>
<varlistentry>
<term>Connections Jpa</term>
<listitem>
Loads and configures Infinispan connections. The default implementation can load datasources
from
WildFly/EAP, or alternatively can be manually configured in keycloak-server.json.
</listitem>
</varlistentry>
<varlistentry>
<term>Connections Jpa Updater</term>
<listitem>
Updates database schema. The default implementation uses Liquibase.
</listitem>
</varlistentry>
<varlistentry>
<term>Connections Mongo</term>
<listitem>
Loads and configures MongoDB connections. The default implementation is configured in
keycloak-server.json.
</listitem>
</varlistentry>
<varlistentry>
<term>Email</term>
<listitem>
Formats and sends email. The default implementation uses FreeMarker templates and JavaMail.
</listitem>
</varlistentry>
<varlistentry>
<term>Events Listener</term>
<listitem>
Listen to user related events for example user login success and failures. Keycloak provides two
implementations out of box. One that logs events to the server log and another that can send
email
notifications to users on certain events.
</listitem>
</varlistentry>
<varlistentry>
<term>Events Store</term>
<listitem>
Store user related events so they can be viewed through the admin console and account management
console.
Keycloak provides implementations for Relational Databases and MongoDB.
</listitem>
</varlistentry>
<varlistentry>
<term>Export</term>
<listitem>
Exports the Keycloak database. Keycloak provides implementations that export to JSON files
either
as a single file, multiple file in a directory or a encrypted ZIP archive.
</listitem>
</varlistentry>
<varlistentry>
<term>Import</term>
<listitem>
Imports and exported Keycloak database. Keycloak provides implementations that import from JSON
files either
as a single file, multiple file in a directory or a encrypted ZIP archive.
</listitem>
</varlistentry>
<varlistentry>
<term>Login</term>
<listitem>
Provides the login pages. The default implementation uses FreeMarker templates.
</listitem>
</varlistentry>
<varlistentry>
<term>Login Protocol</term>
<listitem>
Provides protocols. Keycloak provides implementations of OpenID Connect and SAML 2.0.
</listitem>
</varlistentry>
<varlistentry>
<term>Realm</term>
<listitem>
Provides realm and application meta-data. Keycloak provides implementations for Relational
Databases
and MongoDB.
</listitem>
</varlistentry>
<varlistentry>
<term>Realm Cache</term>
<listitem>
Caches realm and application meta-data to improve performance. Keycloak provides a basic
in-memory
cache and a Infinispan cache.
</listitem>
</varlistentry>
<varlistentry>
<term>Theme</term>
<listitem>
Allows creating themes to customize look and feel. Keycloak provides implementations that can
load
themes from the file-system or classpath.
</listitem>
</varlistentry>
<varlistentry>
<term>Timer</term>
<listitem>
Executes scheduled tasks. Keycloak provides a basic implementation based on java.util.Timer.
</listitem>
</varlistentry>
<varlistentry>
<term>User</term>
<listitem>
Provides users and role-mappings. Keycloak provides implementations for Relational Databases
and MongoDB.
</listitem>
</varlistentry>
<varlistentry>
<term>User Cache</term>
<listitem>
Caches users and role-mappings to improve performance. Keycloak provides a basic in-memory
cache and a Infinispan cache.
</listitem>
</varlistentry>
<varlistentry>
<term>User Federation</term>
<listitem>
Support syncing users from an external source. Keycloak provides implementations for LDAP and
Active Directory.
</listitem>
</varlistentry>
<varlistentry>
<term>User Sessions</term>
<listitem>
Provides users session information. Keycloak provides implementations for basic in-memory,
Infinispan,
Relational Databases and MongoDB
</listitem>
</varlistentry>
</variablelist>
</para>
</section>
</chapter>

View file

@ -173,8 +173,8 @@
contain a file called <literal>org.keycloak.models.UserFederationProviderFactory</literal>
within the <literal>META-INF/services</literal> directory of the JAR. This file is a list
of fully qualified classnames of all implementations of <literal>UserFederationProviderFactory</literal>.
This is how Keycloak discovers which providers have been deployed. Place the JAR in the
keycloak WAR deployment in the <literal>WEB-INF/lib</literal> directory.
For more details on writing provider implementations and how to deploy to Keycloak refer to the
<link linkend='providers'>providers</link> section.
</para>
</section>

View file

@ -1,4 +1,7 @@
Example Event Listener that prints events to System.out
=======================================================
To deploy copy target/event-listener-sysout-example.jar to standalone/deployments/auth-server.war/WEB-INF/lib. Then start (or restart) the server. Once started open the admin console, select your realm, then click on Events, followed by config. Click on Listeners select box, then pick sysout from the dropdown. After this try to logout and login again to see events printed to System.out.
To deploy copy target/event-listener-sysout-example.jar to standalone/configuration/providers.
Then start (or restart) the server. Once started open the admin console, select your realm, then click on Events,
followed by config. Click on Listeners select box, then pick sysout from the dropdown. After this try to logout and
login again to see events printed to System.out.

View file

@ -1,7 +1,9 @@
Example Event Store that stores events in memory
================================================
To deploy copy target/event-store-mem-example.jar to standalone/deployments/auth-server.war/WEB-INF/lib. Then edit standalone/configuration/keycloak-server.json, change:
To deploy copy target/event-store-mem-example.jar to standalone/configuration/providers.
Then edit standalone/configuration/keycloak-server.json, change:
"eventsStore": {
"provider": "jpa"
@ -13,4 +15,6 @@ to:
"provider": "in-mem"
}
Then start (or restart)the server. Once started open the admin console, select your realm, then click on Events, followed by config. Set the toggle for Enabled to ON. After this try to logout and login again then open the Events tab again in the admin console to view events from the in-mem provider.
Then start (or restart)the server. Once started open the admin console, select your realm, then click on Events,
followed by config. Set the toggle for Enabled to ON. After this try to logout and login again then open the Events tab
again in the admin console to view events from the in-mem provider.

View file

@ -2,8 +2,8 @@ Example User Federation Provider
===================================================
This is an example of user federation backed by a simple properties file. This properties file only contains username/password
key pairs. To deploy, build this directory then take the jar and copy it to the WEB-INF/lib of the keycloak server's
WAR file. You will then have to restart the authentication server.
key pairs. To deploy, build this directory then take the jar and copy it to standalone/configuration/providers.
You will then have to restart the authentication server.
The ClasspathPropertiesFederationProvider is an example of a readonly provider. If you go to the Users/Federation
page of the admin console you will see this provider listed under "classpath-properties. To configure this provider you

View file

@ -0,0 +1,51 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
~ Copyright 2013 JBoss Inc
~
~ 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.
-->
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-parent</artifactId>
<version>1.2.0.Beta1-SNAPSHOT</version>
<relativePath>../../pom.xml</relativePath>
</parent>
<artifactId>keycloak-wildfly-extensions</artifactId>
<name>Keycloak WildFly Extensions</name>
<description/>
<packaging>jar</packaging>
<dependencies>
<dependency>
<groupId>org.wildfly.core</groupId>
<artifactId>wildfly-controller</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-model-api</artifactId>
<version>${project.version}</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-services</artifactId>
<version>${project.version}</version>
<scope>provided</scope>
</dependency>
</dependencies>
</project>

View file

@ -0,0 +1,33 @@
package org.keycloak.provider.wildfly;
import org.jboss.modules.Module;
import org.jboss.modules.ModuleClassLoader;
import org.jboss.modules.ModuleIdentifier;
import org.jboss.modules.ModuleLoadException;
import org.keycloak.provider.DefaultProviderLoader;
import org.keycloak.provider.ProviderLoader;
import org.keycloak.provider.ProviderLoaderFactory;
/**
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
*/
public class ModuleProviderLoaderFactory implements ProviderLoaderFactory {
@Override
public boolean supports(String type) {
return "module".equals(type);
}
@Override
public ProviderLoader create(ClassLoader baseClassLoader, String resource) {
try {
System.out.println("XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXx");
Module module = Module.getContextModuleLoader().loadModule(ModuleIdentifier.fromString(resource));
ModuleClassLoader classLoader = module.getClassLoader();
return new DefaultProviderLoader(classLoader);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}

View file

@ -0,0 +1 @@
org.keycloak.provider.wildfly.ModuleProviderLoaderFactory

View file

@ -0,0 +1,12 @@
package org.keycloak.provider;
import java.util.List;
/**
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
*/
public interface ProviderLoader {
List<ProviderFactory> load(Spi spi);
}

View file

@ -0,0 +1,12 @@
package org.keycloak.provider;
/**
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
*/
public interface ProviderLoaderFactory {
boolean supports(String type);
ProviderLoader create(ClassLoader baseClassLoader, String resource);
}

View file

@ -1,4 +1,8 @@
{
"providers": [
"classpath:${jboss.server.config.dir}/providers/*"
],
"admin": {
"realm": "master"
},

View file

@ -0,0 +1,27 @@
package org.keycloak.provider;
import java.util.LinkedList;
import java.util.List;
import java.util.ServiceLoader;
/**
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
*/
public class DefaultProviderLoader implements ProviderLoader {
private ClassLoader classLoader;
public DefaultProviderLoader(ClassLoader classLoader) {
this.classLoader = classLoader;
}
@Override
public List<ProviderFactory> load(Spi spi) {
LinkedList<ProviderFactory> list = new LinkedList<ProviderFactory>();
for (ProviderFactory f : ServiceLoader.load(spi.getProviderFactoryClass(), classLoader)) {
list.add(f);
}
return list;
}
}

View file

@ -0,0 +1,18 @@
package org.keycloak.provider;
/**
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
*/
public class DefaultProviderLoaderFactory implements ProviderLoaderFactory {
@Override
public boolean supports(String type) {
return false;
}
@Override
public ProviderLoader create(ClassLoader baseClassLoader, String resource) {
return new DefaultProviderLoader(baseClassLoader);
}
}

View file

@ -0,0 +1,63 @@
package org.keycloak.provider;
import org.jboss.logging.Logger;
import java.io.File;
import java.io.FilenameFilter;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.LinkedList;
import java.util.List;
/**
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
*/
public class FileSystemProviderLoaderFactory implements ProviderLoaderFactory {
private static final Logger log = Logger.getLogger(FileSystemProviderLoaderFactory.class);
@Override
public boolean supports(String type) {
return "classpath".equals(type);
}
@Override
public ProviderLoader create(ClassLoader baseClassLoader, String resource) {
return new DefaultProviderLoader(createClassLoader(baseClassLoader, resource.split(";")));
}
private static URLClassLoader createClassLoader(ClassLoader parent, String... files) {
try {
List<URL> urls = new LinkedList<URL>();
for (String f : files) {
if (f.endsWith("*")) {
File dir = new File(f.substring(0, f.length() - 1));
if (dir.isDirectory()) {
for (File file : dir.listFiles(new JarFilter())) {
urls.add(file.toURI().toURL());
}
}
} else {
urls.add(new File(f).toURI().toURL());
}
}
log.debug("Loading providers from " + urls.toString());
return new URLClassLoader(urls.toArray(new URL[urls.size()]), parent);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
private static class JarFilter implements FilenameFilter {
@Override
public boolean accept(File dir, String name) {
return name.toLowerCase().endsWith(".jar");
}
}
}

View file

@ -0,0 +1,74 @@
package org.keycloak.provider;
import org.jboss.logging.Logger;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.ServiceLoader;
/**
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
*/
public class ProviderManager {
private static final Logger log = Logger.getLogger(ProviderManager.class);
private List<ProviderLoader> loaders = new LinkedList<ProviderLoader>();
private Map<String, List<ProviderFactory>> cache = new HashMap<String, List<ProviderFactory>>();
public ProviderManager(ClassLoader baseClassLoader, String... resources) {
List<ProviderLoaderFactory> factories = new LinkedList<ProviderLoaderFactory>();
for (ProviderLoaderFactory f : ServiceLoader.load(ProviderLoaderFactory.class)) {
factories.add(f);
}
log.debugv("Provider loaders {0}", factories);
loaders.add(new DefaultProviderLoader(baseClassLoader));
if (resources != null) {
for (String r : resources) {
String type = r.substring(0, r.indexOf(':'));
String resource = r.substring(r.indexOf(':') + 1, r.length());
boolean found = false;
for (ProviderLoaderFactory f : factories) {
if (f.supports(type)) {
loaders.add(f.create(baseClassLoader, resource));
found = true;
break;
}
}
if (!found) {
throw new RuntimeException("Provider loader for " + r + " not found");
}
}
}
}
public synchronized List<ProviderFactory> load(Spi spi) {
List<ProviderFactory> factories = cache.get(spi.getName());
if (factories == null) {
factories = new LinkedList<ProviderFactory>();
for (ProviderLoader loader : loaders) {
List<ProviderFactory> f = loader.load(spi);
if (f != null) {
factories.addAll(f);
}
}
}
return factories;
}
public synchronized ProviderFactory load(Spi spi, String providerId) {
for (ProviderFactory f : load(spi)) {
if (f.getId().equals(providerId)) {
return f;
}
}
return null;
}
}

View file

@ -6,6 +6,7 @@ import org.keycloak.models.KeycloakSession;
import org.keycloak.models.KeycloakSessionFactory;
import org.keycloak.provider.Provider;
import org.keycloak.provider.ProviderFactory;
import org.keycloak.provider.ProviderManager;
import org.keycloak.provider.Spi;
import java.util.HashMap;
@ -24,6 +25,8 @@ public class DefaultKeycloakSessionFactory implements KeycloakSessionFactory {
private Map<Class<? extends Provider>, Map<String, ProviderFactory>> factoriesMap = new HashMap<Class<? extends Provider>, Map<String, ProviderFactory>>();
public void init() {
ProviderManager pm = new ProviderManager(getClass().getClassLoader(), Config.scope().getArray("providers"));
for (Spi spi : ServiceLoader.load(Spi.class)) {
Map<String, ProviderFactory> factories = new HashMap<String, ProviderFactory>();
factoriesMap.put(spi.getProviderClass(), factories);
@ -32,7 +35,11 @@ public class DefaultKeycloakSessionFactory implements KeycloakSessionFactory {
if (provider != null) {
this.provider.put(spi.getProviderClass(), provider);
ProviderFactory factory = loadProviderFactory(spi, provider);
ProviderFactory factory = pm.load(spi, provider);
if (factory == null) {
throw new RuntimeException("Failed to find provider " + provider + " for " + spi.getName());
}
Config.Scope scope = Config.scope(spi.getName(), provider);
factory.init(scope);
@ -40,7 +47,7 @@ public class DefaultKeycloakSessionFactory implements KeycloakSessionFactory {
log.debugv("Loaded SPI {0} (provider = {1})", spi.getName(), provider);
} else {
for (ProviderFactory factory : ServiceLoader.load(spi.getProviderFactoryClass())) {
for (ProviderFactory factory : pm.load(spi)) {
Config.Scope scope = Config.scope(spi.getName(), factory.getId());
factory.init(scope);
@ -59,15 +66,6 @@ public class DefaultKeycloakSessionFactory implements KeycloakSessionFactory {
}
}
private ProviderFactory loadProviderFactory(Spi spi, String id) {
for (ProviderFactory factory : ServiceLoader.load(spi.getProviderFactoryClass())) {
if (factory.getId().equals(id)){
return factory;
}
}
throw new RuntimeException("Failed to find provider " + id + " for " + spi.getName());
}
public KeycloakSession create() {
return new DefaultKeycloakSession(this);
}

View file

@ -0,0 +1,2 @@
org.keycloak.provider.DefaultProviderLoaderFactory
org.keycloak.provider.FileSystemProviderLoaderFactory

View file

@ -8,6 +8,8 @@ log4j.logger.org.keycloak=info
# Enable to view loaded SPI and Providers
# log4j.logger.org.keycloak.services.DefaultKeycloakSessionFactory=debug
# log4j.logger.org.keycloak.provider.ProviderManager=debug
# log4j.logger.org.keycloak.provider.FileSystemProviderLoaderFactory=debug
# Enable to view database updates
# log4j.logger.org.keycloak.connections.jpa.updater.liquibase.LiquibaseJpaUpdaterProvider=debug