[KEYCLOAK-19274] - Avoid loading queries from properties at runtime for Dist.X
This commit is contained in:
parent
339224578e
commit
10e425315f
4 changed files with 95 additions and 47 deletions
|
@ -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);
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -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();
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue