Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@
import java.util.Properties;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;

Expand All @@ -65,6 +66,7 @@
import io.quarkus.hibernate.orm.deployment.AdditionalJpaModelBuildItem;
import io.quarkus.hibernate.orm.deployment.HibernateOrmConfig;
import io.quarkus.hibernate.orm.deployment.PersistenceXmlDescriptorBuildItem;
import io.quarkus.hibernate.orm.deployment.integration.HibernateOrmIntegrationRuntimeConfiguredBuildItem;
import io.quarkus.resteasy.server.common.deployment.ResteasyDeploymentCustomizerBuildItem;
import io.quarkus.runtime.LaunchMode;
import io.quarkus.runtime.configuration.ProfileManager;
Expand All @@ -84,6 +86,8 @@
import org.jboss.resteasy.plugins.server.servlet.ResteasyContextParameters;
import org.jboss.resteasy.spi.ResteasyDeployment;
import org.keycloak.Config;
import org.keycloak.connections.jpa.JpaConnectionProvider;
import org.keycloak.connections.jpa.JpaConnectionSpi;
import org.keycloak.quarkus.runtime.QuarkusProfile;
import org.keycloak.quarkus.runtime.configuration.PersistedConfigSource;
import org.keycloak.quarkus.runtime.configuration.QuarkusPropertiesConfigSource;
Expand Down Expand Up @@ -121,6 +125,7 @@
import io.quarkus.deployment.builditem.FeatureBuildItem;
import io.quarkus.vertx.http.deployment.FilterBuildItem;

import org.keycloak.quarkus.runtime.storage.database.jpa.NamedJpaConnectionProviderFactory;
import org.keycloak.representations.provider.ScriptProviderDescriptor;
import org.keycloak.representations.provider.ScriptProviderMetadata;
import org.keycloak.quarkus.runtime.integration.web.NotFoundHandler;
Expand Down Expand Up @@ -191,14 +196,27 @@ FeatureBuildItem getFeature() {
* @param descriptors
*/
@BuildStep
void configureHibernate(HibernateOrmConfig config,
@Record(ExecutionTime.RUNTIME_INIT)
void configurePersistenceUnits(HibernateOrmConfig config,
List<PersistenceXmlDescriptorBuildItem> descriptors,
List<JdbcDataSourceBuildItem> jdbcDataSources,
BuildProducer<AdditionalJpaModelBuildItem> additionalJpaModel,
CombinedIndexBuildItem indexBuildItem) {
ParsedPersistenceXmlDescriptor descriptor = descriptors.get(0).getDescriptor();
configureJpaProperties(descriptor, config, jdbcDataSources);
configureJpaModel(descriptor, indexBuildItem);
CombinedIndexBuildItem indexBuildItem,
BuildProducer<HibernateOrmIntegrationRuntimeConfiguredBuildItem> runtimeConfigured,
KeycloakRecorder recorder) {
for (PersistenceXmlDescriptorBuildItem item : descriptors) {
ParsedPersistenceXmlDescriptor descriptor = item.getDescriptor();

if ("keycloak-default".equals(descriptor.getName())) {
configureJpaProperties(descriptor, config, jdbcDataSources);
configureJpaModel(descriptor, indexBuildItem);
} else {
Properties properties = descriptor.getProperties();
// register a listener for customizing the unit configuration at runtime
runtimeConfigured.produce(new HibernateOrmIntegrationRuntimeConfiguredBuildItem("keycloak", descriptor.getName())
.setInitListener(recorder.createUnitListener(properties.getProperty(AvailableSettings.DATASOURCE))));
}
}
}

private void configureJpaProperties(ParsedPersistenceXmlDescriptor descriptor, HibernateOrmConfig config,
Expand Down Expand Up @@ -245,30 +263,55 @@ private boolean isCustomJpaModel(String targetName) {
@Consume(RuntimeConfigSetupCompleteBuildItem.class)
@Record(ExecutionTime.RUNTIME_INIT)
@BuildStep
KeycloakSessionFactoryPreInitBuildItem configureProviders(KeycloakRecorder recorder) {
KeycloakSessionFactoryPreInitBuildItem configureProviders(KeycloakRecorder recorder, List<PersistenceXmlDescriptorBuildItem> descriptors) {
Profile.setInstance(new QuarkusProfile());
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<>();

for (Entry<Spi, Map<Class<? extends Provider>, Map<String, ProviderFactory>>> entry : loadFactories(preConfiguredProviders)
.entrySet()) {
checkProviders(entry.getKey(), entry.getValue(), defaultProviders);
Spi spi = entry.getKey();

checkProviders(spi, entry.getValue(), defaultProviders);

for (Entry<Class<? extends Provider>, Map<String, ProviderFactory>> value : entry.getValue().entrySet()) {
for (ProviderFactory factory : value.getValue().values()) {
factories.computeIfAbsent(entry.getKey(),
factories.computeIfAbsent(spi,
key -> new HashMap<>())
.computeIfAbsent(entry.getKey().getProviderClass(), aClass -> new HashMap<>()).put(factory.getId(),factory.getClass());
.computeIfAbsent(spi.getProviderClass(), aClass -> new HashMap<>()).put(factory.getId(),factory.getClass());
}
}

if (spi instanceof JpaConnectionSpi) {
configureUserDefinedPersistenceUnits(descriptors, factories, preConfiguredProviders, spi);
}
}

recorder.configSessionFactory(factories, defaultProviders, preConfiguredProviders, Environment.isRebuild());

return new KeycloakSessionFactoryPreInitBuildItem();
}

private void configureUserDefinedPersistenceUnits(List<PersistenceXmlDescriptorBuildItem> descriptors,
Map<Spi, Map<Class<? extends Provider>, Map<String, Class<? extends ProviderFactory>>>> factories,
Map<String, ProviderFactory> preConfiguredProviders, Spi spi) {
descriptors.stream()
.map(PersistenceXmlDescriptorBuildItem::getDescriptor)
.map(ParsedPersistenceXmlDescriptor::getName)
.filter(Predicate.not("keycloak-default"::equals)).forEach(new Consumer<String>() {
@Override
public void accept(String unitName) {
NamedJpaConnectionProviderFactory factory = new NamedJpaConnectionProviderFactory();

factory.setUnitName(unitName);

factories.get(spi).get(JpaConnectionProvider.class).put(unitName, NamedJpaConnectionProviderFactory.class);
preConfiguredProviders.put(unitName, factory);
}
});
}

/**
* Register the custom {@link org.eclipse.microprofile.config.spi.ConfigSource} implementations.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,19 +17,24 @@

package org.keycloak.quarkus.runtime;

import java.lang.annotation.Annotation;
import java.util.List;
import java.util.Map;
import java.util.function.BiConsumer;

import io.agroal.api.AgroalDataSource;
import io.quarkus.agroal.DataSource;
import io.quarkus.arc.Arc;
import io.quarkus.arc.InstanceHandle;
import io.quarkus.hibernate.orm.runtime.integration.HibernateOrmIntegrationRuntimeInitListener;
import liquibase.Scope;
import org.infinispan.configuration.parsing.ConfigurationBuilderHolder;
import org.infinispan.configuration.parsing.ParserRegistry;
import org.infinispan.jboss.marshalling.core.JBossUserMarshaller;

import org.hibernate.cfg.AvailableSettings;
import org.infinispan.manager.DefaultCacheManager;
import io.quarkus.smallrye.metrics.runtime.SmallRyeMetricsHandler;
import io.vertx.core.Handler;
import io.vertx.ext.web.RoutingContext;
import org.keycloak.common.Profile;
import org.keycloak.quarkus.runtime.configuration.Configuration;
import org.keycloak.quarkus.runtime.integration.QuarkusKeycloakSessionFactory;
import org.keycloak.quarkus.runtime.storage.database.liquibase.FastServiceLocator;
import org.keycloak.provider.Provider;
Expand Down Expand Up @@ -95,4 +100,23 @@ public Handler<RoutingContext> createMetricsHandler(String path) {
metricsHandler.setMetricsPath(path);
return metricsHandler;
}

public HibernateOrmIntegrationRuntimeInitListener createUnitListener(String name) {
return new HibernateOrmIntegrationRuntimeInitListener() {
@Override
public void contributeRuntimeProperties(BiConsumer<String, Object> propertyCollector) {
InstanceHandle<AgroalDataSource> instance = Arc.container().instance(
AgroalDataSource.class, new DataSource() {
@Override public Class<? extends Annotation> annotationType() {
return DataSource.class;
}

@Override public String value() {
return name;
}
});
propertyCollector.accept(AvailableSettings.DATASOURCE, instance.get());
}
};
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,6 @@
import java.util.Set;
import java.util.stream.Collectors;

import javax.enterprise.inject.Instance;
import javax.inject.Inject;
import javax.persistence.EntityManagerFactory;
import javax.ws.rs.ApplicationPath;

import org.keycloak.Config;
Expand All @@ -47,12 +44,8 @@ private static boolean filterSingletons(Object o) {
return !WelcomeResource.class.isInstance(o);
}

@Inject
Instance<EntityManagerFactory> entityManagerFactory;

@Override
protected void startup() {
forceEntityManagerInitialization();
initializeKeycloakSessionFactory();
setupScheduledTasks(sessionFactory);
createAdminUser();
Expand All @@ -76,12 +69,6 @@ private void initializeKeycloakSessionFactory() {
sessionFactory.publish(new PostMigrationEvent());
}

private void forceEntityManagerInitialization() {
// also forces an initialization of the entity manager so that providers don't need to wait for any initialization logic
// when first creating an entity manager
entityManagerFactory.get().createEntityManager().close();
}

private void createAdminUser() {
String adminUserName = System.getenv(KEYCLOAK_ADMIN_ENV_VAR);
String adminPassword = System.getenv(KEYCLOAK_ADMIN_PASSWORD_ENV_VAR);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
/*
* Copyright 2021 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.quarkus.runtime.storage.database.jpa;

import java.lang.annotation.Annotation;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.Optional;
import javax.enterprise.inject.Instance;
import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.EntityTransaction;
Comment thread
pedroigor marked this conversation as resolved.
import javax.persistence.SynchronizationType;
import org.hibernate.internal.SessionFactoryImpl;
import org.keycloak.Config;
import org.keycloak.connections.jpa.JpaConnectionProviderFactory;
import org.keycloak.connections.jpa.JpaKeycloakTransaction;
Comment thread
pedroigor marked this conversation as resolved.
import org.keycloak.connections.jpa.PersistenceExceptionConverter;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.KeycloakSessionFactory;
import org.keycloak.quarkus.runtime.configuration.Configuration;

import io.quarkus.arc.Arc;
import io.quarkus.hibernate.orm.PersistenceUnit;

public abstract class AbstractJpaConnectionProviderFactory implements JpaConnectionProviderFactory {

protected Config.Scope config;
protected Boolean xaEnabled;
protected EntityManagerFactory entityManagerFactory;

@Override
public Connection getConnection() {
SessionFactoryImpl entityManagerFactory = this.entityManagerFactory.unwrap(SessionFactoryImpl.class);

try {
return entityManagerFactory.getJdbcServices().getBootstrapJdbcConnectionAccess().obtainConnection();
} catch (SQLException cause) {
throw new RuntimeException("Failed to obtain JDBC connection", cause);
}
}

@Override
public String getSchema() {
return Configuration.getRawValue("kc.db-schema");
}

@Override
public void init(Config.Scope config) {
this.config = config;
xaEnabled = "xa".equals(Configuration.getRawValue("kc.transaction-xa-enabled"));
}

@Override
public void postInit(KeycloakSessionFactory factory) {
entityManagerFactory = getEntityManagerFactory();
}

@Override
public void close() {
if (entityManagerFactory != null) {
entityManagerFactory.close();
}
}

protected abstract EntityManagerFactory getEntityManagerFactory();

protected Optional<EntityManagerFactory> getEntityManagerFactory(String unitName) {
Instance<EntityManagerFactory> instance = Arc.container().select(EntityManagerFactory.class, new PersistenceUnit() {

@Override
public Class<? extends Annotation> annotationType() {
return PersistenceUnit.class;
}

@Override
public String value() {
return unitName;
}
});

if (instance.isResolvable()) {
return Optional.of(instance.get());
}

return Optional.empty();
}

protected EntityManager createEntityManager(EntityManagerFactory emf, KeycloakSession session) {
EntityManager entityManager;

if (xaEnabled) {
entityManager = PersistenceExceptionConverter.create(session, emf.createEntityManager(SynchronizationType.SYNCHRONIZED));
} else {
entityManager = PersistenceExceptionConverter.create(session, emf.createEntityManager());
}

return entityManager;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
/*
* Copyright 2021 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.quarkus.runtime.storage.database.jpa;

import java.util.function.Supplier;
import javax.persistence.EntityManagerFactory;
import org.keycloak.connections.jpa.DefaultJpaConnectionProvider;
import org.keycloak.connections.jpa.JpaConnectionProvider;
import org.keycloak.models.KeycloakSession;

public final class NamedJpaConnectionProviderFactory extends AbstractJpaConnectionProviderFactory {

private String unitName;

@Override
public JpaConnectionProvider create(KeycloakSession session) {
return new DefaultJpaConnectionProvider(createEntityManager(entityManagerFactory, session));
}

@Override
protected EntityManagerFactory getEntityManagerFactory() {
return getEntityManagerFactory(unitName).orElseThrow(new Supplier<IllegalStateException>() {
@Override
public IllegalStateException get() {
return new IllegalStateException("Could not resolve named EntityManagerFactory [" + unitName + "]");
}
});
}

public String getUnitName() {
return unitName;
}

public void setUnitName(String unitName) {
this.unitName = unitName;
}

@Override
public String getId() {
return unitName;
}
}
Loading