KEYCLOAK-2474 Possibility to add custom SPI and extend the data model
This commit is contained in:
parent
111bcb7433
commit
f4ead484de
15 changed files with 380 additions and 38 deletions
|
@ -20,25 +20,17 @@ package org.keycloak.connections.jpa;
|
|||
import java.sql.Connection;
|
||||
import java.sql.DatabaseMetaData;
|
||||
import java.sql.DriverManager;
|
||||
import java.sql.ResultSet;
|
||||
import java.sql.SQLException;
|
||||
import java.util.HashMap;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import javax.naming.InitialContext;
|
||||
import javax.persistence.EntityManager;
|
||||
import javax.persistence.EntityManagerFactory;
|
||||
import javax.persistence.Persistence;
|
||||
import javax.persistence.spi.PersistenceUnitTransactionType;
|
||||
import javax.sql.DataSource;
|
||||
|
||||
import org.hibernate.boot.registry.classloading.internal.ClassLoaderServiceImpl;
|
||||
import org.hibernate.ejb.AvailableSettings;
|
||||
import org.hibernate.jpa.boot.internal.ParsedPersistenceXmlDescriptor;
|
||||
import org.hibernate.jpa.boot.internal.PersistenceXmlParser;
|
||||
import org.hibernate.jpa.boot.spi.Bootstrap;
|
||||
import org.jboss.logging.Logger;
|
||||
import org.keycloak.Config;
|
||||
import org.keycloak.connections.jpa.updater.JpaUpdaterProvider;
|
||||
|
@ -182,7 +174,7 @@ public class DefaultJpaConnectionProviderFactory implements JpaConnectionProvide
|
|||
}
|
||||
|
||||
logger.trace("Creating EntityManagerFactory");
|
||||
emf = JpaUtils.createEntityManagerFactory(unitName, properties, getClass().getClassLoader());
|
||||
emf = JpaUtils.createEntityManagerFactory(session, unitName, properties, getClass().getClassLoader());
|
||||
logger.trace("EntityManagerFactory created");
|
||||
|
||||
if (globalStatsInterval != -1) {
|
||||
|
|
|
@ -0,0 +1,47 @@
|
|||
/*
|
||||
* 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.connections.jpa.entityprovider;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.keycloak.provider.Provider;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:erik.mulder@docdatapayments.com">Erik Mulder</a>
|
||||
*
|
||||
* A JPA Entity Provider can supply extra JPA entities that the Keycloak system should include in it's entity manager. The
|
||||
* entities should be provided as a list of Class objects.
|
||||
*/
|
||||
public interface JpaEntityProvider extends Provider {
|
||||
|
||||
/**
|
||||
* Return the entities that should be added to the entity manager.
|
||||
*
|
||||
* @return list of class objects
|
||||
*/
|
||||
List<Class<?>> getEntities();
|
||||
|
||||
/**
|
||||
* Return the location of the Liquibase changelog that facilitates the extra JPA entities.
|
||||
* This should be a location that can be found on the same classpath as the entity classes.
|
||||
*
|
||||
* @return a changelog location or null if not needed
|
||||
*/
|
||||
String getChangelogLocation();
|
||||
|
||||
}
|
|
@ -0,0 +1,29 @@
|
|||
/*
|
||||
* 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.connections.jpa.entityprovider;
|
||||
|
||||
import org.keycloak.provider.ProviderFactory;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:erik.mulder@docdatapayments.com">Erik Mulder</a>
|
||||
*
|
||||
* Extended interface for a provider factory for JpaEntityProvider's.
|
||||
*/
|
||||
public interface JpaEntityProviderFactory extends ProviderFactory<JpaEntityProvider> {
|
||||
|
||||
}
|
|
@ -0,0 +1,51 @@
|
|||
/*
|
||||
* 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.connections.jpa.entityprovider;
|
||||
|
||||
import org.keycloak.provider.Provider;
|
||||
import org.keycloak.provider.ProviderFactory;
|
||||
import org.keycloak.provider.Spi;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:erik.mulder@docdatapayments.com">Erik Mulder</a>
|
||||
*
|
||||
* Spi that allows for adding extra JPA entity's to the Keycloak entity manager.
|
||||
*/
|
||||
public class JpaEntitySpi implements Spi {
|
||||
|
||||
@Override
|
||||
public boolean isInternal() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return "jpa-entity-provider";
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<? extends Provider> getProviderClass() {
|
||||
return JpaEntityProvider.class;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<? extends ProviderFactory> getProviderFactoryClass() {
|
||||
return JpaEntityProviderFactory.class;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,87 @@
|
|||
/*
|
||||
* 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.connections.jpa.entityprovider;
|
||||
|
||||
import java.net.URL;
|
||||
import java.util.Collection;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:erik.mulder@docdatapayments.com">Erik Mulder</a>
|
||||
*
|
||||
* Classloader implementation to facilitate loading classes and resources from a collection of other classloaders.
|
||||
* Effectively it forms a proxy to one or more other classloaders.
|
||||
*
|
||||
* The way it works:
|
||||
* - Get all (unique) classloaders from all provided classes
|
||||
* - For each class or resource that is 'requested':
|
||||
* - First try all provided classloaders and if we have a match, return that
|
||||
* - If no match was found: proceed with 'normal' classloading in 'current classpath' scope
|
||||
*
|
||||
* In this particular context: only loadClass and getResource overrides are needed, since those
|
||||
* are the methods that a classloading and resource loading process will need.
|
||||
*/
|
||||
public class ProxyClassLoader extends ClassLoader {
|
||||
|
||||
private Set<ClassLoader> classloaders;
|
||||
|
||||
public ProxyClassLoader(Collection<Class<?>> classes, ClassLoader parentClassLoader) {
|
||||
super(parentClassLoader);
|
||||
init(classes);
|
||||
}
|
||||
|
||||
public ProxyClassLoader(Collection<Class<?>> classes) {
|
||||
init(classes);
|
||||
}
|
||||
|
||||
private void init(Collection<Class<?>> classes) {
|
||||
classloaders = new HashSet<>();
|
||||
for (Class<?> clazz : classes) {
|
||||
classloaders.add(clazz.getClassLoader());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<?> loadClass(String name) throws ClassNotFoundException {
|
||||
for (ClassLoader classloader : classloaders) {
|
||||
try {
|
||||
return classloader.loadClass(name);
|
||||
} catch (ClassNotFoundException e) {
|
||||
// This particular class loader did not find the class. It's expected behavior that
|
||||
// this can happen, so we'll just ignore the exception and let the next one try.
|
||||
}
|
||||
}
|
||||
// We did not find the class in the proxy class loaders, so proceed with 'normal' behavior.
|
||||
return super.loadClass(name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public URL getResource(String name) {
|
||||
for (ClassLoader classloader : classloaders) {
|
||||
URL resource = classloader.getResource(name);
|
||||
if (resource != null) {
|
||||
return resource;
|
||||
}
|
||||
// Resource == null means not found, so let the next one try.
|
||||
}
|
||||
// We could not get the resource from the proxy class loaders, so proceed with 'normal' behavior.
|
||||
return super.getResource(name);
|
||||
}
|
||||
|
||||
}
|
|
@ -18,8 +18,25 @@
|
|||
package org.keycloak.connections.jpa.updater.liquibase.conn;
|
||||
|
||||
import java.sql.Connection;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
import org.jboss.logging.Logger;
|
||||
import org.keycloak.Config;
|
||||
import org.keycloak.connections.jpa.entityprovider.JpaEntityProvider;
|
||||
import org.keycloak.connections.jpa.entityprovider.ProxyClassLoader;
|
||||
import org.keycloak.connections.jpa.updater.liquibase.LiquibaseJpaUpdaterProvider;
|
||||
import org.keycloak.connections.jpa.updater.liquibase.PostgresPlusDatabase;
|
||||
import org.keycloak.connections.jpa.updater.liquibase.lock.CustomInsertLockRecordGenerator;
|
||||
import org.keycloak.connections.jpa.updater.liquibase.lock.CustomLockDatabaseChangeLogGenerator;
|
||||
import org.keycloak.connections.jpa.updater.liquibase.lock.DummyLockService;
|
||||
import org.keycloak.connections.jpa.util.JpaUtils;
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
import org.keycloak.models.KeycloakSessionFactory;
|
||||
|
||||
import liquibase.Liquibase;
|
||||
import liquibase.changelog.ChangeLogParameters;
|
||||
import liquibase.changelog.ChangeSet;
|
||||
import liquibase.changelog.DatabaseChangeLog;
|
||||
import liquibase.database.Database;
|
||||
|
@ -27,23 +44,14 @@ import liquibase.database.DatabaseFactory;
|
|||
import liquibase.database.core.DB2Database;
|
||||
import liquibase.database.jvm.JdbcConnection;
|
||||
import liquibase.exception.LiquibaseException;
|
||||
import liquibase.lockservice.LockService;
|
||||
import liquibase.lockservice.LockServiceFactory;
|
||||
import liquibase.logging.LogFactory;
|
||||
import liquibase.logging.LogLevel;
|
||||
import liquibase.parser.ChangeLogParser;
|
||||
import liquibase.parser.ChangeLogParserFactory;
|
||||
import liquibase.resource.ClassLoaderResourceAccessor;
|
||||
import liquibase.resource.ResourceAccessor;
|
||||
import liquibase.servicelocator.ServiceLocator;
|
||||
import liquibase.sqlgenerator.SqlGeneratorFactory;
|
||||
import org.jboss.logging.Logger;
|
||||
import org.keycloak.Config;
|
||||
import org.keycloak.connections.jpa.updater.liquibase.LiquibaseJpaUpdaterProvider;
|
||||
import org.keycloak.connections.jpa.updater.liquibase.PostgresPlusDatabase;
|
||||
import org.keycloak.connections.jpa.updater.liquibase.lock.CustomInsertLockRecordGenerator;
|
||||
import org.keycloak.connections.jpa.updater.liquibase.lock.CustomLockDatabaseChangeLogGenerator;
|
||||
import org.keycloak.connections.jpa.updater.liquibase.lock.CustomLockService;
|
||||
import org.keycloak.connections.jpa.updater.liquibase.lock.DummyLockService;
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
import org.keycloak.models.KeycloakSessionFactory;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||
|
@ -54,8 +62,11 @@ public class DefaultLiquibaseConnectionProvider implements LiquibaseConnectionPr
|
|||
|
||||
private volatile boolean initialized = false;
|
||||
|
||||
private KeycloakSession keycloakSession;
|
||||
|
||||
@Override
|
||||
public LiquibaseConnectionProvider create(KeycloakSession session) {
|
||||
this.keycloakSession = session;
|
||||
if (!initialized) {
|
||||
synchronized (this) {
|
||||
if (!initialized) {
|
||||
|
@ -134,7 +145,61 @@ public class DefaultLiquibaseConnectionProvider implements LiquibaseConnectionPr
|
|||
String changelog = (database instanceof DB2Database) ? LiquibaseJpaUpdaterProvider.DB2_CHANGELOG : LiquibaseJpaUpdaterProvider.CHANGELOG;
|
||||
logger.debugf("Using changelog file: %s", changelog);
|
||||
|
||||
return new Liquibase(changelog, new ClassLoaderResourceAccessor(getClass().getClassLoader()), database);
|
||||
ResourceAccessor resourceAccessor = new ClassLoaderResourceAccessor(getClass().getClassLoader());
|
||||
DatabaseChangeLog databaseChangeLog = generateDynamicChangeLog(changelog, resourceAccessor, database);
|
||||
|
||||
return new Liquibase(databaseChangeLog, resourceAccessor, database);
|
||||
}
|
||||
|
||||
/**
|
||||
* We want to be able to provide extra changesets as an extension to the Keycloak data model.
|
||||
* But we do not want users to be able to not execute certain parts of the Keycloak internal data model.
|
||||
* Therefore, we generate a dynamic changelog here that always contains the keycloak changelog file
|
||||
* and optionally include the user extension changelog files.
|
||||
*
|
||||
* @param changelog the changelog file location
|
||||
* @param resourceAccessor the resource accessor
|
||||
* @param database the database
|
||||
* @return
|
||||
*/
|
||||
private DatabaseChangeLog generateDynamicChangeLog(String changelog, ResourceAccessor resourceAccessor, Database database) throws LiquibaseException {
|
||||
ChangeLogParameters changeLogParameters = new ChangeLogParameters(database);
|
||||
ChangeLogParser parser = ChangeLogParserFactory.getInstance().getParser(changelog, resourceAccessor);
|
||||
DatabaseChangeLog keycloakDatabaseChangeLog = parser.parse(changelog, changeLogParameters, resourceAccessor);
|
||||
|
||||
List<String> locations = new ArrayList<>();
|
||||
Set<JpaEntityProvider> entityProviders = keycloakSession.getAllProviders(JpaEntityProvider.class);
|
||||
for (JpaEntityProvider entityProvider : entityProviders) {
|
||||
String location = entityProvider.getChangelogLocation();
|
||||
if (location != null) {
|
||||
locations.add(location);
|
||||
}
|
||||
}
|
||||
|
||||
final DatabaseChangeLog dynamicMasterChangeLog;
|
||||
if (locations.isEmpty()) {
|
||||
// If there are no extra changelog locations, we'll just use the keycloak one.
|
||||
dynamicMasterChangeLog = keycloakDatabaseChangeLog;
|
||||
} else {
|
||||
// A change log is essentially not much more than a (big) collection of changesets.
|
||||
// The original (file) destination is not important. So we can just make one big dynamic change log that include all changesets.
|
||||
dynamicMasterChangeLog = new DatabaseChangeLog();
|
||||
dynamicMasterChangeLog.setChangeLogParameters(changeLogParameters);
|
||||
for (ChangeSet changeSet : keycloakDatabaseChangeLog.getChangeSets()) {
|
||||
dynamicMasterChangeLog.addChangeSet(changeSet);
|
||||
}
|
||||
ProxyClassLoader proxyClassLoader = new ProxyClassLoader(JpaUtils.getProvidedEntities(keycloakSession));
|
||||
for (String location : locations) {
|
||||
ResourceAccessor proxyResourceAccessor = new ClassLoaderResourceAccessor(proxyClassLoader);
|
||||
ChangeLogParser locationParser = ChangeLogParserFactory.getInstance().getParser(location, proxyResourceAccessor);
|
||||
DatabaseChangeLog locationDatabaseChangeLog = locationParser.parse(location, changeLogParameters, proxyResourceAccessor);
|
||||
for (ChangeSet changeSet : locationDatabaseChangeLog.getChangeSets()) {
|
||||
dynamicMasterChangeLog.addChangeSet(changeSet);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return dynamicMasterChangeLog;
|
||||
}
|
||||
|
||||
private static class LogWrapper extends LogFactory {
|
||||
|
|
|
@ -21,12 +21,18 @@ import org.hibernate.boot.registry.classloading.internal.ClassLoaderServiceImpl;
|
|||
import org.hibernate.jpa.boot.internal.ParsedPersistenceXmlDescriptor;
|
||||
import org.hibernate.jpa.boot.internal.PersistenceXmlParser;
|
||||
import org.hibernate.jpa.boot.spi.Bootstrap;
|
||||
import org.keycloak.connections.jpa.entityprovider.JpaEntityProvider;
|
||||
import org.keycloak.connections.jpa.entityprovider.ProxyClassLoader;
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
|
||||
import javax.persistence.EntityManager;
|
||||
import javax.persistence.EntityManagerFactory;
|
||||
import javax.persistence.spi.PersistenceUnitTransactionType;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||
|
@ -40,14 +46,40 @@ public class JpaUtils {
|
|||
return (schema==null) ? tableName : schema + "." + tableName;
|
||||
}
|
||||
|
||||
public static EntityManagerFactory createEntityManagerFactory(String unitName, Map<String, Object> properties, ClassLoader classLoader) {
|
||||
public static EntityManagerFactory createEntityManagerFactory(KeycloakSession session, String unitName, Map<String, Object> properties, ClassLoader classLoader) {
|
||||
PersistenceXmlParser parser = new PersistenceXmlParser(new ClassLoaderServiceImpl(classLoader), PersistenceUnitTransactionType.RESOURCE_LOCAL);
|
||||
List<ParsedPersistenceXmlDescriptor> persistenceUnits = parser.doResolve(properties);
|
||||
for (ParsedPersistenceXmlDescriptor persistenceUnit : persistenceUnits) {
|
||||
if (persistenceUnit.getName().equals(unitName)) {
|
||||
return Bootstrap.getEntityManagerFactoryBuilder(persistenceUnit, properties, classLoader).build();
|
||||
List<Class<?>> providedEntities = getProvidedEntities(session);
|
||||
for (Class<?> entityClass : providedEntities) {
|
||||
// Add all extra entity classes to the persistence unit.
|
||||
persistenceUnit.addClasses(entityClass.getName());
|
||||
}
|
||||
// Now build the entity manager factory, supplying a proxy classloader, so Hibernate will be able
|
||||
// to find and load the extra provided entities. Set the provided classloader as parent classloader.
|
||||
return Bootstrap.getEntityManagerFactoryBuilder(persistenceUnit, properties,
|
||||
new ProxyClassLoader(providedEntities, classLoader)).build();
|
||||
}
|
||||
}
|
||||
throw new RuntimeException("Persistence unit '" + unitName + "' not found");
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a list of all provided entities by looping over all configured entity providers.
|
||||
*
|
||||
* @param session the keycloak session
|
||||
* @return a list of all provided entities (can be an empty list)
|
||||
*/
|
||||
public static List<Class<?>> getProvidedEntities(KeycloakSession session) {
|
||||
List<Class<?>> providedEntityClasses = new ArrayList<>();
|
||||
// Get all configured entity providers.
|
||||
Set<JpaEntityProvider> entityProviders = session.getAllProviders(JpaEntityProvider.class);
|
||||
// For every provider, add all entity classes to the list.
|
||||
for (JpaEntityProvider entityProvider : entityProviders) {
|
||||
providedEntityClasses.addAll(entityProvider.getEntities());
|
||||
}
|
||||
return providedEntityClasses;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -17,4 +17,5 @@
|
|||
|
||||
org.keycloak.connections.jpa.JpaConnectionSpi
|
||||
org.keycloak.connections.jpa.updater.JpaUpdaterSpi
|
||||
org.keycloak.connections.jpa.updater.liquibase.conn.LiquibaseConnectionSpi
|
||||
org.keycloak.connections.jpa.updater.liquibase.conn.LiquibaseConnectionSpi
|
||||
org.keycloak.connections.jpa.entityprovider.JpaEntitySpi
|
||||
|
|
|
@ -24,6 +24,19 @@ import java.util.List;
|
|||
*/
|
||||
public interface ProviderLoader {
|
||||
|
||||
/**
|
||||
* Load the SPI definitions themselves.
|
||||
*
|
||||
* @return a list of Spi definition objects
|
||||
*/
|
||||
List<Spi> loadSpis();
|
||||
|
||||
/**
|
||||
* Load all provider factories of a specific SPI.
|
||||
*
|
||||
* @param spi the Spi definition
|
||||
* @return a list of provider factories
|
||||
*/
|
||||
List<ProviderFactory> load(Spi spi);
|
||||
|
||||
}
|
||||
|
|
|
@ -32,6 +32,15 @@ public class DefaultProviderLoader implements ProviderLoader {
|
|||
this.classLoader = classLoader;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Spi> loadSpis() {
|
||||
LinkedList<Spi> list = new LinkedList<>();
|
||||
for (Spi spi : ServiceLoader.load(Spi.class, classLoader)) {
|
||||
list.add(spi);
|
||||
}
|
||||
return list;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<ProviderFactory> load(Spi spi) {
|
||||
LinkedList<ProviderFactory> list = new LinkedList<ProviderFactory>();
|
||||
|
|
|
@ -65,6 +65,20 @@ public class ProviderManager {
|
|||
}
|
||||
}
|
||||
|
||||
public synchronized List<Spi> loadSpis() {
|
||||
// Use a map to prevent duplicates, since the loaders may have overlapping classpaths.
|
||||
Map<String, Spi> spiMap = new HashMap<>();
|
||||
for (ProviderLoader loader : loaders) {
|
||||
List<Spi> spis = loader.loadSpis();
|
||||
if (spis != null) {
|
||||
for (Spi spi : spis) {
|
||||
spiMap.put(spi.getName(), spi);
|
||||
}
|
||||
}
|
||||
}
|
||||
return new LinkedList<>(spiMap.values());
|
||||
}
|
||||
|
||||
public synchronized List<ProviderFactory> load(Spi spi) {
|
||||
List<ProviderFactory> factories = cache.get(spi.getName());
|
||||
if (factories == null) {
|
||||
|
|
|
@ -70,8 +70,9 @@ public class DefaultKeycloakSessionFactory implements KeycloakSessionFactory {
|
|||
|
||||
ProviderManager pm = new ProviderManager(getClass().getClassLoader(), Config.scope().getArray("providers"));
|
||||
|
||||
ServiceLoader<Spi> load = ServiceLoader.load(Spi.class, getClass().getClassLoader());
|
||||
loadSPIs(pm, load);
|
||||
// Load the SPI classes through the provider manager, so both Keycloak internal SPI's and
|
||||
// the ones defined in deployed modules will be found.
|
||||
loadSPIs(pm, pm.loadSpis());
|
||||
for ( Map<String, ProviderFactory> factories : factoriesMap.values()) {
|
||||
for (ProviderFactory factory : factories.values()) {
|
||||
factory.postInit(this);
|
||||
|
@ -79,8 +80,8 @@ public class DefaultKeycloakSessionFactory implements KeycloakSessionFactory {
|
|||
}
|
||||
}
|
||||
|
||||
protected void loadSPIs(ProviderManager pm, ServiceLoader<Spi> load) {
|
||||
for (Spi spi : load) {
|
||||
protected void loadSPIs(ProviderManager pm, List<Spi> spiList) {
|
||||
for (Spi spi : spiList) {
|
||||
spis.add(spi);
|
||||
|
||||
Map<String, ProviderFactory> factories = new HashMap<String, ProviderFactory>();
|
||||
|
|
|
@ -101,7 +101,7 @@ public class RealmsResource {
|
|||
|
||||
@Path("{realm}/protocol/{protocol}")
|
||||
public Object getProtocol(final @PathParam("realm") String name,
|
||||
final @PathParam("protocol") String protocol) {
|
||||
final @PathParam("protocol") String protocol) {
|
||||
RealmModel realm = init(name);
|
||||
|
||||
LoginProtocolFactory factory = (LoginProtocolFactory)session.getKeycloakSessionFactory().getProviderFactory(LoginProtocol.class, protocol);
|
||||
|
@ -239,7 +239,7 @@ public class RealmsResource {
|
|||
@Path("{realm}/.well-known/{provider}")
|
||||
@Produces(MediaType.APPLICATION_JSON)
|
||||
public Response getWellKnown(final @PathParam("realm") String name,
|
||||
final @PathParam("provider") String providerName) {
|
||||
final @PathParam("provider") String providerName) {
|
||||
init(name);
|
||||
|
||||
WellKnownProvider wellKnown = session.getProvider(WellKnownProvider.class, providerName);
|
||||
|
|
|
@ -496,9 +496,9 @@ public class RealmAdminResource {
|
|||
@NoCache
|
||||
@Produces(MediaType.APPLICATION_JSON)
|
||||
public List<EventRepresentation> getEvents(@QueryParam("type") List<String> types, @QueryParam("client") String client,
|
||||
@QueryParam("user") String user, @QueryParam("dateFrom") String dateFrom, @QueryParam("dateTo") String dateTo,
|
||||
@QueryParam("ipAddress") String ipAddress, @QueryParam("first") Integer firstResult,
|
||||
@QueryParam("max") Integer maxResults) {
|
||||
@QueryParam("user") String user, @QueryParam("dateFrom") String dateFrom, @QueryParam("dateTo") String dateTo,
|
||||
@QueryParam("ipAddress") String ipAddress, @QueryParam("first") Integer firstResult,
|
||||
@QueryParam("max") Integer maxResults) {
|
||||
auth.init(RealmAuth.Resource.EVENTS).requireView();
|
||||
|
||||
EventStoreProvider eventStore = session.getProvider(EventStoreProvider.class);
|
||||
|
@ -585,10 +585,10 @@ public class RealmAdminResource {
|
|||
@NoCache
|
||||
@Produces(MediaType.APPLICATION_JSON)
|
||||
public List<AdminEventRepresentation> getEvents(@QueryParam("operationTypes") List<String> operationTypes, @QueryParam("authRealm") String authRealm, @QueryParam("authClient") String authClient,
|
||||
@QueryParam("authUser") String authUser, @QueryParam("authIpAddress") String authIpAddress,
|
||||
@QueryParam("resourcePath") String resourcePath, @QueryParam("dateFrom") String dateFrom,
|
||||
@QueryParam("dateTo") String dateTo, @QueryParam("first") Integer firstResult,
|
||||
@QueryParam("max") Integer maxResults) {
|
||||
@QueryParam("authUser") String authUser, @QueryParam("authIpAddress") String authIpAddress,
|
||||
@QueryParam("resourcePath") String resourcePath, @QueryParam("dateFrom") String dateFrom,
|
||||
@QueryParam("dateTo") String dateTo, @QueryParam("first") Integer firstResult,
|
||||
@QueryParam("max") Integer maxResults) {
|
||||
auth.init(RealmAuth.Resource.EVENTS).requireView();
|
||||
|
||||
EventStoreProvider eventStore = session.getProvider(EventStoreProvider.class);
|
||||
|
|
|
@ -18,3 +18,4 @@
|
|||
org.keycloak.exportimport.ClientDescriptionConverterSpi
|
||||
org.keycloak.wellknown.WellKnownSpi
|
||||
org.keycloak.services.clientregistration.ClientRegistrationSpi
|
||||
|
||||
|
|
Loading…
Reference in a new issue