Reputation: 35
We are investigating a migration of Keycloak 13.0.1 to Keycloak 20.0.1. Our application stores users on a external postgresql database and exposes them to Keycloak via a custom UserStorageProvider. In Keycloak 20 I am unable to get a functioning EntityManagerFactory or EntityManager to power our UserStorageProvider.
CustomUserStorageProviderFactory:
public class CustomUserStorageProviderFactory implements UserStorageProviderFactory<CustomUserStorageProvider> {
private static final String PROVIDER_ID = "CUSTOM_ID";
@Override
public CustomUserStorageProvider create(KeycloakSession session, ComponentModel model) {
try {
log.info("Intializing user federation provider.");
EntityManagerFactory factory = EntityManagerFactoryHelper.getFactory();
CustomUserStorageProvider provider = new CustomUserStorageProvider(session, model, factory.createEntityManager());
return provider;
} catch (Exception e) {
throw new RuntimeException(e);
}
}
@Override
public String getId() {
return PROVIDER_ID;
}
}
EntityManagerFactoryHelper:
public class EntityManagerFactoryHelper {
private static String PERSISTENCE_UNIT_NAME = "customunit";
private static EntityManagerFactory factory;
static {
try {
factory = Persistence.createEntityManagerFactory(PERSISTENCE_UNIT_NAME);
} catch (Exception e) {
e.printStackTrace();
throw new RuntimeException(e);
}
}
public static EntityManagerFactory getFactory() {
return factory;
}
}
persistence.xml:
<persistence xmlns="http://java.sun.com/xml/ns/persistence"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/persistence http://java.sun.com/xml/ns/persistence/persistence_1_0.xsd"
version="1.0">
<persistence-unit name="customunit">
<class>my.custom.Class</class>
<exclude-unlisted-classes>true</exclude-unlisted-classes>
<properties>
<property name="javax.persistence.jdbc.url" value="jdbc:postgresql://database_url:5432/database_name" />
<property name="javax.persistence.jdbc.user" value="user" />
<property name="javax.persistence.jdbc.password" value="password" />
<property name="javax.persistence.provider" value="org.hibernate.jpa.HibernatePersistenceProvider" />
<property name="javax.persistence.jdbc.driver" value="org.postgresql.Driver" />
<property name="hibernate.dialect" value="org.hibernate.dialect.PostgreSQLDialect" />
<property name="hibernate.hbm2ddl.auto" value="none" />
<property name="hibernate.show_sql" value="true" />
<property name="hibernate.format_sql" value="true" />
</properties>
</persistence-unit>
</persistence>
My custom provider code is packaged as a JAR and placed in the 'providers' folder
When the CustomUserStorageProviderFactory is initialized, the following error is thrown:
java.lang.NullPointerException
at io.quarkus.hibernate.orm.runtime.FastBootHibernatePersistenceProvider.getEntityManagerFactoryBuilderOrNull(FastBootHibernatePersistenceProvider.java:172)
at io.quarkus.hibernate.orm.runtime.FastBootHibernatePersistenceProvider.createEntityManagerFactory(FastBootHibernatePersistenceProvider.java:66)
at javax.persistence.Persistence.createEntityManagerFactory(Persistence.java:80)
at javax.persistence.Persistence.createEntityManagerFactory(Persistence.java:55)
at my.package.EntityManagerFactoryHelper.<clinit>(EntityManagerFactoryHelper.java:14)
at my.package.CustomUserStorageProviderFactory.create(CustomUserStorageProviderFactory.java:22)
at my.package.CustomUserStorageProviderFactory.create(CustomUserStorageProviderFactory.java:10)
I have also tried configuring the data source in quarkus.properties inside the conf directory and then referencing the data source in persistence.xml but the results are the same.
The source of the NullPointer seems to be the following lines in FastBootHibernatePersistenceProvider. RecordedState is null here:
RecordedState recordedState = PersistenceUnitsHolder.popRecordedState(persistenceUnitName);
if (recordedState.isReactive()) {
throw new IllegalStateException(
"Attempting to boot a blocking Hibernate ORM instance on a reactive RecordedState");
}
When TRACE logs are enabled, during keycloak server startup, I can see that this same code executes without any problems or NullPointers in the JPA Startup Thread. A connection is opened to the configured database, and the Entities are inspected and registered correctly. It's only when I attempt to initialize the custom provider and retrieve the EntityManagerFactory that the error appears.
What is missing from the configuration to obtain a custom Entity Manager for Keycloak 20?
Upvotes: 0
Views: 1041
Reputation: 35
Figured out a solution that works. The key was resolving the CustomUserStorageProvider via javax.enterprise.inject.spi.CDI and using @Inject and @PersistenceUnit to autowire the EntityManager.
CustomUserStorageProviderFactory:
@Override
public CustomUserStorageProvider create(KeycloakSession session, ComponentModel model) {
try {
log.info("Intializing user federation provider.");
CustomUserStorageProvider provider = CDI.current().select(CustomUserStorageProvider.class).get();
provider.setComponentModel(model);
provider.setSession(session);
return provider;
} catch (Exception e) {
log.info("Error initializing user federation provider.", e);
e.printStackTrace();
throw new RuntimeException(e);
}
}
CustomUserStorageProvider:
@RequestScoped
@Unremovable
public class CustomUserStorageProvider implements UserStorageProvider, UserLookupProvider, UserQueryProvider, UserGroupMembershipFederatedStorage {
private KeycloakSession session;
private ComponentModel componentModel;
@Inject
@PersistenceUnit("customunit")
private EntityManager entityManager;
...
}
Upvotes: 0