[KEYCLOAK-19274] - Avoid loading queries from properties at runtime for Dist.X

This commit is contained in:
Pedro Igor 2021-09-15 10:19:03 -03:00 committed by Hynek Mlnařík
parent 339224578e
commit 10e425315f
4 changed files with 95 additions and 47 deletions

View file

@ -17,6 +17,10 @@
package org.keycloak.connections.jpa;
import static org.keycloak.connections.jpa.util.JpaUtils.configureNamedQuery;
import static org.keycloak.connections.jpa.util.JpaUtils.getDatabaseType;
import static org.keycloak.connections.jpa.util.JpaUtils.loadSpecificNamedQueries;
import org.hibernate.cfg.AvailableSettings;
import org.hibernate.engine.transaction.jta.platform.internal.AbstractJtaPlatform;
import org.jboss.logging.Logger;
@ -25,7 +29,6 @@ import org.keycloak.ServerStartupError;
import org.keycloak.common.util.StackUtil;
import org.keycloak.common.util.StringPropertyReplacer;
import org.keycloak.connections.jpa.updater.JpaUpdaterProvider;
import org.keycloak.connections.jpa.updater.liquibase.conn.LiquibaseConnectionProvider;
import org.keycloak.connections.jpa.util.JpaUtils;
import org.keycloak.migration.MigrationModelManager;
import org.keycloak.models.KeycloakSession;
@ -55,8 +58,6 @@ import java.util.Collection;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Map;
import liquibase.Liquibase;
import liquibase.exception.LiquibaseException;
/**
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
@ -105,13 +106,16 @@ public class DefaultJpaConnectionProviderFactory implements JpaConnectionProvide
}
private void addSpecificNamedQueries(KeycloakSession session, Connection connection) {
LiquibaseConnectionProvider liquibaseProvider = session.getProvider(LiquibaseConnectionProvider.class);
EntityManager em = null;
try {
Liquibase liquibase = liquibaseProvider.getLiquibase(connection, this.getSchema());
em = createEntityManager(session);
JpaUtils.addSpecificNamedQueries(em, liquibase.getDatabase().getShortName());
} catch (LiquibaseException e) {
String dbKind = getDatabaseType(connection.getMetaData().getDatabaseProductName());
for (Map.Entry<Object, Object> query : loadSpecificNamedQueries(dbKind.toLowerCase()).entrySet()) {
String queryName = query.getKey().toString();
String querySql = query.getValue().toString();
configureNamedQuery(queryName, querySql, em);
}
} catch (SQLException e) {
throw new IllegalStateException(e);
} finally {
JpaUtils.closeEntityManager(em);

View file

@ -17,9 +17,9 @@
package org.keycloak.connections.jpa.util;
import org.jboss.logging.Logger;
import org.hibernate.engine.query.spi.sql.NativeSQLQueryReturn;
import org.hibernate.engine.query.spi.sql.NativeSQLQuerySpecification;
import org.jboss.logging.Logger;
import org.hibernate.engine.spi.SessionFactoryImplementor;
import org.hibernate.jpa.boot.internal.ParsedPersistenceXmlDescriptor;
import org.hibernate.jpa.boot.internal.PersistenceXmlParser;
@ -110,7 +110,7 @@ public class JpaUtils {
* @param url The url to load, it can be null
* @return A properties file with the url loaded or null
*/
private static Properties loadSqlProperties(URL url) {
public static Properties loadSqlProperties(URL url) {
if (url == null) {
return null;
}
@ -174,42 +174,72 @@ public class JpaUtils {
* that database type.
* @param em The entity manager to use
* @param databaseType The database type as managed in
* <a href="https://www.liquibase.org/get-started/databases">liquibase</a>.
* @return
*/
public static void addSpecificNamedQueries(EntityManager em, String databaseType) {
final SessionFactoryImplementor sfi = em.getEntityManagerFactory().unwrap(SessionFactoryImplementor.class);
public static Properties loadSpecificNamedQueries(String databaseType) {
URL specificUrl = JpaUtils.class.getClassLoader().getResource("META-INF/queries-" + databaseType + ".properties");
URL defaultUrl = JpaUtils.class.getClassLoader().getResource("META-INF/queries-default.properties");
if (defaultUrl == null) {
throw new IllegalStateException("META-INF/queries-default.properties was not found in the classpath");
}
Properties specificQueries = loadSqlProperties(specificUrl);
Properties defaultQueries = loadSqlProperties(defaultUrl);
Properties queries = new Properties();
for (String queryNameFull : defaultQueries.stringPropertyNames()) {
String querySql;
String querySql = defaultQueries.getProperty(queryNameFull);
String queryName = getQueryShortName(queryNameFull);
String specificQueryNameFull = getQueryFromProperties(queryName, specificQueries);
if (specificQueryNameFull != null) {
// the query is redefined in the specific database file => use it
queryNameFull = specificQueryNameFull;
querySql = specificQueries.getProperty(queryNameFull);
} else {
// use the default query sql
querySql = defaultQueries.getProperty(queryNameFull);
}
boolean isNative = queryNameFull.endsWith(QUERY_NATIVE_SUFFIX);
queries.put(queryNameFull, querySql);
}
return queries;
}
/**
* Configures a named query to Hibernate.
*
* @param queryName the query name
* @param querySql the query SQL
* @param entityManager the entity manager
*/
public static void configureNamedQuery(String queryName, String querySql, EntityManager entityManager) {
boolean isNative = queryName.endsWith(QUERY_NATIVE_SUFFIX);
queryName = getQueryShortName(queryName);
logger.tracef("adding query from properties files native=%b %s:%s", isNative, queryName, querySql);
SessionFactoryImplementor sessionFactory = entityManager.getEntityManagerFactory().unwrap(SessionFactoryImplementor.class);
if (isNative) {
NativeSQLQuerySpecification spec = new NativeSQLQuerySpecification(querySql, new NativeSQLQueryReturn[0], Collections.emptySet());
sfi.getQueryPlanCache().getNativeSQLQueryPlan(spec);
em.getEntityManagerFactory().addNamedQuery(queryName, em.createNativeQuery(querySql));
sessionFactory.getQueryPlanCache().getNativeSQLQueryPlan(spec);
sessionFactory.addNamedQuery(queryName, entityManager.createNativeQuery(querySql));
} else {
sfi.getQueryPlanCache().getHQLQueryPlan(querySql, false, Collections.emptyMap());
em.getEntityManagerFactory().addNamedQuery(queryName, em.createQuery(querySql));
sessionFactory.getQueryPlanCache().getHQLQueryPlan(querySql, false, Collections.emptyMap());
sessionFactory.addNamedQuery(queryName, entityManager.createQuery(querySql));
}
}
public static String getDatabaseType(String productName) {
switch (productName) {
case "Microsoft SQL Server":
case "SQLOLEDB":
return "mssql";
case "EnterpriseDB":
return "postgresql";
default:
return productName.toLowerCase();
}
}
/**

View file

@ -20,6 +20,8 @@ package org.keycloak.quarkus.deployment;
import static java.util.Collections.emptyList;
import static org.keycloak.configuration.Configuration.getPropertyNames;
import static org.keycloak.configuration.Configuration.getRawValue;
import static org.keycloak.connections.jpa.QuarkusJpaConnectionProviderFactory.QUERY_PROPERTY_PREFIX;
import static org.keycloak.connections.jpa.util.JpaUtils.loadSpecificNamedQueries;
import static org.keycloak.representations.provider.ScriptProviderDescriptor.AUTHENTICATORS;
import static org.keycloak.representations.provider.ScriptProviderDescriptor.MAPPERS;
import static org.keycloak.representations.provider.ScriptProviderDescriptor.POLICIES;
@ -38,15 +40,15 @@ import java.util.Enumeration;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.Map.Entry;
import java.util.Optional;
import java.util.Properties;
import java.util.ServiceLoader;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
import io.quarkus.agroal.spi.JdbcDataSourceBuildItem;
import io.quarkus.deployment.IsDevelopment;
import io.quarkus.deployment.builditem.HotDeploymentWatchedFileBuildItem;
import io.quarkus.deployment.builditem.IndexDependencyBuildItem;
@ -72,13 +74,14 @@ import org.keycloak.authorization.policy.provider.PolicySpi;
import org.keycloak.authorization.policy.provider.js.DeployedScriptPolicyFactory;
import org.keycloak.common.Profile;
import org.keycloak.common.util.StreamUtil;
import org.keycloak.config.ConfigProviderFactory;
import org.keycloak.configuration.Configuration;
import org.keycloak.configuration.KeycloakConfigSourceProvider;
import org.keycloak.configuration.MicroProfileConfigProvider;
import org.keycloak.connections.jpa.DefaultJpaConnectionProviderFactory;
import org.keycloak.connections.jpa.QuarkusJpaConnectionProviderFactory;
import org.keycloak.connections.jpa.updater.liquibase.LiquibaseJpaUpdaterProviderFactory;
import org.keycloak.connections.jpa.updater.liquibase.conn.DefaultLiquibaseConnectionProvider;
import org.keycloak.connections.jpa.util.JpaUtils;
import org.keycloak.protocol.ProtocolMapperSpi;
import org.keycloak.protocol.oidc.mappers.DeployedScriptOIDCProtocolMapper;
import org.keycloak.provider.EnvironmentDependentProviderFactory;
@ -155,12 +158,20 @@ class KeycloakProcessor {
*/
@Record(ExecutionTime.STATIC_INIT)
@BuildStep
void configureHibernate(KeycloakRecorder recorder, HibernateOrmConfig config, List<PersistenceUnitDescriptorBuildItem> descriptors) {
void configureHibernate(KeycloakRecorder recorder, HibernateOrmConfig config, List<PersistenceUnitDescriptorBuildItem> descriptors,
List<JdbcDataSourceBuildItem> jdbcDataSources) {
PersistenceUnitDescriptor unit = descriptors.get(0).asOutputPersistenceUnitDefinition(emptyList()).getActualHibernateDescriptor();
Properties unitProperties = unit.getProperties();
unit.getProperties().setProperty(AvailableSettings.DIALECT, config.defaultPersistenceUnit.dialect.dialect.orElse(null));
unit.getProperties().setProperty(AvailableSettings.JPA_TRANSACTION_TYPE, PersistenceUnitTransactionType.JTA.name());
unit.getProperties().setProperty(AvailableSettings.QUERY_STARTUP_CHECKING, Boolean.FALSE.toString());
unitProperties.setProperty(AvailableSettings.DIALECT, config.defaultPersistenceUnit.dialect.dialect.orElse(null));
unitProperties.setProperty(AvailableSettings.JPA_TRANSACTION_TYPE, PersistenceUnitTransactionType.JTA.name());
unitProperties.setProperty(AvailableSettings.QUERY_STARTUP_CHECKING, Boolean.FALSE.toString());
String dbKind = jdbcDataSources.get(0).getDbKind();
for (Entry<Object, Object> query : loadSpecificNamedQueries(dbKind.toLowerCase()).entrySet()) {
unitProperties.setProperty(QUERY_PROPERTY_PREFIX + query.getKey(), query.getValue().toString());
}
}
/**
@ -179,11 +190,11 @@ class KeycloakProcessor {
Map<Class<? extends Provider>, String> defaultProviders = new HashMap<>();
Map<String, ProviderFactory> preConfiguredProviders = new HashMap<>();
for (Map.Entry<Spi, Map<Class<? extends Provider>, Map<String, ProviderFactory>>> entry : loadFactories(preConfiguredProviders)
for (Entry<Spi, Map<Class<? extends Provider>, Map<String, ProviderFactory>>> entry : loadFactories(preConfiguredProviders)
.entrySet()) {
checkProviders(entry.getKey(), entry.getValue(), defaultProviders);
for (Map.Entry<Class<? extends Provider>, Map<String, ProviderFactory>> value : entry.getValue().entrySet()) {
for (Entry<Class<? extends Provider>, Map<String, ProviderFactory>> value : entry.getValue().entrySet()) {
for (ProviderFactory factory : value.getValue().values()) {
factories.computeIfAbsent(entry.getKey(),
key -> new HashMap<>())
@ -393,7 +404,7 @@ class KeycloakProcessor {
descriptor = JsonSerialization.readValue(is, ScriptProviderDescriptor.class);
}
for (Map.Entry<String, List<ScriptProviderMetadata>> entry : descriptor.getProviders().entrySet()) {
for (Entry<String, List<ScriptProviderMetadata>> entry : descriptor.getProviders().entrySet()) {
if (isScriptForSpi(spi, entry.getKey())) {
for (ScriptProviderMetadata metadata : entry.getValue()) {
ProviderFactory provider = createDeployableScriptProvider(jarFile, entry, metadata);
@ -411,7 +422,7 @@ class KeycloakProcessor {
return providers;
}
private ProviderFactory createDeployableScriptProvider(JarFile jarFile, Map.Entry<String, List<ScriptProviderMetadata>> entry,
private ProviderFactory createDeployableScriptProvider(JarFile jarFile, Entry<String, List<ScriptProviderMetadata>> entry,
ScriptProviderMetadata metadata) throws IOException {
String fileName = metadata.getFileName();

View file

@ -17,6 +17,7 @@
package org.keycloak.connections.jpa;
import static org.keycloak.connections.jpa.util.JpaUtils.configureNamedQuery;
import static org.keycloak.connections.liquibase.QuarkusJpaUpdaterProvider.VERIFY_AND_RUN_MASTER_CHANGELOG;
import static org.keycloak.models.utils.KeycloakModelUtils.runJobInTransaction;
@ -28,6 +29,7 @@ import java.sql.DatabaseMetaData;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
@ -43,8 +45,8 @@ import javax.transaction.Transaction;
import com.fasterxml.jackson.core.type.TypeReference;
import io.quarkus.runtime.Quarkus;
import liquibase.Liquibase;
import liquibase.exception.LiquibaseException;
import org.hibernate.engine.spi.SessionFactoryImplementor;
import org.hibernate.internal.SessionFactoryImpl;
import org.jboss.logging.Logger;
import org.keycloak.Config;
@ -52,7 +54,6 @@ import org.keycloak.ServerStartupError;
import org.keycloak.common.Version;
import org.keycloak.connections.jpa.updater.JpaUpdaterProvider;
import org.keycloak.exportimport.ExportImportConfig;
import org.keycloak.connections.jpa.updater.liquibase.conn.LiquibaseConnectionProvider;
import org.keycloak.connections.jpa.util.JpaUtils;
import org.keycloak.exportimport.ExportImportManager;
import org.keycloak.migration.MigrationModelManager;
@ -80,6 +81,7 @@ import org.keycloak.util.JsonSerialization;
*/
public final class QuarkusJpaConnectionProviderFactory implements JpaConnectionProviderFactory, ServerInfoAwareProviderFactory {
public static final String QUERY_PROPERTY_PREFIX = "kc.query.";
private static final Logger logger = Logger.getLogger(QuarkusJpaConnectionProviderFactory.class);
private static final String SQL_GET_LATEST_VERSION = "SELECT VERSION FROM %sMIGRATION_MODEL";
@ -116,14 +118,15 @@ public final class QuarkusJpaConnectionProviderFactory implements JpaConnectionP
}
private void addSpecificNamedQueries(KeycloakSession session, Connection connection) {
LiquibaseConnectionProvider liquibaseProvider = session.getProvider(LiquibaseConnectionProvider.class);
EntityManager em = null;
SessionFactoryImplementor sfi = emf.unwrap(SessionFactoryImplementor.class);
EntityManager em = createEntityManager(session);
try {
Liquibase liquibase = liquibaseProvider.getLiquibase(connection, this.getSchema());
em = createEntityManager(session);
JpaUtils.addSpecificNamedQueries(em, liquibase.getDatabase().getShortName());
} catch (LiquibaseException e) {
throw new IllegalStateException(e);
Map<String, Object> unitProperties = emf.getProperties();
unitProperties.entrySet().stream()
.filter(entry -> entry.getKey().startsWith(QUERY_PROPERTY_PREFIX))
.forEach(entry -> configureNamedQuery(entry.getKey().substring(QUERY_PROPERTY_PREFIX.length()), entry.getValue().toString(), em));
} finally {
JpaUtils.closeEntityManager(em);
}