From 4ef5f47c095f88d9ef801a8e2e495c2133431fa1 Mon Sep 17 00:00:00 2001 From: Steve Hawkins Date: Tue, 24 Mar 2026 08:25:36 -0400 Subject: [PATCH 1/4] fix: introduces the concept of a synthetic option closes: #47385 Signed-off-by: Steve Hawkins # Conflicts: # quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/configuration/mappers/DatabasePropertyMappers.java # quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/configuration/mappers/PropertyMapper.java # quarkus/runtime/src/test/java/org/keycloak/quarkus/runtime/configuration/ConfigurationTest.java --- .../org/keycloak/config/DatabaseOptions.java | 101 +------------- .../main/java/org/keycloak/config/Option.java | 11 +- .../org/keycloak/config/OptionBuilder.java | 9 +- .../keycloak/quarkus/runtime/cli/Picocli.java | 9 +- .../mappers/DatabasePropertyMappers.java | 81 ++++------- .../mappers/LoggingPropertyMappers.java | 13 +- .../configuration/mappers/PropertyMapper.java | 32 ++--- .../mappers/PropertyMappers.java | 7 +- .../mappers/WildcardPropertyMapper.java | 2 +- .../AbstractConfigurationTest.java | 2 + .../configuration/ConfigurationTest.java | 130 +++++------------- 11 files changed, 123 insertions(+), 274 deletions(-) diff --git a/quarkus/config-api/src/main/java/org/keycloak/config/DatabaseOptions.java b/quarkus/config-api/src/main/java/org/keycloak/config/DatabaseOptions.java index 54ef24b373eb..5fa70520e3d6 100644 --- a/quarkus/config-api/src/main/java/org/keycloak/config/DatabaseOptions.java +++ b/quarkus/config-api/src/main/java/org/keycloak/config/DatabaseOptions.java @@ -154,90 +154,16 @@ public class DatabaseOptions { .description("The type of the truststore file. Common values include 'JKS' (Java KeyStore) and 'PKCS12'. If not specified, it uses the driver's default.") .build(); - // TLS hidden options, per vendor, to configure TLS in the driver public static final Option DB_ORACLE_TLS_TRANSPORT = new OptionBuilder<>("db-oracle-protocol", String.class) .hidden() .build(); - public static final Option DB_PROPERTY_SSL_SERVER_DN_MATCH = new OptionBuilder<>("db-property-ssl-server-dn-match", String.class) - .hidden() - .build(); - public static final Option DB_PROPERTY_ENCRYPT = new OptionBuilder<>("db-property-encrypt", String.class) - .hidden() - .build(); - public static final Option DB_PROPERTY_TRUST_SERVER_CERTIFICATE = new OptionBuilder<>("db-property-trust-server-certificate", String.class) - .hidden() - .build(); - public static final Option DB_PROPERTY_SSLFACTORY = new OptionBuilder<>("db-property-sslfactory", String.class) - .hidden() - .build(); - public static final Option DB_PROPERTY_SSLMODE = new OptionBuilder<>("db-property-sslmode", String.class) - .hidden() - .build(); - public static final Option DB_PROPERTY_SSL_MODE = new OptionBuilder<>("db-property-sslMode", String.class) - .hidden() - .build(); - public static final Option DB_PROPERTY_SSLROOTCERT = new OptionBuilder<>("db-property-sslrootcert", String.class) - .hidden() - .build(); - public static final Option DB_PROPERTY_TRUST_CERTIFICATE_KEY_STORE_URL = new OptionBuilder<>("db-property-trustCertificateKeyStoreUrl", String.class) - .hidden() - .build(); - public static final Option DB_PROPERTY_SERVER_SSL_CERT = new OptionBuilder<>("db-property-serverSslCert", String.class) - .hidden() - .build(); - public static final Option DB_PROPERTY_TRUST_STORE = new OptionBuilder<>("db-property-trustStore", String.class) - .hidden() - .build(); - public static final Option DB_PROPERTY_ORACLE_TRUST_STORE = new OptionBuilder<>("db-property-javax.net.ssl.trustStore", String.class) - .hidden() - .build(); - public static final Option DB_PROPERTY_TRUST_CERTIFICATE_KEY_STORE_PASSWORD = new OptionBuilder<>("db-property-trustCertificateKeyStorePassword", String.class) - .hidden() - .build(); - public static final Option DB_PROPERTY_TRUST_STORE_PASSWORD = new OptionBuilder<>("db-property-trustStorePassword", String.class) - .hidden() - .build(); - public static final Option DB_PROPERTY_ORACLE_TRUST_STORE_PASSWORD = new OptionBuilder<>("db-property-javax.net.ssl.trustStorePassword", String.class) - .hidden() - .build(); - public static final Option DB_PROPERTY_ORACLE_TRUST_STORE_TYPE = new OptionBuilder<>("db-property-javax.net.ssl.trustStoreType", String.class) - .hidden() - .build(); - public static final Option DB_MSSQL_SEND_STRING_PARAMETER_AS_UNICODE = new OptionBuilder<>("db-mssql-send-string-parameter-as-unicode", String.class) - .category(OptionCategory.DATABASE) - .defaultValue("false") - .hidden() - .build(); - public static final Option DB_MYSQL_CONNECT_TIMEOUT = new OptionBuilder<>("db-mysql-connect-timeout", String.class) - .category(OptionCategory.DATABASE) - .hidden() - .build(); - public static final Option DB_MARIADB_CONNECT_TIMEOUT = new OptionBuilder<>("db-mariadb-connect-timeout", String.class) - .category(OptionCategory.DATABASE) - .hidden() - .build(); - public static final Option DB_ORACLE_CONNECT_TIMEOUT = new OptionBuilder<>("db-oracle-connect-timeout", String.class) - .category(OptionCategory.DATABASE) - .hidden() - .build(); - public static final Option DB_MSSQL_CONNECT_TIMEOUT = new OptionBuilder<>("db-mssql-login-timeout", String.class) - .category(OptionCategory.DATABASE) - .hidden() - .build(); - public static final Option DB_POSTGRES_CONNECT_TIMEOUT = new OptionBuilder<>("db-postgres-connect-timeout", String.class) - .category(OptionCategory.DATABASE) - .hidden() - .build(); - public static final Option DB_TIDB_CONNECT_TIMEOUT = new OptionBuilder<>("db-tidb-connect-timeout", String.class) - .category(OptionCategory.DATABASE) - .hidden() - .build(); + public static final class Datasources { /** * Options that have their sibling for a named datasource * Example: for `db-dialect`, `db-dialect-` is created */ - public static final List> OPTIONS_DATASOURCES = List.of( + public static final List OPTIONS_DATASOURCES = List.of( DB_DIALECT, DB_DRIVER, DB, @@ -258,23 +184,8 @@ public static final class Datasources { DB_TLS_TRUST_STORE_FILE, DB_TLS_TRUST_STORE_PASSWORD, DB_TLS_TRUST_STORE_TYPE, - DB_PROPERTY_SSLMODE, - DB_PROPERTY_SSLFACTORY, - DB_PROPERTY_SSL_MODE, - DB_PROPERTY_ENCRYPT, - DB_PROPERTY_TRUST_SERVER_CERTIFICATE, - DB_PROPERTY_SSL_SERVER_DN_MATCH, - DB_ORACLE_TLS_TRANSPORT, - DB_PROPERTY_SSLROOTCERT, - DB_PROPERTY_TRUST_CERTIFICATE_KEY_STORE_URL, - DB_PROPERTY_SERVER_SSL_CERT, - DB_PROPERTY_TRUST_STORE, - DB_PROPERTY_ORACLE_TRUST_STORE, - DB_PROPERTY_TRUST_CERTIFICATE_KEY_STORE_PASSWORD, - DB_PROPERTY_TRUST_STORE_PASSWORD, - DB_PROPERTY_ORACLE_TRUST_STORE_PASSWORD, - DB_PROPERTY_ORACLE_TRUST_STORE_TYPE - ); + DB_ORACLE_TLS_TRANSPORT + ).stream().map(Option::getKey).toList(); /** * In order to avoid ambiguity, we need to have unique option names for wildcard options. @@ -308,7 +219,7 @@ public static final class Datasources { */ @SuppressWarnings("unchecked") public static Optional> getDatasourceOption(Option parentOption) { - if (!OPTIONS_DATASOURCES.contains(parentOption)) { + if (!OPTIONS_DATASOURCES.contains(parentOption.getKey())) { return Optional.empty(); } @@ -354,7 +265,7 @@ public static Optional getKeyForDatasource(Option option) { */ public static Optional getKeyForDatasource(String option) { return Optional.of(option) - .filter(o -> OPTIONS_DATASOURCES.stream().map(Option::getKey).anyMatch(o::equals)) + .filter(OPTIONS_DATASOURCES::contains) .map(key -> key.concat(DATASOURCES_OVERRIDES_SUFFIX.getOrDefault(key, ""))) .map(key -> key.concat("-")); } diff --git a/quarkus/config-api/src/main/java/org/keycloak/config/Option.java b/quarkus/config-api/src/main/java/org/keycloak/config/Option.java index 1975d0ead4da..16f98ce62e30 100644 --- a/quarkus/config-api/src/main/java/org/keycloak/config/Option.java +++ b/quarkus/config-api/src/main/java/org/keycloak/config/Option.java @@ -22,10 +22,11 @@ public class Option { private final DeprecatedMetadata deprecatedMetadata; private final Set connectedOptions; private String wildcardKey; + private boolean synthetic; public Option(Class type, String key, OptionCategory category, boolean hidden, boolean buildTime, String description, Optional defaultValue, List expectedValues, boolean strictExpectedValues, boolean caseInsensitiveExpectedValues, - DeprecatedMetadata deprecatedMetadata, Set connectedOptions, String wildcardKey, Class componentType) { + DeprecatedMetadata deprecatedMetadata, Set connectedOptions, String wildcardKey, Class componentType, boolean synthetic) { this.type = type; this.key = key; this.category = category; @@ -40,6 +41,7 @@ public Option(Class type, String key, OptionCategory category, boolean hidden this.connectedOptions = connectedOptions; this.wildcardKey = wildcardKey; this.componentType = componentType; + this.synthetic = synthetic; } public Class getType() { @@ -135,6 +137,9 @@ public OptionBuilder toBuilder() { if (hidden) { builder.hidden(); } + if (synthetic) { + builder.synthetic(); + } return builder; } @@ -176,4 +181,8 @@ public static String transformEnumValue(String value) { public Class getComponentType() { return componentType; } + + public boolean isSynthetic() { + return synthetic; + } } diff --git a/quarkus/config-api/src/main/java/org/keycloak/config/OptionBuilder.java b/quarkus/config-api/src/main/java/org/keycloak/config/OptionBuilder.java index a6294b880834..3b1c18e3c1bb 100644 --- a/quarkus/config-api/src/main/java/org/keycloak/config/OptionBuilder.java +++ b/quarkus/config-api/src/main/java/org/keycloak/config/OptionBuilder.java @@ -35,6 +35,8 @@ public class OptionBuilder { private DeprecatedMetadata deprecatedMetadata; private String wildcardKey; + private boolean synthetic; + public static OptionBuilder> listOptionBuilder(String key, Class type) { return new OptionBuilder(key, List.class, type); } @@ -194,7 +196,12 @@ public Option build() { } } - return new Option(type, key, category, hidden, build, description, defaultValue, expectedValues, strictExpectedValues, caseInsensitiveExpectedValues, deprecatedMetadata, connectedOptions, wildcardKey, expected); + return new Option(type, key, category, hidden || synthetic, build, description, defaultValue, expectedValues, strictExpectedValues, caseInsensitiveExpectedValues, deprecatedMetadata, connectedOptions, wildcardKey, expected, synthetic); + } + + public OptionBuilder synthetic() { + this.synthetic = true; + return this; } } diff --git a/quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/cli/Picocli.java b/quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/cli/Picocli.java index 208a965caec1..2e3e8bb844c8 100644 --- a/quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/cli/Picocli.java +++ b/quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/cli/Picocli.java @@ -59,7 +59,7 @@ import org.keycloak.quarkus.runtime.configuration.mappers.PropertyMappers; import io.quarkus.bootstrap.runner.QuarkusEntryPoint; -import io.quarkus.dev.console.QuarkusConsole; +import io.quarkus.dev.console.TerminalUtils; import io.quarkus.runtime.LaunchMode; import io.smallrye.config.ConfigValue; import io.smallrye.mutiny.tuples.Functions.TriConsumer; @@ -106,7 +106,7 @@ private record IncludeOptions(boolean includeRuntime, boolean includeBuildTime, private Set duplicatedOptionsNames = new HashSet(); public static boolean hasColorSupport() { - return QuarkusConsole.hasColorSupport(); + return TerminalUtils.hasColorSupport(); } public Ansi getColorMode() { @@ -290,7 +290,7 @@ public void validateConfig() { if (!mapper.hasWildcard() // non-wildcard options will be validated in the next pass // validate only canonical mappings to kc. values - no need to consider alternative forms || !name.startsWith(MicroProfileConfigProvider.NS_KEYCLOAK_PREFIX)) { - return; + return; } validateProperty(abstractCommand, options, ignoredRunTime, disabledBuildTime, disabledRunTime, deprecatedInUse, missingOption, disabledMappers.contains(mapper), forKey, unnecessary); @@ -738,6 +738,9 @@ private static void addMappedOptionsToArgGroups(CommandLine commandLine, Map mapper : entry.getValue()) { + if (mapper.getOption().isSynthetic()) { + continue; + } String name = mapper.getCliFormat(); boolean hidden = mapper.isHidden() || ac.isHiddenCategory(mapper.getCategory()) diff --git a/quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/configuration/mappers/DatabasePropertyMappers.java b/quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/configuration/mappers/DatabasePropertyMappers.java index 4104901548c8..85794447a999 100644 --- a/quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/configuration/mappers/DatabasePropertyMappers.java +++ b/quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/configuration/mappers/DatabasePropertyMappers.java @@ -16,7 +16,6 @@ import org.keycloak.config.CachingOptions.Stack; import org.keycloak.config.DatabaseOptions; import org.keycloak.config.Option; -import org.keycloak.config.OptionBuilder; import org.keycloak.config.OptionsUtil; import org.keycloak.config.TransactionOptions; import org.keycloak.config.database.Database; @@ -34,19 +33,6 @@ import static org.keycloak.config.DatabaseOptions.DB_ORACLE_TLS_TRANSPORT; import static org.keycloak.config.DatabaseOptions.DB_POOL_INITIAL_SIZE; import static org.keycloak.config.DatabaseOptions.DB_POOL_MAX_SIZE; -import static org.keycloak.config.DatabaseOptions.DB_PROPERTY_ENCRYPT; -import static org.keycloak.config.DatabaseOptions.DB_PROPERTY_ORACLE_TRUST_STORE; -import static org.keycloak.config.DatabaseOptions.DB_PROPERTY_ORACLE_TRUST_STORE_PASSWORD; -import static org.keycloak.config.DatabaseOptions.DB_PROPERTY_ORACLE_TRUST_STORE_TYPE; -import static org.keycloak.config.DatabaseOptions.DB_PROPERTY_SERVER_SSL_CERT; -import static org.keycloak.config.DatabaseOptions.DB_PROPERTY_SSLFACTORY; -import static org.keycloak.config.DatabaseOptions.DB_PROPERTY_SSLROOTCERT; -import static org.keycloak.config.DatabaseOptions.DB_PROPERTY_SSL_SERVER_DN_MATCH; -import static org.keycloak.config.DatabaseOptions.DB_PROPERTY_TRUST_CERTIFICATE_KEY_STORE_PASSWORD; -import static org.keycloak.config.DatabaseOptions.DB_PROPERTY_TRUST_CERTIFICATE_KEY_STORE_URL; -import static org.keycloak.config.DatabaseOptions.DB_PROPERTY_TRUST_SERVER_CERTIFICATE; -import static org.keycloak.config.DatabaseOptions.DB_PROPERTY_TRUST_STORE; -import static org.keycloak.config.DatabaseOptions.DB_PROPERTY_TRUST_STORE_PASSWORD; import static org.keycloak.config.DatabaseOptions.DB_TLS_MODE; import static org.keycloak.config.DatabaseOptions.DB_TLS_TRUST_STORE_FILE; import static org.keycloak.config.DatabaseOptions.DB_TLS_TRUST_STORE_PASSWORD; @@ -104,7 +90,7 @@ public List> getPropertyMappers() { .to(PG_TARGET_SERVER_TYPE) .isEnabled(DatabasePropertyMappers::isPostgresqlTargetServerTypeEnabled) .build(), - fromOption(DatabaseOptions.DB_MSSQL_SEND_STRING_PARAMETER_AS_UNICODE) + fromOption(DB).mapFrom(DB, (name, value, context) -> "false") .to(MSSQL_SEND_STRING_PARAMETER_AS_UNICODE) .isEnabled(DatabasePropertyMappers::isMssqlSendStringParametersAsUnicode) .build(), @@ -117,37 +103,37 @@ public List> getPropertyMappers() { .to(JDBC_ACQUISITION_TIMEOUT) .mapFrom(DatabaseOptions.DB_CONNECT_TIMEOUT, (name, value, context) -> computeAcquisitionTimeout(value)) .build(), - fromOption(DatabaseOptions.DB_MYSQL_CONNECT_TIMEOUT) + fromOption(DatabaseOptions.DB_CONNECT_TIMEOUT) .to(CONNECT_TIMEOUT) .mapFrom(DatabaseOptions.DB_CONNECT_TIMEOUT, (name, value, context) -> durationToMillis(value)) .isEnabled(DatabasePropertyMappers::isMysqlConnectTimeoutEnabled) .build(), - fromOption(DatabaseOptions.DB_MARIADB_CONNECT_TIMEOUT) + fromOption(DatabaseOptions.DB_CONNECT_TIMEOUT) .to(CONNECT_TIMEOUT) .mapFrom(DatabaseOptions.DB_CONNECT_TIMEOUT, (name, value, context) -> durationToMillis(value)) .isEnabled(DatabasePropertyMappers::isMariadbConnectTimeoutEnabled) .build(), - fromOption(DatabaseOptions.DB_ORACLE_CONNECT_TIMEOUT) + fromOption(DatabaseOptions.DB_CONNECT_TIMEOUT) .to(ORACLEDB_CONNECT_TIMEOUT) .mapFrom(DatabaseOptions.DB_CONNECT_TIMEOUT, (name, value, context) -> durationToMillis(value)) .isEnabled(DatabasePropertyMappers::isOracleConnectTimeoutEnabled) .build(), - fromOption(DatabaseOptions.DB_MSSQL_CONNECT_TIMEOUT) + fromOption(DatabaseOptions.DB_CONNECT_TIMEOUT) .to(MSSQL_CONNECT_TIMEOUT) .mapFrom(DatabaseOptions.DB_CONNECT_TIMEOUT, (name, value, context) -> durationToSeconds(value)) .isEnabled(DatabasePropertyMappers::isMssqlLoginTimeoutEnabled) .build(), - fromOption(DatabaseOptions.DB_POSTGRES_CONNECT_TIMEOUT) + fromOption(DatabaseOptions.DB_CONNECT_TIMEOUT) .to(CONNECT_TIMEOUT) .mapFrom(DatabaseOptions.DB_CONNECT_TIMEOUT, (name, value, context) -> durationToSeconds(value)) .isEnabled(DatabasePropertyMappers::isPostgresConnectTimeoutEnabled) .build(), - fromOption(DatabaseOptions.DB_TIDB_CONNECT_TIMEOUT) + fromOption(DatabaseOptions.DB_CONNECT_TIMEOUT) .to(CONNECT_TIMEOUT) .mapFrom(DatabaseOptions.DB_CONNECT_TIMEOUT, (name, value, context) -> durationToMillis(value)) @@ -203,11 +189,11 @@ public List> getPropertyMappers() { fromOption(DatabaseOptions.DB_ENABLED_DATASOURCE) .to("quarkus.datasource.\"\".active") .build(), - fromOption(DB_URL_PATH) - .build(), // Database TLS configuration fromOption(DB_TLS_MODE) .paramLabel("mode") + .transformer(DatabasePropertyMappers::transformOracleProtocol) + .to(NS_KEYCLOAK_PREFIX + DB_ORACLE_TLS_TRANSPORT.getKey()) .build(), fromOption(DB_TLS_TRUST_STORE_FILE) .paramLabel("path") @@ -219,41 +205,38 @@ public List> getPropertyMappers() { .paramLabel("password") .isMasked(true) .build(), - fromOption(DB_ORACLE_TLS_TRANSPORT) - .mapFrom(DB_TLS_MODE, DatabasePropertyMappers::transformOracleProtocol) - .build(), // Oracle - setTlsJdbcProperty(DB_PROPERTY_SSL_SERVER_DN_MATCH, "ssl_server_dn_match", Map.of(Database.Vendor.ORACLE, "true")), - setInputTlsJdbcProperty(DB_PROPERTY_ORACLE_TRUST_STORE, DB_TLS_TRUST_STORE_FILE, "javax.net.ssl.trustStore", EnumSet.of(Database.Vendor.ORACLE)), - setInputTlsJdbcProperty(DB_PROPERTY_ORACLE_TRUST_STORE_PASSWORD, DB_TLS_TRUST_STORE_PASSWORD, "javax.net.ssl.trustStorePassword", EnumSet.of(Database.Vendor.ORACLE)), - setInputTlsJdbcProperty(DB_PROPERTY_ORACLE_TRUST_STORE_TYPE, DB_TLS_TRUST_STORE_TYPE, "javax.net.ssl.trustStoreType", EnumSet.of(Database.Vendor.ORACLE)), + setTlsJdbcProperty("ssl_server_dn_match", Map.of(Database.Vendor.ORACLE, "true")), + setInputTlsJdbcProperty(DB_TLS_TRUST_STORE_FILE, "javax.net.ssl.trustStore", EnumSet.of(Database.Vendor.ORACLE)), + setInputTlsJdbcProperty(DB_TLS_TRUST_STORE_PASSWORD, "javax.net.ssl.trustStorePassword", EnumSet.of(Database.Vendor.ORACLE)), + setInputTlsJdbcProperty(DB_TLS_TRUST_STORE_TYPE, "javax.net.ssl.trustStoreType", EnumSet.of(Database.Vendor.ORACLE)), // MSSQL - setTlsJdbcProperty(DB_PROPERTY_ENCRYPT, "encrypt", Map.of(Database.Vendor.MSSQL, "true")), - setTlsJdbcProperty(DB_PROPERTY_TRUST_SERVER_CERTIFICATE, "trustServerCertificate", Map.of(Database.Vendor.MSSQL, "false")), - setInputTlsJdbcProperty(DB_PROPERTY_TRUST_STORE, DB_TLS_TRUST_STORE_FILE, "trustStore", EnumSet.of(Database.Vendor.MSSQL)), - setInputTlsJdbcProperty(DB_PROPERTY_TRUST_STORE_PASSWORD, DB_TLS_TRUST_STORE_PASSWORD, "trustStorePassword", EnumSet.of(Database.Vendor.MSSQL)), + setTlsJdbcProperty("encrypt", Map.of(Database.Vendor.MSSQL, "true")), + setTlsJdbcProperty("trustServerCertificate", Map.of(Database.Vendor.MSSQL, "false")), + setInputTlsJdbcProperty(DB_TLS_TRUST_STORE_FILE, "trustStore", EnumSet.of(Database.Vendor.MSSQL)), + setInputTlsJdbcProperty(DB_TLS_TRUST_STORE_PASSWORD, "trustStorePassword", EnumSet.of(Database.Vendor.MSSQL)), // Mysql/MariaDB/TiDB - setTlsJdbcProperty(DatabaseOptions.DB_PROPERTY_SSL_MODE, "sslMode", + setTlsJdbcProperty("sslMode", Map.of( Database.Vendor.MARIADB, "verify-full", Database.Vendor.MYSQL, "VERIFY_IDENTITY", Database.Vendor.TIDB, "VERIFY_IDENTITY" ) ), - setInputTlsJdbcProperty(DB_PROPERTY_TRUST_CERTIFICATE_KEY_STORE_URL, DB_TLS_TRUST_STORE_FILE, "trustCertificateKeyStoreUrl", EnumSet.of(Database.Vendor.MYSQL, Database.Vendor.TIDB)), - setInputTlsJdbcProperty(DB_PROPERTY_TRUST_CERTIFICATE_KEY_STORE_PASSWORD, DB_TLS_TRUST_STORE_PASSWORD, "trustCertificateKeyStorePassword", EnumSet.of(Database.Vendor.MYSQL, Database.Vendor.TIDB)), - setInputTlsJdbcProperty(DB_PROPERTY_SERVER_SSL_CERT, DB_TLS_TRUST_STORE_FILE, "serverSslCert", EnumSet.of(Database.Vendor.MARIADB)), + setInputTlsJdbcProperty(DB_TLS_TRUST_STORE_FILE, "trustCertificateKeyStoreUrl", EnumSet.of(Database.Vendor.MYSQL, Database.Vendor.TIDB)), + setInputTlsJdbcProperty(DB_TLS_TRUST_STORE_PASSWORD, "trustCertificateKeyStorePassword", EnumSet.of(Database.Vendor.MYSQL, Database.Vendor.TIDB)), + setInputTlsJdbcProperty(DB_TLS_TRUST_STORE_FILE, "serverSslCert", EnumSet.of(Database.Vendor.MARIADB)), // PostgreSQL - setTlsJdbcProperty(DatabaseOptions.DB_PROPERTY_SSLMODE, "sslmode", Map.of(Database.Vendor.POSTGRES, "verify-full")), - fromOption(DB_PROPERTY_SSLFACTORY) + setTlsJdbcProperty("sslmode", Map.of(Database.Vendor.POSTGRES, "verify-full")), + fromOption(DB) .mapFrom(DB) .transformer(DatabasePropertyMappers::computePostgresSSLFactory) .to("quarkus.datasource.jdbc.additional-jdbc-properties.sslfactory") .build(), - setInputTlsJdbcProperty(DB_PROPERTY_SSLROOTCERT, DB_TLS_TRUST_STORE_FILE, "sslrootcert", EnumSet.of(Database.Vendor.POSTGRES)) + setInputTlsJdbcProperty(DB_TLS_TRUST_STORE_FILE, "sslrootcert", EnumSet.of(Database.Vendor.POSTGRES)) ); return appendDatasourceMappers(mappers, Map.of( @@ -283,11 +266,6 @@ private static boolean isJdbcPingStack() { return Stack.jdbc_ping.toString().equals(stack) || Stack.jdbc_ping_udp.toString().equals(stack); } - private static final Option DB_URL_PATH = new OptionBuilder<>("db-url-path", String.class) - .hidden() - .description("Used for internal purposes of H2 database.") - .build(); - public static boolean isPostgresqlTargetServerTypeEnabled() { String db = Configuration.getConfigValue(DB).getValue(); Database.Vendor vendor = Database.getVendor(db).orElse(null); @@ -551,18 +529,17 @@ private static String transformOracleProtocol(String datasource, String value, C return tlsMode != DatabaseOptions.DatabaseTlsMode.DISABLED ? "@tcps:" : "@"; } - private static PropertyMapper setTlsJdbcProperty(Option option, String jdbcPropertyKey, Map vendorValues) { - return fromOption(option) + private static PropertyMapper setTlsJdbcProperty(String jdbcPropertyKey, Map vendorValues) { + return fromOption(DB) .mapFrom(DB) .transformer((name, value, context) -> computeTlsProperty(vendorValues, name, value, jdbcPropertyKey)) .to("quarkus.datasource.jdbc.additional-jdbc-properties." + jdbcPropertyKey) .build(); } - private static PropertyMapper setInputTlsJdbcProperty(Option option, Option from, String jdbcPropertyKey, Collection vendorValues) { - return fromOption(option) - .mapFrom(from) - .transformer((name, value, context) -> transformTlsUserProperty(vendorValues, name, value, jdbcPropertyKey)) + private static PropertyMapper setInputTlsJdbcProperty(Option from, String jdbcPropertyKey, Collection vendorValues) { + return fromOption(from) + .mapFrom(from, (name, value, context) -> transformTlsUserProperty(vendorValues, name, value, jdbcPropertyKey)) .to("quarkus.datasource.jdbc.additional-jdbc-properties." + jdbcPropertyKey) .build(); } diff --git a/quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/configuration/mappers/LoggingPropertyMappers.java b/quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/configuration/mappers/LoggingPropertyMappers.java index e071a868f79b..26829a31beee 100644 --- a/quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/configuration/mappers/LoggingPropertyMappers.java +++ b/quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/configuration/mappers/LoggingPropertyMappers.java @@ -34,7 +34,6 @@ import static org.keycloak.quarkus.runtime.configuration.Configuration.isSet; import static org.keycloak.quarkus.runtime.configuration.Configuration.isTrue; import static org.keycloak.quarkus.runtime.configuration.mappers.PropertyMapper.fromOption; -import static org.keycloak.quarkus.runtime.configuration.mappers.PropertyMapper.fromParentOption; public final class LoggingPropertyMappers implements PropertyMapperGrouping { @@ -85,11 +84,11 @@ public List> getPropertyMappers() { .to("quarkus.log.console.json.log-format") .paramLabel("format") .build(), - fromParentOption(LoggingOptions.LOG_SERVICE_NAME) + fromOption(LoggingOptions.LOG_SERVICE_NAME).mapFrom(LoggingOptions.LOG_SERVICE_NAME) .isEnabled(LoggingPropertyMappers::isConsoleJsonEnabled, "%s and output is set to 'json'".formatted(CONSOLE_ENABLED_MSG)) .to("quarkus.log.console.json.additional-field.\"service.name\".value") .build(), - fromParentOption(LoggingOptions.LOG_SERVICE_ENVIRONMENT) + fromOption(LoggingOptions.LOG_SERVICE_ENVIRONMENT).mapFrom(LoggingOptions.LOG_SERVICE_ENVIRONMENT) .isEnabled(LoggingPropertyMappers::isConsoleJsonEnabled, "%s and output is set to 'json'".formatted(CONSOLE_ENABLED_MSG)) .to("quarkus.log.console.json.additional-field.\"service.environment\".value") .build(), @@ -149,11 +148,11 @@ public List> getPropertyMappers() { .to("quarkus.log.file.json.log-format") .paramLabel("format") .build(), - fromParentOption(LoggingOptions.LOG_SERVICE_NAME) + fromOption(LoggingOptions.LOG_SERVICE_NAME).mapFrom(LoggingOptions.LOG_SERVICE_NAME) .isEnabled(LoggingPropertyMappers::isFileJsonEnabled, FILE_ENABLED_MSG + " and output is set to 'json'") .to("quarkus.log.file.json.additional-field.\"service.name\".value") .build(), - fromParentOption(LoggingOptions.LOG_SERVICE_ENVIRONMENT) + fromOption(LoggingOptions.LOG_SERVICE_ENVIRONMENT).mapFrom(LoggingOptions.LOG_SERVICE_ENVIRONMENT) .isEnabled(LoggingPropertyMappers::isFileJsonEnabled, FILE_ENABLED_MSG + " and output is set to 'json'") .to("quarkus.log.file.json.additional-field.\"service.environment\".value") .build(), @@ -269,11 +268,11 @@ public List> getPropertyMappers() { .to("quarkus.log.syslog.json.log-format") .paramLabel("format") .build(), - fromParentOption(LoggingOptions.LOG_SERVICE_NAME) + fromOption(LoggingOptions.LOG_SERVICE_NAME).mapFrom(LoggingOptions.LOG_SERVICE_NAME) .isEnabled(LoggingPropertyMappers::isSyslogJsonEnabled, SYSLOG_ENABLED_MSG + " and output is set to 'json'") .to("quarkus.log.syslog.json.additional-field.\"service.name\".value") .build(), - fromParentOption(LoggingOptions.LOG_SERVICE_ENVIRONMENT) + fromOption(LoggingOptions.LOG_SERVICE_ENVIRONMENT).mapFrom(LoggingOptions.LOG_SERVICE_ENVIRONMENT) .isEnabled(LoggingPropertyMappers::isSyslogJsonEnabled, SYSLOG_ENABLED_MSG + " and output is set to 'json'") .to("quarkus.log.syslog.json.additional-field.\"service.environment\".value") .build(), diff --git a/quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/configuration/mappers/PropertyMapper.java b/quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/configuration/mappers/PropertyMapper.java index d45966a75c26..29ddee93778d 100644 --- a/quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/configuration/mappers/PropertyMapper.java +++ b/quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/configuration/mappers/PropertyMapper.java @@ -36,7 +36,6 @@ import org.keycloak.config.OptionBuilder; import org.keycloak.config.OptionCategory; import org.keycloak.config.WildcardOptionsUtil; -import org.keycloak.models.utils.KeycloakModelUtils; import org.keycloak.quarkus.runtime.cli.PropertyException; import org.keycloak.quarkus.runtime.cli.ShortErrorMessageHandler; import org.keycloak.quarkus.runtime.cli.command.AbstractCommand; @@ -148,7 +147,10 @@ ConfigValue getConfigValue(String name, ConfigSourceInterceptorContext context) // try to obtain the value for the property we want to map first // we don't want the NestedPropertyMappingInterceptor to restart the chain here, so we force a proceed // this ensures that mapFrom transformers, and regular transformers are applied exclusively - not chained - ConfigValue config = convertValue(NestedPropertyMappingInterceptor.proceed(context, from)); + ConfigValue config = null; + if (!option.isSynthetic()) { + config = convertValue(NestedPropertyMappingInterceptor.proceed(context, from)); + } Optional directValue = null; boolean parentValue = false; @@ -584,8 +586,12 @@ public PropertyMapper build() { if (paramLabel == null && Boolean.class.equals(option.getType())) { paramLabel = Boolean.TRUE + "|" + Boolean.FALSE; } + Option opt = option; + if (Objects.equals(option.getKey(), mapFrom)) { + opt = option.toBuilder().synthetic().build(); + } if (option.getKey().contains(WildcardOptionsUtil.WILDCARD_START)) { - return new WildcardPropertyMapper<>(option, to, enabled, enabledWhen, mapper, mapFrom, parentMapper, paramLabel, isMasked, validator, description, isRequired, requiredWhen, wildcardKeysTransformer, wildcardMapFrom); + return new WildcardPropertyMapper<>(opt, to, enabled, enabledWhen, mapper, mapFrom, parentMapper, paramLabel, isMasked, validator, description, isRequired, requiredWhen, wildcardKeysTransformer, wildcardMapFrom); } if (wildcardKeysTransformer != null || wildcardMapFrom != null) { throw new AssertionError("Wildcard operations not expected with non-wildcard mapper"); @@ -593,7 +599,7 @@ public PropertyMapper build() { if (!option.isBuildTime() && to != null && PropertyMappers.isSpiBuildTimeProperty(to, true)) { throw new AssertionError("A runtime option should not map to a build time spi option"); } - return new PropertyMapper<>(option, to, enabled, enabledWhen, mapper, mapFrom, parentMapper, paramLabel, isMasked, validator, description, isRequired, requiredWhen, null, null); + return new PropertyMapper<>(opt, to, enabled, enabledWhen, mapper, mapFrom, parentMapper, paramLabel, isMasked, validator, description, isRequired, requiredWhen, null, null); } } @@ -601,18 +607,6 @@ public static PropertyMapper.Builder fromOption(Option opt) { return new PropertyMapper.Builder<>(opt); } - /** - * Create a property mapper that get value from the parent option - */ - public static PropertyMapper.Builder fromParentOption(Option parentOption) { - final var option = new OptionBuilder<>(parentOption.getKey() + KeycloakModelUtils.generateId(), parentOption.getType()) - .buildTime(parentOption.isBuildTime()) - .hidden() - .build(); - return new Builder<>(option) - .mapFrom(parentOption); - } - /** * Create a property mapper from a feature. * The mapper maps to external properties the state of the feature. @@ -622,11 +616,11 @@ public static PropertyMapper.Builder fromParentOption(Option parentOpt public static PropertyMapper.Builder fromFeature(Profile.Feature feature) { final var option = new OptionBuilder<>(feature.getKey() + "-hidden-mapper", Boolean.class) .buildTime(true) - .hidden() + .defaultValue(Boolean.TRUE) + .synthetic() .build(); return new Builder<>(option) - .isEnabled(() -> Profile.isFeatureEnabled(feature)) - .transformer((v, ctx) -> Boolean.TRUE.toString()); // we know the feature is enabled due to .isEnabled() + .isEnabled(() -> Profile.isFeatureEnabled(feature)); } public void validate(ConfigValue value) { diff --git a/quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/configuration/mappers/PropertyMappers.java b/quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/configuration/mappers/PropertyMappers.java index 2f3008008851..93c4452ff4c6 100644 --- a/quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/configuration/mappers/PropertyMappers.java +++ b/quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/configuration/mappers/PropertyMappers.java @@ -363,7 +363,10 @@ private static void sanitizeMappers(Map>> } private static void handleMapper(PropertyMapper mapper, BiConsumer> operation) { - operation.accept(mapper.getFrom(), mapper); + // skip accounting for the from if it is a synthetic option + if (!mapper.getOption().isSynthetic()) { + operation.accept(mapper.getFrom(), mapper); + } if (!mapper.getFrom().equals(mapper.getTo())) { String to = mapper.getTo(); operation.accept(to, mapper); @@ -374,6 +377,8 @@ private static void handleMapper(PropertyMapper mapper, BiConsumer getConnectedOptions(String key) { public Optional extractWildcardValue(String key) { String result = null; - if (key.startsWith(fromPrefix)) { + if (!this.option.isSynthetic() && key.startsWith(fromPrefix)) { result = key.substring(fromPrefix.length()); } else if (key.startsWith(toPrefix) && key.endsWith(toSuffix)) { // TODO: this presumes that the quarkus value is quoted diff --git a/quarkus/runtime/src/test/java/org/keycloak/quarkus/runtime/configuration/AbstractConfigurationTest.java b/quarkus/runtime/src/test/java/org/keycloak/quarkus/runtime/configuration/AbstractConfigurationTest.java index 46bac7dc3a6b..8192c63a9d8c 100644 --- a/quarkus/runtime/src/test/java/org/keycloak/quarkus/runtime/configuration/AbstractConfigurationTest.java +++ b/quarkus/runtime/src/test/java/org/keycloak/quarkus/runtime/configuration/AbstractConfigurationTest.java @@ -26,6 +26,7 @@ import org.keycloak.Config; import org.keycloak.quarkus.runtime.Environment; import org.keycloak.quarkus.runtime.KeycloakMain; +import org.keycloak.quarkus.runtime.cli.command.Start; import org.keycloak.quarkus.runtime.configuration.mappers.PropertyMappers; import io.smallrye.config.ConfigValue; @@ -140,6 +141,7 @@ protected static SmallRyeConfig createConfigFromCliArguments(String... args) { ConfigArgsConfigSource.setCliArgs(args); var config = createConfig(); PropertyMappers.reset(); + PropertyMappers.sanitizeDisabledMappers(new Start()); return config; } diff --git a/quarkus/runtime/src/test/java/org/keycloak/quarkus/runtime/configuration/ConfigurationTest.java b/quarkus/runtime/src/test/java/org/keycloak/quarkus/runtime/configuration/ConfigurationTest.java index 43ec88cd2dd4..9c28c82d44c5 100644 --- a/quarkus/runtime/src/test/java/org/keycloak/quarkus/runtime/configuration/ConfigurationTest.java +++ b/quarkus/runtime/src/test/java/org/keycloak/quarkus/runtime/configuration/ConfigurationTest.java @@ -31,10 +31,8 @@ import org.keycloak.Config; import org.keycloak.config.CachingOptions; import org.keycloak.quarkus.runtime.Environment; -import org.keycloak.quarkus.runtime.cli.command.Start; import org.keycloak.quarkus.runtime.configuration.mappers.DatabasePropertyMappers; import org.keycloak.quarkus.runtime.configuration.mappers.HttpPropertyMappers; -import org.keycloak.quarkus.runtime.configuration.mappers.PropertyMappers; import org.keycloak.quarkus.runtime.vault.FilesKeystoreVaultProviderFactory; import org.keycloak.quarkus.runtime.vault.FilesPlainTextVaultProviderFactory; import org.keycloak.spi.infinispan.CacheEmbeddedConfigProviderSpi; @@ -379,79 +377,65 @@ public void testDatabaseProperties() { System.setProperty("kc.db-url-properties", ";;test=test;test1=test1"); System.setProperty("kc.db-url-path", "test-dir"); System.setProperty("kc.transaction-xa-enabled", "true"); - ConfigArgsConfigSource.setCliArgs("--db=dev-file"); - SmallRyeConfig config = createConfig(); + SmallRyeConfig config = createConfigFromCliArguments("--db=dev-file"); assertEquals(H2Dialect.class.getName(), config.getConfigValue("kc.db-dialect").getValue()); assertEquals("jdbc:h2:file:test-dir/data/h2/keycloakdb;;test=test;test1=test1;NON_KEYWORDS=VALUE;DB_CLOSE_ON_EXIT=FALSE;DB_CLOSE_DELAY=0", config.getConfigValue("quarkus.datasource.jdbc.url").getValue()); assertEquals("xa", config.getConfigValue("quarkus.datasource.jdbc.transactions").getValue()); - ConfigArgsConfigSource.setCliArgs(""); - config = createConfig(); + config = createConfigFromCliArguments(""); assertEquals(H2Dialect.class.getName(), config.getConfigValue("kc.db-dialect").getValue()); assertEquals("jdbc:h2:file:test-dir/data/h2/keycloakdb;;test=test;test1=test1;NON_KEYWORDS=VALUE;DB_CLOSE_ON_EXIT=FALSE;DB_CLOSE_DELAY=0", config.getConfigValue("quarkus.datasource.jdbc.url").getValue()); System.setProperty("kc.db-url-properties", "?test=test&test1=test1"); - ConfigArgsConfigSource.setCliArgs("--db=mariadb"); - config = createConfig(); + config = createConfigFromCliArguments("--db=mariadb"); assertEquals("jdbc:mariadb://localhost:3306/keycloak?test=test&test1=test1", config.getConfigValue("quarkus.datasource.jdbc.url").getValue()); assertEquals(MariaDBDialect.class.getName(), config.getConfigValue("kc.db-dialect").getValue()); assertEquals(MariaDbDataSource.class.getName(), config.getConfigValue("quarkus.datasource.jdbc.driver").getValue()); - ConfigArgsConfigSource.setCliArgs("--db=postgres"); - config = createConfig(); + config = createConfigFromCliArguments("--db=postgres"); assertEquals("jdbc:postgresql://localhost:5432/keycloak?test=test&test1=test1", config.getConfigValue("quarkus.datasource.jdbc.url").getValue()); assertEquals(PostgreSQLDialect.class.getName(), config.getConfigValue("kc.db-dialect").getValue()); assertEquals(PGXADataSource.class.getName(), config.getConfigValue("quarkus.datasource.jdbc.driver").getValue()); - ConfigArgsConfigSource.setCliArgs("--db-schema=test-schema"); - config = createConfig(); + config = createConfigFromCliArguments("--db-schema=test-schema"); assertEquals("test-schema", config.getConfigValue("kc.db-schema").getValue()); assertEquals("test-schema", config.getConfigValue("kc.db-schema").getValue()); - ConfigArgsConfigSource.setCliArgs("--db=postgres"); - config = createConfig(); + config = createConfigFromCliArguments("--db=postgres"); assertTrue(DatabasePropertyMappers.isPostgresqlTargetServerTypeEnabled()); assertTrue(StreamSupport.stream(config.getPropertyNames().spliterator(), false).anyMatch(DatabasePropertyMappers.PG_TARGET_SERVER_TYPE::equals)); assertEquals("primary", config.getConfigValue(DatabasePropertyMappers.PG_TARGET_SERVER_TYPE).getValue()); - ConfigArgsConfigSource.setCliArgs("--db=postgres", "--db-url-properties=?targetServerType=any"); - config = createConfig(); + config = createConfigFromCliArguments("--db=postgres", "--db-url-properties=?targetServerType=any"); assertFalse(DatabasePropertyMappers.isPostgresqlTargetServerTypeEnabled()); - ConfigArgsConfigSource.setCliArgs("--db=postgres", "--db-driver=software.amazon.jdbc.Driver"); - config = createConfig(); + config = createConfigFromCliArguments("--db=postgres", "--db-driver=software.amazon.jdbc.Driver"); assertFalse(DatabasePropertyMappers.isPostgresqlTargetServerTypeEnabled()); - ConfigArgsConfigSource.setCliArgs("--db=postgres", "--db-url=jdbc:postgresql://localhost:5432/keycloak?targetServerType=any"); - config = createConfig(); + config = createConfigFromCliArguments("--db=postgres", "--db-url=jdbc:postgresql://localhost:5432/keycloak?targetServerType=any"); assertFalse(DatabasePropertyMappers.isPostgresqlTargetServerTypeEnabled()); // MSSQL: sendStringParametersAsUnicode should be set to false by default - ConfigArgsConfigSource.setCliArgs("--db=mssql"); - config = createConfig(); + config = createConfigFromCliArguments("--db=mssql"); assertTrue(DatabasePropertyMappers.isMssqlSendStringParametersAsUnicode()); assertTrue(StreamSupport.stream(config.getPropertyNames().spliterator(), false) .anyMatch(DatabasePropertyMappers.MSSQL_SEND_STRING_PARAMETER_AS_UNICODE::equals)); assertEquals("false", config.getConfigValue(DatabasePropertyMappers.MSSQL_SEND_STRING_PARAMETER_AS_UNICODE).getValue()); // sendStringParametersAsUnicode already present in db-url-properties -> disabled - ConfigArgsConfigSource.setCliArgs("--db=mssql", "--db-url-properties=;sendStringParametersAsUnicode=true"); - config = createConfig(); + config = createConfigFromCliArguments("--db=mssql", "--db-url-properties=;sendStringParametersAsUnicode=true"); assertFalse(DatabasePropertyMappers.isMssqlSendStringParametersAsUnicode()); // custom JDBC driver -> disabled - ConfigArgsConfigSource.setCliArgs("--db=mssql", "--db-driver=com.custom.CustomSQLServerDriver"); - config = createConfig(); + config = createConfigFromCliArguments("--db=mssql", "--db-driver=com.custom.CustomSQLServerDriver"); assertFalse(DatabasePropertyMappers.isMssqlSendStringParametersAsUnicode()); // sendStringParametersAsUnicode already present in db-url -> disabled - ConfigArgsConfigSource.setCliArgs("--db=mssql", "--db-url=jdbc:sqlserver://localhost:1433;databaseName=keycloak;sendStringParametersAsUnicode=false"); - config = createConfig(); + config = createConfigFromCliArguments("--db=mssql", "--db-url=jdbc:sqlserver://localhost:1433;databaseName=keycloak;sendStringParametersAsUnicode=false"); assertFalse(DatabasePropertyMappers.isMssqlSendStringParametersAsUnicode()); // other db vendor -> disabled (already covered implicitly but good to be explicit) - ConfigArgsConfigSource.setCliArgs("--db=postgres"); - config = createConfig(); + config = createConfigFromCliArguments("--db=postgres"); assertFalse(DatabasePropertyMappers.isMssqlSendStringParametersAsUnicode()); } @@ -867,9 +851,9 @@ public void testDefaultDatabaseConnectTimeouts() { assertEquals("10s", config.getConfigValue(DatabasePropertyMappers.JDBC_LOGIN_TIMEOUT).getValue()); assertEquals("PT20S", config.getConfigValue(DatabasePropertyMappers.JDBC_ACQUISITION_TIMEOUT).getValue()); - createConfigFromCliArguments("--db=mysql", "--db-url-properties=?connectTimeout=5000"); + config = createConfigFromCliArguments("--db=mysql", "--db-url-properties=?connectTimeout=5000"); assertFalse(DatabasePropertyMappers.isMysqlConnectTimeoutEnabled()); - createConfigFromCliArguments("--db=mysql", "--db-url=jdbc:mysql://localhost:3306/keycloak?connectTimeout=5000"); + config = createConfigFromCliArguments("--db=mysql", "--db-url=jdbc:mysql://localhost:3306/keycloak?connectTimeout=5000"); assertFalse(DatabasePropertyMappers.isMysqlConnectTimeoutEnabled()); config = createConfigFromCliArguments("--db=mysql", "--db-connect-timeout=30s"); @@ -878,132 +862,90 @@ public void testDefaultDatabaseConnectTimeouts() { assertEquals("PT1M", config.getConfigValue(DatabasePropertyMappers.JDBC_ACQUISITION_TIMEOUT).getValue()); // MariaDB: - ConfigArgsConfigSource.setCliArgs("--db=mariadb"); - config = createConfig(); + config = createConfigFromCliArguments("--db=mariadb"); assertTrue(DatabasePropertyMappers.isMariadbConnectTimeoutEnabled()); assertEquals("10000", config.getConfigValue(DatabasePropertyMappers.CONNECT_TIMEOUT).getValue()); assertEquals("10s", config.getConfigValue(DatabasePropertyMappers.JDBC_LOGIN_TIMEOUT).getValue()); assertEquals("PT20S", config.getConfigValue(DatabasePropertyMappers.JDBC_ACQUISITION_TIMEOUT).getValue()); - ConfigArgsConfigSource.setCliArgs("--db=mariadb", "--db-url=jdbc:mariadb://localhost:3306/keycloak?connectTimeout=5000"); - config = createConfig(); + config = createConfigFromCliArguments("--db=mariadb", "--db-url=jdbc:mariadb://localhost:3306/keycloak?connectTimeout=5000"); assertFalse(DatabasePropertyMappers.isMariadbConnectTimeoutEnabled()); - ConfigArgsConfigSource.setCliArgs("--db=mariadb", "--db-url-properties=?connectTimeout=5000"); - config = createConfig(); + config = createConfigFromCliArguments("--db=mariadb", "--db-url-properties=?connectTimeout=5000"); assertFalse(DatabasePropertyMappers.isMariadbConnectTimeoutEnabled()); - ConfigArgsConfigSource.setCliArgs("--db=mariadb", "--db-connect-timeout=30s"); - config = createConfig(); + config = createConfigFromCliArguments("--db=mariadb", "--db-connect-timeout=30s"); assertTrue(DatabasePropertyMappers.isMariadbConnectTimeoutEnabled()); assertEquals("30000", config.getConfigValue(DatabasePropertyMappers.CONNECT_TIMEOUT).getValue()); assertEquals("30s", config.getConfigValue(DatabasePropertyMappers.JDBC_LOGIN_TIMEOUT).getValue()); assertEquals("PT1M", config.getConfigValue(DatabasePropertyMappers.JDBC_ACQUISITION_TIMEOUT).getValue()); // Oracle: - ConfigArgsConfigSource.setCliArgs("--db=oracle"); - config = createConfig(); + config = createConfigFromCliArguments("--db=oracle"); assertTrue(DatabasePropertyMappers.isOracleConnectTimeoutEnabled()); assertEquals("10000", config.getConfigValue(DatabasePropertyMappers.ORACLEDB_CONNECT_TIMEOUT).getValue()); assertEquals("10s", config.getConfigValue(DatabasePropertyMappers.JDBC_LOGIN_TIMEOUT).getValue()); assertEquals("PT20S", config.getConfigValue(DatabasePropertyMappers.JDBC_ACQUISITION_TIMEOUT).getValue()); - - ConfigArgsConfigSource.setCliArgs("--db=oracle", "--db-url-properties=?oracle.net.CONNECT_TIMEOUT=5000"); - - config = createConfig(); + config = createConfigFromCliArguments("--db=oracle", "--db-url-properties=?oracle.net.CONNECT_TIMEOUT=5000"); assertFalse(DatabasePropertyMappers.isOracleConnectTimeoutEnabled()); - ConfigArgsConfigSource.setCliArgs("--db=oracle", "--db-url=jdbc:oracle:thin:@//localhost:1521/keycloak?oracle.net.CONNECT_TIMEOUT=5000"); - config = createConfig(); + config = createConfigFromCliArguments("--db=oracle", "--db-url=jdbc:oracle:thin:@//localhost:1521/keycloak?oracle.net.CONNECT_TIMEOUT=5000"); assertFalse(DatabasePropertyMappers.isOracleConnectTimeoutEnabled()); - ConfigArgsConfigSource.setCliArgs("--db=oracle", "--db-connect-timeout=30s"); - config = createConfig(); + config = createConfigFromCliArguments("--db=oracle", "--db-connect-timeout=30s"); assertTrue(DatabasePropertyMappers.isOracleConnectTimeoutEnabled()); assertEquals("30000", config.getConfigValue(DatabasePropertyMappers.ORACLEDB_CONNECT_TIMEOUT).getValue()); assertEquals("30s", config.getConfigValue(DatabasePropertyMappers.JDBC_LOGIN_TIMEOUT).getValue()); assertEquals("PT1M", config.getConfigValue(DatabasePropertyMappers.JDBC_ACQUISITION_TIMEOUT).getValue()); // MSSQL: - ConfigArgsConfigSource.setCliArgs("--db=mssql"); - config = createConfig(); + config = createConfigFromCliArguments("--db=mssql"); assertTrue(DatabasePropertyMappers.isMssqlLoginTimeoutEnabled()); assertEquals("10", config.getConfigValue(DatabasePropertyMappers.MSSQL_CONNECT_TIMEOUT).getValue()); assertEquals("10s", config.getConfigValue(DatabasePropertyMappers.JDBC_LOGIN_TIMEOUT).getValue()); assertEquals("PT20S", config.getConfigValue(DatabasePropertyMappers.JDBC_ACQUISITION_TIMEOUT).getValue()); - ConfigArgsConfigSource.setCliArgs("--db=mssql", "--db-url-properties=;loginTimeout=20"); - config = createConfig(); + config = createConfigFromCliArguments("--db=mssql", "--db-url-properties=;loginTimeout=20"); assertFalse(DatabasePropertyMappers.isMssqlLoginTimeoutEnabled()); - ConfigArgsConfigSource.setCliArgs("--db=mssql", "--db-url=jdbc:sqlserver://localhost:1433;databaseName=keycloak;loginTimeout=20"); - config = createConfig(); + config = createConfigFromCliArguments("--db=mssql", "--db-url=jdbc:sqlserver://localhost:1433;databaseName=keycloak;loginTimeout=20"); assertFalse(DatabasePropertyMappers.isMssqlLoginTimeoutEnabled()); - ConfigArgsConfigSource.setCliArgs("--db=mssql", "--db-connect-timeout=30s"); - config = createConfig(); + config = createConfigFromCliArguments("--db=mssql", "--db-connect-timeout=30s"); assertTrue(DatabasePropertyMappers.isMssqlLoginTimeoutEnabled()); assertEquals("30", config.getConfigValue(DatabasePropertyMappers.MSSQL_CONNECT_TIMEOUT).getValue()); assertEquals("30s", config.getConfigValue(DatabasePropertyMappers.JDBC_LOGIN_TIMEOUT).getValue()); assertEquals("PT1M", config.getConfigValue(DatabasePropertyMappers.JDBC_ACQUISITION_TIMEOUT).getValue()); // PostgreSQL: - resetConfiguration(); - ConfigArgsConfigSource.setCliArgs("--db=postgres"); - createConfig(); - PropertyMappers.sanitizeDisabledMappers(new Start()); - config = createConfig(); + config = createConfigFromCliArguments("--db=postgres"); assertTrue(DatabasePropertyMappers.isPostgresConnectTimeoutEnabled()); assertEquals("10", config.getConfigValue(DatabasePropertyMappers.CONNECT_TIMEOUT).getValue()); assertEquals("10s", config.getConfigValue(DatabasePropertyMappers.JDBC_LOGIN_TIMEOUT).getValue()); assertEquals("PT20S", config.getConfigValue(DatabasePropertyMappers.JDBC_ACQUISITION_TIMEOUT).getValue()); - resetConfiguration(); - ConfigArgsConfigSource.setCliArgs("--db=postgres", "--db-url-properties=?connectTimeout=5"); - createConfig(); - PropertyMappers.sanitizeDisabledMappers(new Start()); + config = createConfigFromCliArguments("--db=postgres", "--db-url-properties=?connectTimeout=5"); assertFalse(DatabasePropertyMappers.isPostgresConnectTimeoutEnabled()); - resetConfiguration(); - ConfigArgsConfigSource.setCliArgs("--db=postgres", "--db-url=jdbc:postgresql://localhost:5432/keycloak?connectTimeout=5"); - createConfig(); - PropertyMappers.sanitizeDisabledMappers(new Start()); + config = createConfigFromCliArguments("--db=postgres", "--db-url=jdbc:postgresql://localhost:5432/keycloak?connectTimeout=5"); assertFalse(DatabasePropertyMappers.isPostgresConnectTimeoutEnabled()); - resetConfiguration(); - ConfigArgsConfigSource.setCliArgs("--db=postgres", "--db-driver=software.amazon.jdbc.Driver"); - createConfig(); - PropertyMappers.sanitizeDisabledMappers(new Start()); + config = createConfigFromCliArguments("--db=postgres", "--db-driver=software.amazon.jdbc.Driver"); assertFalse(DatabasePropertyMappers.isPostgresConnectTimeoutEnabled()); - resetConfiguration(); - ConfigArgsConfigSource.setCliArgs("--db=postgres", "--db-connect-timeout=30s"); - createConfig(); - PropertyMappers.sanitizeDisabledMappers(new Start()); - config = createConfig(); + config = createConfigFromCliArguments("--db=postgres", "--db-connect-timeout=30s"); assertTrue(DatabasePropertyMappers.isPostgresConnectTimeoutEnabled()); assertEquals("30", config.getConfigValue(DatabasePropertyMappers.CONNECT_TIMEOUT).getValue()); assertEquals("30s", config.getConfigValue(DatabasePropertyMappers.JDBC_LOGIN_TIMEOUT).getValue()); assertEquals("PT1M", config.getConfigValue(DatabasePropertyMappers.JDBC_ACQUISITION_TIMEOUT).getValue()); resetConfiguration(); - ConfigArgsConfigSource.setCliArgs("--db=postgres", "--db-connect-timeout=30s", "--transaction-setup-timeout=30s"); - config = createConfig(); - PropertyMappers.sanitizeDisabledMappers(new Start()); - config = createConfig(); + config = createConfigFromCliArguments("--db=postgres", "--db-connect-timeout=30s", "--transaction-setup-timeout=30s"); assertTrue(DatabasePropertyMappers.isPostgresConnectTimeoutEnabled()); assertEquals("PT30S", config.getConfigValue(DatabasePropertyMappers.JDBC_ACQUISITION_TIMEOUT).getValue()); - resetConfiguration(); - ConfigArgsConfigSource.setCliArgs("--db=postgres", "--db-connect-timeout=30s", "--transaction-setup-timeout=45s"); - config = createConfig(); - PropertyMappers.sanitizeDisabledMappers(new Start()); - config = createConfig(); + config = createConfigFromCliArguments("--db=postgres", "--db-connect-timeout=30s", "--transaction-setup-timeout=45s"); assertTrue(DatabasePropertyMappers.isPostgresConnectTimeoutEnabled()); assertEquals("PT45S", config.getConfigValue(DatabasePropertyMappers.JDBC_ACQUISITION_TIMEOUT).getValue()); - resetConfiguration(); - ConfigArgsConfigSource.setCliArgs("--db=postgres", "--db-connect-timeout=60s", "--transaction-setup-timeout=30s"); - createConfig(); - PropertyMappers.sanitizeDisabledMappers(new Start()); - config = createConfig(); + config = createConfigFromCliArguments("--db=postgres", "--db-connect-timeout=60s", "--transaction-setup-timeout=30s"); assertTrue(DatabasePropertyMappers.isPostgresConnectTimeoutEnabled()); assertEquals("PT1M", config.getConfigValue(DatabasePropertyMappers.JDBC_ACQUISITION_TIMEOUT).getValue()); } From 1276a6ebb57c4dc3990e7be23cfadc7dbec1613c Mon Sep 17 00:00:00 2001 From: Steve Hawkins Date: Tue, 24 Mar 2026 15:42:16 -0400 Subject: [PATCH 2/4] correcting the db synthetic property also removing the disabled mapper interceptor Signed-off-by: Steve Hawkins --- .../keycloak/quarkus/runtime/cli/Picocli.java | 174 +++++++++--------- .../DisabledMappersInterceptor.java | 97 ---------- .../mappers/DatabasePropertyMappers.java | 7 +- .../mappers/PropertyMappers.java | 30 ++- ...io.smallrye.config.ConfigSourceInterceptor | 1 - .../configuration/ConfigurationTest.java | 3 + 6 files changed, 103 insertions(+), 209 deletions(-) delete mode 100644 quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/configuration/DisabledMappersInterceptor.java diff --git a/quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/cli/Picocli.java b/quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/cli/Picocli.java index 2e3e8bb844c8..3b0fe14bd2df 100644 --- a/quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/cli/Picocli.java +++ b/quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/cli/Picocli.java @@ -50,7 +50,6 @@ import org.keycloak.quarkus.runtime.cli.command.WindowsService; import org.keycloak.quarkus.runtime.configuration.ConfigArgsConfigSource; import org.keycloak.quarkus.runtime.configuration.Configuration; -import org.keycloak.quarkus.runtime.configuration.DisabledMappersInterceptor; import org.keycloak.quarkus.runtime.configuration.KcUnmatchedArgumentException; import org.keycloak.quarkus.runtime.configuration.MicroProfileConfigProvider; import org.keycloak.quarkus.runtime.configuration.PropertyMappingInterceptor; @@ -244,109 +243,102 @@ public void validateConfig() { validateBuildtime(); } - final boolean disabledMappersInterceptorEnabled = DisabledMappersInterceptor.isEnabled(); // return to the state before the disable - try { - DisabledMappersInterceptor.disable(); // we want all properties, even disabled ones - - final List ignoredRunTime = new ArrayList<>(); - final Set disabledBuildTime = new LinkedHashSet<>(); - final Set disabledRunTime = new LinkedHashSet<>(); - final Set deprecatedInUse = new LinkedHashSet<>(); - final Set missingOption = new LinkedHashSet<>(); - final Set ambiguousSpi = new LinkedHashSet<>(); - final Set unnecessary = new LinkedHashSet<>(); - final LinkedHashMap secondClassOptions = new LinkedHashMap<>(); - - final Set> disabledMappers = new HashSet<>(); - if (options.includeBuildTime) { - disabledMappers.addAll(PropertyMappers.getDisabledBuildTimeMappers().values()); - } - if (options.includeRuntime) { - disabledMappers.addAll(PropertyMappers.getDisabledRuntimeMappers().values()); - } + final List ignoredRunTime = new ArrayList<>(); + final Set disabledBuildTime = new LinkedHashSet<>(); + final Set disabledRunTime = new LinkedHashSet<>(); + final Set deprecatedInUse = new LinkedHashSet<>(); + final Set missingOption = new LinkedHashSet<>(); + final Set ambiguousSpi = new LinkedHashSet<>(); + final Set unnecessary = new LinkedHashSet<>(); + final LinkedHashMap secondClassOptions = new LinkedHashMap<>(); - // first validate the advertised property names - // - this allows for efficient resolution of wildcard values and checking spi options - Configuration.getPropertyNames().forEach(name -> { - if (name.startsWith(PropertyMappers.KC_SPI_PREFIX)) { - if (!options.includeRuntime) { - checkRuntimeSpiOptions(name, ignoredRunTime); - } - if (PropertyMappers.isMaybeSpiBuildTimeProperty(name)) { - ambiguousSpi.add(name); - } - } - PropertyMapper mapper = PropertyMappers.getMapper(name); - if (mapper == null) { - return; // TODO: need to look for disabled Wildcard mappers + final Set> disabledMappers = new HashSet<>(); + if (options.includeBuildTime) { + disabledMappers.addAll(PropertyMappers.getDisabledBuildTimeMappers().values()); + } + if (options.includeRuntime) { + disabledMappers.addAll(PropertyMappers.getDisabledRuntimeMappers().values()); + } + + // first validate the advertised property names + // - this allows for efficient resolution of wildcard values and checking spi options + Configuration.getPropertyNames().forEach(name -> { + if (name.startsWith(PropertyMappers.KC_SPI_PREFIX)) { + if (!options.includeRuntime) { + checkRuntimeSpiOptions(name, ignoredRunTime); } - var forKey = mapper.forKey(name); - if (!name.equals(forKey.getFrom())) { - ConfigValue value = getUnmappedValue(name); - if (value.getValue() != null && isUserModifiable(value)) { - secondClassOptions.put(name, forKey.getFrom()); - } + if (PropertyMappers.isMaybeSpiBuildTimeProperty(name)) { + ambiguousSpi.add(name); } - if (!mapper.hasWildcard() // non-wildcard options will be validated in the next pass - // validate only canonical mappings to kc. values - no need to consider alternative forms - || !name.startsWith(MicroProfileConfigProvider.NS_KEYCLOAK_PREFIX)) { - return; + } + PropertyMapper mapper = PropertyMappers.getMapper(name); + if (mapper == null) { + return; // TODO: need to look for disabled Wildcard mappers + } + var forKey = mapper.forKey(name); + if (!name.equals(forKey.getFrom())) { + ConfigValue value = getUnmappedValue(name); + if (value.getValue() != null && isUserModifiable(value)) { + secondClassOptions.put(name, forKey.getFrom()); } - validateProperty(abstractCommand, options, ignoredRunTime, disabledBuildTime, disabledRunTime, - deprecatedInUse, missingOption, disabledMappers.contains(mapper), forKey, unnecessary); - }); + } + if (!mapper.hasWildcard() // non-wildcard options will be validated in the next pass + // validate only canonical mappings to kc. values - no need to consider alternative forms + || !name.startsWith(MicroProfileConfigProvider.NS_KEYCLOAK_PREFIX)) { + return; + } + validateProperty(abstractCommand, options, ignoredRunTime, disabledBuildTime, disabledRunTime, + deprecatedInUse, missingOption, disabledMappers.contains(mapper), forKey, unnecessary); + }); - // second pass validate any property mapper not seen in the first pass - // - this will catch required values, anything missing from the property names, or disabled - for (PropertyMapper mapper : PropertyMappers.getMappers()) { - if (!mapper.hasWildcard()) { - validateProperty(abstractCommand, options, ignoredRunTime, disabledBuildTime, disabledRunTime, - deprecatedInUse, missingOption, disabledMappers.contains(mapper), mapper, unnecessary); - } + // second pass validate any property mapper not seen in the first pass + // - this will catch required values, anything missing from the property names, or disabled + for (PropertyMapper mapper : PropertyMappers.getMappers()) { + if (!mapper.hasWildcard()) { + validateProperty(abstractCommand, options, ignoredRunTime, disabledBuildTime, disabledRunTime, + deprecatedInUse, missingOption, disabledMappers.contains(mapper), mapper, unnecessary); } + } - PropertyMappers.getPropertyMapperGroupings().forEach(g -> g.validateConfig(this)); + PropertyMappers.getPropertyMapperGroupings().forEach(g -> g.validateConfig(this)); - // third pass check for disabled mappers - for (PropertyMapper mapper : disabledMappers) { - if (!mapper.hasWildcard()) { - validateProperty(abstractCommand, options, ignoredRunTime, disabledBuildTime, disabledRunTime, - deprecatedInUse, missingOption, disabledMappers.contains(mapper), mapper, unnecessary); - } + // third pass check for disabled mappers + for (PropertyMapper mapper : disabledMappers) { + if (!mapper.hasWildcard()) { + validateProperty(abstractCommand, options, ignoredRunTime, disabledBuildTime, disabledRunTime, + deprecatedInUse, missingOption, disabledMappers.contains(mapper), mapper, unnecessary); } + } - if (!missingOption.isEmpty()) { - throw new PropertyException("The following options are required: \n%s".formatted(String.join("\n", missingOption))); - } - if (!ignoredRunTime.isEmpty()) { - info(format("The following run time options were found, but will be ignored during build time: %s\n", - String.join(", ", ignoredRunTime))); - } + if (!missingOption.isEmpty()) { + throw new PropertyException("The following options are required: \n%s".formatted(String.join("\n", missingOption))); + } + if (!ignoredRunTime.isEmpty()) { + info(format("The following run time options were found, but will be ignored during build time: %s\n", + String.join(", ", ignoredRunTime))); + } - if (!disabledBuildTime.isEmpty()) { - outputDisabledProperties(disabledBuildTime, true); - } else if (!disabledRunTime.isEmpty()) { - outputDisabledProperties(disabledRunTime, false); - } + if (!disabledBuildTime.isEmpty()) { + outputDisabledProperties(disabledBuildTime, true); + } else if (!disabledRunTime.isEmpty()) { + outputDisabledProperties(disabledRunTime, false); + } - if (!deprecatedInUse.isEmpty()) { - warn("The following used options or option values are DEPRECATED and will be removed or their behaviour changed in a future release:\n" + String.join("\n", deprecatedInUse) + "\nConsult the Release Notes for details."); - } - if (!ambiguousSpi.isEmpty()) { - warn("The following SPI options are using the legacy format and are not being treated as build time options. Please use the new format with the appropriate -- separators to resolve this ambiguity: " + String.join("\n", ambiguousSpi)); - } - secondClassOptions.forEach((key, firstClass) -> { - if (Configuration.getConfigValue(firstClass).getConfigSourceName() != null) { - warn("With the first-class option `%s` set, you should remove the usage of `%s`".formatted(firstClass, key)); - } else { - warn("Please use the first-class option `%s` instead of `%s`".formatted(firstClass, key)); - } - }); - if (!unnecessary.isEmpty()) { - info("The following options were specified, but are typically not relevant for this command: " + String.join("\n", unnecessary)); + if (!deprecatedInUse.isEmpty()) { + warn("The following used options or option values are DEPRECATED and will be removed or their behaviour changed in a future release:\n" + String.join("\n", deprecatedInUse) + "\nConsult the Release Notes for details."); + } + if (!ambiguousSpi.isEmpty()) { + warn("The following SPI options are using the legacy format and are not being treated as build time options. Please use the new format with the appropriate -- separators to resolve this ambiguity: " + String.join("\n", ambiguousSpi)); + } + secondClassOptions.forEach((key, firstClass) -> { + if (Configuration.getConfigValue(firstClass).getConfigSourceName() != null) { + warn("With the first-class option `%s` set, you should remove the usage of `%s`".formatted(firstClass, key)); + } else { + warn("Please use the first-class option `%s` instead of `%s`".formatted(firstClass, key)); } - } finally { - DisabledMappersInterceptor.enable(disabledMappersInterceptorEnabled); + }); + if (!unnecessary.isEmpty()) { + info("The following options were specified, but are typically not relevant for this command: " + String.join("\n", unnecessary)); } } diff --git a/quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/configuration/DisabledMappersInterceptor.java b/quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/configuration/DisabledMappersInterceptor.java deleted file mode 100644 index 76e180930ecd..000000000000 --- a/quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/configuration/DisabledMappersInterceptor.java +++ /dev/null @@ -1,97 +0,0 @@ -/* - * Copyright 2024 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.configuration; - -import java.util.Iterator; - -import jakarta.annotation.Priority; - -import org.keycloak.quarkus.runtime.configuration.mappers.PropertyMappers; - -import io.smallrye.config.ConfigSourceInterceptor; -import io.smallrye.config.ConfigSourceInterceptorContext; -import io.smallrye.config.ConfigValue; -import io.smallrye.config.Priorities; -import org.apache.commons.collections4.iterators.FilterIterator; - -import static org.keycloak.quarkus.runtime.configuration.MicroProfileConfigProvider.NS_KEYCLOAK_PREFIX; - -/** - *

This interceptor is responsible for ignoring disabled Keycloak properties - * - *

This interceptor should execute before the {@link PropertyMappingInterceptor} so that disabled properties - * are not mapped to the Quarkus properties. - *

- * The reason for the used priority is to always execute the interceptor before default Application Config Source interceptors - * and before the {@link PropertyMappingInterceptor} - */ -@Priority(Priorities.APPLICATION - 20) -public class DisabledMappersInterceptor implements ConfigSourceInterceptor { - - private static final ThreadLocal ENABLED = ThreadLocal.withInitial(() -> false); - - public static void enable() { - enable(true); - } - - public static void disable() { - enable(false); - } - - public static void enable(boolean enable) { - ENABLED.set(enable); - } - - private boolean isDisabledMapper(String property) { - return property.startsWith(NS_KEYCLOAK_PREFIX) && PropertyMappers.isDisabledMapper(property); - } - - Iterator filterDisabledMappers(Iterator iter) { - return new FilterIterator<>(iter, item -> !isDisabledMapper(item)); - } - - @Override - public Iterator iterateNames(ConfigSourceInterceptorContext context) { - return filterDisabledMappers(context.iterateNames()); - } - - @Override - public ConfigValue getValue(ConfigSourceInterceptorContext context, String name) { - if (isEnabled() && isDisabledMapper(name)) { - return null; - } - return context.proceed(name); - } - - public static boolean isEnabled() { - return Boolean.TRUE.equals(ENABLED.get()); - } - - public static void runWithDisabled(Runnable execution) { - if (!isEnabled()) { - execution.run(); - return; - } - try { - disable(); - execution.run(); - } finally { - enable(); - } - } -} diff --git a/quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/configuration/mappers/DatabasePropertyMappers.java b/quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/configuration/mappers/DatabasePropertyMappers.java index 85794447a999..7632abae13b8 100644 --- a/quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/configuration/mappers/DatabasePropertyMappers.java +++ b/quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/configuration/mappers/DatabasePropertyMappers.java @@ -48,6 +48,7 @@ import static org.keycloak.quarkus.runtime.configuration.mappers.PropertyMapper.fromOption; public final class DatabasePropertyMappers implements PropertyMapperGrouping { + private static final Option SYNTHETIC_RUNTIME_DB_OPTION = DB.toBuilder().synthetic().buildTime(false).build(); public static final String PG_TARGET_SERVER_TYPE = "quarkus.datasource.jdbc.additional-jdbc-properties.targetServerType"; public static final String MSSQL_SEND_STRING_PARAMETER_AS_UNICODE = "quarkus.datasource.jdbc.additional-jdbc-properties.sendStringParametersAsUnicode"; public static final String CONNECT_TIMEOUT = "quarkus.datasource.jdbc.additional-jdbc-properties.connectTimeout"; @@ -90,7 +91,7 @@ public List> getPropertyMappers() { .to(PG_TARGET_SERVER_TYPE) .isEnabled(DatabasePropertyMappers::isPostgresqlTargetServerTypeEnabled) .build(), - fromOption(DB).mapFrom(DB, (name, value, context) -> "false") + fromOption(SYNTHETIC_RUNTIME_DB_OPTION).mapFrom(DB, (name, value, context) -> "false") .to(MSSQL_SEND_STRING_PARAMETER_AS_UNICODE) .isEnabled(DatabasePropertyMappers::isMssqlSendStringParametersAsUnicode) .build(), @@ -231,7 +232,7 @@ public List> getPropertyMappers() { // PostgreSQL setTlsJdbcProperty("sslmode", Map.of(Database.Vendor.POSTGRES, "verify-full")), - fromOption(DB) + fromOption(SYNTHETIC_RUNTIME_DB_OPTION) .mapFrom(DB) .transformer(DatabasePropertyMappers::computePostgresSSLFactory) .to("quarkus.datasource.jdbc.additional-jdbc-properties.sslfactory") @@ -530,7 +531,7 @@ private static String transformOracleProtocol(String datasource, String value, C } private static PropertyMapper setTlsJdbcProperty(String jdbcPropertyKey, Map vendorValues) { - return fromOption(DB) + return fromOption(SYNTHETIC_RUNTIME_DB_OPTION) .mapFrom(DB) .transformer((name, value, context) -> computeTlsProperty(vendorValues, name, value, jdbcPropertyKey)) .to("quarkus.datasource.jdbc.additional-jdbc-properties." + jdbcPropertyKey) diff --git a/quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/configuration/mappers/PropertyMappers.java b/quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/configuration/mappers/PropertyMappers.java index 93c4452ff4c6..4ed01128c5cc 100644 --- a/quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/configuration/mappers/PropertyMappers.java +++ b/quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/configuration/mappers/PropertyMappers.java @@ -25,7 +25,6 @@ import org.keycloak.quarkus.runtime.cli.Picocli; import org.keycloak.quarkus.runtime.cli.PropertyException; import org.keycloak.quarkus.runtime.cli.command.AbstractCommand; -import org.keycloak.quarkus.runtime.configuration.DisabledMappersInterceptor; import org.keycloak.quarkus.runtime.configuration.MicroProfileConfigProvider; import org.keycloak.quarkus.runtime.configuration.NestedPropertyMappingInterceptor; import org.keycloak.quarkus.runtime.configuration.PersistedConfigSource; @@ -311,25 +310,22 @@ public Set> getWildcardMappers() { } public void sanitizeDisabledMappers(AbstractCommand command) { - DisabledMappersInterceptor.runWithDisabled(() -> { // We need to have the whole configuration available - - // Initialize profile in order to check state of features. Disable Persisted CS for re-augmentation - if (isRebuildCheck()) { - PersistedConfigSource.getInstance().runWithDisabled(Environment::getCurrentOrCreateFeatureProfile); - } else { - Environment.getCurrentOrCreateFeatureProfile(); - if (!command.shouldStart()) { - // this will use the deferred logger, which means it may not be seen in some circumstances - Profile.getInstance().logUnsupportedFeatures(); - } + // Initialize profile in order to check state of features. Disable Persisted CS for re-augmentation + if (isRebuildCheck()) { + PersistedConfigSource.getInstance().runWithDisabled(Environment::getCurrentOrCreateFeatureProfile); + } else { + Environment.getCurrentOrCreateFeatureProfile(); + if (!command.shouldStart()) { + // this will use the deferred logger, which means it may not be seen in some circumstances + Profile.getInstance().logUnsupportedFeatures(); } + } - sanitizeMappers(buildTimeMappers, disabledBuildTimeMappers, command); - sanitizeMappers(runtimeTimeMappers, disabledRuntimeMappers, command); + sanitizeMappers(buildTimeMappers, disabledBuildTimeMappers, command); + sanitizeMappers(runtimeTimeMappers, disabledRuntimeMappers, command); - entrySet().stream().filter(e -> e.getValue().size() > 1).findFirst().ifPresent(e -> { - throw new PropertyException(String.format("Duplicated mapper for key '%s'.", e.getKey())); - }); + entrySet().stream().filter(e -> e.getValue().size() > 1).findFirst().ifPresent(e -> { + throw new PropertyException(String.format("Duplicated mapper for key '%s'.", e.getKey())); }); } diff --git a/quarkus/runtime/src/main/resources/META-INF/services/io.smallrye.config.ConfigSourceInterceptor b/quarkus/runtime/src/main/resources/META-INF/services/io.smallrye.config.ConfigSourceInterceptor index 8856d3d62bbe..ea84cd486bd4 100644 --- a/quarkus/runtime/src/main/resources/META-INF/services/io.smallrye.config.ConfigSourceInterceptor +++ b/quarkus/runtime/src/main/resources/META-INF/services/io.smallrye.config.ConfigSourceInterceptor @@ -17,4 +17,3 @@ org.keycloak.quarkus.runtime.configuration.PropertyMappingInterceptor org.keycloak.quarkus.runtime.configuration.NestedPropertyMappingInterceptor -org.keycloak.quarkus.runtime.configuration.DisabledMappersInterceptor \ No newline at end of file diff --git a/quarkus/runtime/src/test/java/org/keycloak/quarkus/runtime/configuration/ConfigurationTest.java b/quarkus/runtime/src/test/java/org/keycloak/quarkus/runtime/configuration/ConfigurationTest.java index 9c28c82d44c5..8c9b6b2da392 100644 --- a/quarkus/runtime/src/test/java/org/keycloak/quarkus/runtime/configuration/ConfigurationTest.java +++ b/quarkus/runtime/src/test/java/org/keycloak/quarkus/runtime/configuration/ConfigurationTest.java @@ -433,6 +433,9 @@ public void testDatabaseProperties() { // sendStringParametersAsUnicode already present in db-url -> disabled config = createConfigFromCliArguments("--db=mssql", "--db-url=jdbc:sqlserver://localhost:1433;databaseName=keycloak;sendStringParametersAsUnicode=false"); assertFalse(DatabasePropertyMappers.isMssqlSendStringParametersAsUnicode()); + assertTrue(StreamSupport.stream(config.getPropertyNames().spliterator(), false) + .noneMatch(DatabasePropertyMappers.MSSQL_SEND_STRING_PARAMETER_AS_UNICODE::equals)); + assertNull(config.getConfigValue(DatabasePropertyMappers.MSSQL_SEND_STRING_PARAMETER_AS_UNICODE).getValue()); // other db vendor -> disabled (already covered implicitly but good to be explicit) config = createConfigFromCliArguments("--db=postgres"); From c16f63869d3e2adbfa35c6deb2427236cf7bb056 Mon Sep 17 00:00:00 2001 From: Steve Hawkins Date: Wed, 25 Mar 2026 21:56:20 -0400 Subject: [PATCH 3/4] addressing review comments Signed-off-by: Steve Hawkins --- .../java/org/keycloak/config/DatabaseOptions.java | 5 +++-- .../src/main/java/org/keycloak/config/Option.java | 2 +- .../org/keycloak/quarkus/runtime/cli/Picocli.java | 12 ++++++------ .../keycloak/quarkus/runtime/cli/PicocliTest.java | 6 ++++++ 4 files changed, 16 insertions(+), 9 deletions(-) diff --git a/quarkus/config-api/src/main/java/org/keycloak/config/DatabaseOptions.java b/quarkus/config-api/src/main/java/org/keycloak/config/DatabaseOptions.java index 5fa70520e3d6..ef13f87c09de 100644 --- a/quarkus/config-api/src/main/java/org/keycloak/config/DatabaseOptions.java +++ b/quarkus/config-api/src/main/java/org/keycloak/config/DatabaseOptions.java @@ -7,6 +7,7 @@ import java.util.Map; import java.util.Optional; import java.util.function.Consumer; +import java.util.stream.Stream; import org.keycloak.config.database.Database; @@ -163,7 +164,7 @@ public static final class Datasources { * Options that have their sibling for a named datasource * Example: for `db-dialect`, `db-dialect-` is created */ - public static final List OPTIONS_DATASOURCES = List.of( + public static final List OPTIONS_DATASOURCES = Stream.of( DB_DIALECT, DB_DRIVER, DB, @@ -185,7 +186,7 @@ public static final class Datasources { DB_TLS_TRUST_STORE_PASSWORD, DB_TLS_TRUST_STORE_TYPE, DB_ORACLE_TLS_TRANSPORT - ).stream().map(Option::getKey).toList(); + ).map(Option::getKey).toList(); /** * In order to avoid ambiguity, we need to have unique option names for wildcard options. diff --git a/quarkus/config-api/src/main/java/org/keycloak/config/Option.java b/quarkus/config-api/src/main/java/org/keycloak/config/Option.java index 16f98ce62e30..3b032cb8537a 100644 --- a/quarkus/config-api/src/main/java/org/keycloak/config/Option.java +++ b/quarkus/config-api/src/main/java/org/keycloak/config/Option.java @@ -22,7 +22,7 @@ public class Option { private final DeprecatedMetadata deprecatedMetadata; private final Set connectedOptions; private String wildcardKey; - private boolean synthetic; + private final boolean synthetic; public Option(Class type, String key, OptionCategory category, boolean hidden, boolean buildTime, String description, Optional defaultValue, List expectedValues, boolean strictExpectedValues, boolean caseInsensitiveExpectedValues, diff --git a/quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/cli/Picocli.java b/quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/cli/Picocli.java index 3b0fe14bd2df..80ec67586fa0 100644 --- a/quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/cli/Picocli.java +++ b/quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/cli/Picocli.java @@ -272,7 +272,7 @@ public void validateConfig() { } } PropertyMapper mapper = PropertyMappers.getMapper(name); - if (mapper == null) { + if (mapper == null || mapper.getOption().isSynthetic()) { return; // TODO: need to look for disabled Wildcard mappers } var forKey = mapper.forKey(name); @@ -288,15 +288,15 @@ public void validateConfig() { return; } validateProperty(abstractCommand, options, ignoredRunTime, disabledBuildTime, disabledRunTime, - deprecatedInUse, missingOption, disabledMappers.contains(mapper), forKey, unnecessary); + deprecatedInUse, missingOption, false, forKey, unnecessary); }); // second pass validate any property mapper not seen in the first pass // - this will catch required values, anything missing from the property names, or disabled for (PropertyMapper mapper : PropertyMappers.getMappers()) { - if (!mapper.hasWildcard()) { + if (!mapper.hasWildcard() && !mapper.getOption().isSynthetic()) { validateProperty(abstractCommand, options, ignoredRunTime, disabledBuildTime, disabledRunTime, - deprecatedInUse, missingOption, disabledMappers.contains(mapper), mapper, unnecessary); + deprecatedInUse, missingOption, false, mapper, unnecessary); } } @@ -304,9 +304,9 @@ public void validateConfig() { // third pass check for disabled mappers for (PropertyMapper mapper : disabledMappers) { - if (!mapper.hasWildcard()) { + if (!mapper.hasWildcard() && !mapper.getOption().isSynthetic()) { validateProperty(abstractCommand, options, ignoredRunTime, disabledBuildTime, disabledRunTime, - deprecatedInUse, missingOption, disabledMappers.contains(mapper), mapper, unnecessary); + deprecatedInUse, missingOption, true, mapper, unnecessary); } } diff --git a/quarkus/runtime/src/test/java/org/keycloak/quarkus/runtime/cli/PicocliTest.java b/quarkus/runtime/src/test/java/org/keycloak/quarkus/runtime/cli/PicocliTest.java index eb4b01027105..d3cd8f1e5b7c 100644 --- a/quarkus/runtime/src/test/java/org/keycloak/quarkus/runtime/cli/PicocliTest.java +++ b/quarkus/runtime/src/test/java/org/keycloak/quarkus/runtime/cli/PicocliTest.java @@ -483,6 +483,12 @@ public void testReaugFromDevToProdExport() { assertEquals("prod", nonRunningPicocli.buildProps.getProperty(org.keycloak.common.util.Environment.PROFILE)); } + @Test + public void testSyntheticValidationSkipped() { + NonRunningPicocli nonRunningPicocli = build("build", "--db=postgres"); + assertFalse(nonRunningPicocli.getOutString(), nonRunningPicocli.getOutString().contains("kc.db")); + } + @Test public void testOptimizedReaugmentationMessage() { build("build", "--db=dev-file"); From a788e79a05c0ce54c2c02c1346c020b670057df4 Mon Sep 17 00:00:00 2001 From: Steve Hawkins Date: Fri, 10 Apr 2026 16:44:01 -0400 Subject: [PATCH 4/4] moving datasource mappers using isEnabled out of wildcards Signed-off-by: Steve Hawkins --- .../mappers/DatabasePropertyMappers.java | 96 ++++++++++--------- .../DatasourcesConfigurationTest.java | 5 + 2 files changed, 56 insertions(+), 45 deletions(-) diff --git a/quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/configuration/mappers/DatabasePropertyMappers.java b/quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/configuration/mappers/DatabasePropertyMappers.java index 7632abae13b8..986fcb61681f 100644 --- a/quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/configuration/mappers/DatabasePropertyMappers.java +++ b/quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/configuration/mappers/DatabasePropertyMappers.java @@ -87,14 +87,6 @@ public List> getPropertyMappers() { .mapFrom(DB, DatabasePropertyMappers::getDatabaseUrl) .paramLabel("jdbc-url") .build(), - fromOption(DatabaseOptions.DB_POSTGRESQL_TARGET_SERVER_TYPE) - .to(PG_TARGET_SERVER_TYPE) - .isEnabled(DatabasePropertyMappers::isPostgresqlTargetServerTypeEnabled) - .build(), - fromOption(SYNTHETIC_RUNTIME_DB_OPTION).mapFrom(DB, (name, value, context) -> "false") - .to(MSSQL_SEND_STRING_PARAMETER_AS_UNICODE) - .isEnabled(DatabasePropertyMappers::isMssqlSendStringParametersAsUnicode) - .build(), fromOption(DatabaseOptions.DB_CONNECT_TIMEOUT) .to(JDBC_LOGIN_TIMEOUT) .validator(DatabasePropertyMappers::validateConnectTimeout) @@ -104,42 +96,6 @@ public List> getPropertyMappers() { .to(JDBC_ACQUISITION_TIMEOUT) .mapFrom(DatabaseOptions.DB_CONNECT_TIMEOUT, (name, value, context) -> computeAcquisitionTimeout(value)) .build(), - fromOption(DatabaseOptions.DB_CONNECT_TIMEOUT) - .to(CONNECT_TIMEOUT) - .mapFrom(DatabaseOptions.DB_CONNECT_TIMEOUT, (name, value, context) - -> durationToMillis(value)) - .isEnabled(DatabasePropertyMappers::isMysqlConnectTimeoutEnabled) - .build(), - fromOption(DatabaseOptions.DB_CONNECT_TIMEOUT) - .to(CONNECT_TIMEOUT) - .mapFrom(DatabaseOptions.DB_CONNECT_TIMEOUT, (name, value, context) - -> durationToMillis(value)) - .isEnabled(DatabasePropertyMappers::isMariadbConnectTimeoutEnabled) - .build(), - fromOption(DatabaseOptions.DB_CONNECT_TIMEOUT) - .to(ORACLEDB_CONNECT_TIMEOUT) - .mapFrom(DatabaseOptions.DB_CONNECT_TIMEOUT, (name, value, context) - -> durationToMillis(value)) - .isEnabled(DatabasePropertyMappers::isOracleConnectTimeoutEnabled) - .build(), - fromOption(DatabaseOptions.DB_CONNECT_TIMEOUT) - .to(MSSQL_CONNECT_TIMEOUT) - .mapFrom(DatabaseOptions.DB_CONNECT_TIMEOUT, (name, value, context) - -> durationToSeconds(value)) - .isEnabled(DatabasePropertyMappers::isMssqlLoginTimeoutEnabled) - .build(), - fromOption(DatabaseOptions.DB_CONNECT_TIMEOUT) - .to(CONNECT_TIMEOUT) - .mapFrom(DatabaseOptions.DB_CONNECT_TIMEOUT, (name, value, context) - -> durationToSeconds(value)) - .isEnabled(DatabasePropertyMappers::isPostgresConnectTimeoutEnabled) - .build(), - fromOption(DatabaseOptions.DB_CONNECT_TIMEOUT) - .to(CONNECT_TIMEOUT) - .mapFrom(DatabaseOptions.DB_CONNECT_TIMEOUT, (name, value, context) - -> durationToMillis(value)) - .isEnabled(DatabasePropertyMappers::isTidbConnectTimeoutEnabled) - .build(), fromOption(DatabaseOptions.DB_URL_HOST) .paramLabel("hostname") .build(), @@ -240,12 +196,62 @@ public List> getPropertyMappers() { setInputTlsJdbcProperty(DB_TLS_TRUST_STORE_FILE, "sslrootcert", EnumSet.of(Database.Vendor.POSTGRES)) ); - return appendDatasourceMappers(mappers, Map.of( + List> result = appendDatasourceMappers(mappers, Map.of( // Inherit options from the DB mappers DB, PropertyMapper.Builder::removeMapFrom, DB_POOL_INITIAL_SIZE, mapper -> mapper.mapFrom(DB_POOL_INITIAL_SIZE), DB_POOL_MAX_SIZE, mapper -> mapper.mapFrom(DB_POOL_MAX_SIZE) )); + + // finally add mappers that utilize isEnabled - that won't work as expected for multiple datasources + // so these will only affect the primary datasource + result.addAll(List.of( + fromOption(DatabaseOptions.DB_POSTGRESQL_TARGET_SERVER_TYPE) + .to(PG_TARGET_SERVER_TYPE) + .isEnabled(DatabasePropertyMappers::isPostgresqlTargetServerTypeEnabled) + .build(), + fromOption(SYNTHETIC_RUNTIME_DB_OPTION).mapFrom(DB, (name, value, context) -> "false") + .to(MSSQL_SEND_STRING_PARAMETER_AS_UNICODE) + .isEnabled(DatabasePropertyMappers::isMssqlSendStringParametersAsUnicode) + .build(), + fromOption(DatabaseOptions.DB_CONNECT_TIMEOUT) + .to(CONNECT_TIMEOUT) + .mapFrom(DatabaseOptions.DB_CONNECT_TIMEOUT, (name, value, context) + -> durationToMillis(value)) + .isEnabled(DatabasePropertyMappers::isMysqlConnectTimeoutEnabled) + .build(), + fromOption(DatabaseOptions.DB_CONNECT_TIMEOUT) + .to(CONNECT_TIMEOUT) + .mapFrom(DatabaseOptions.DB_CONNECT_TIMEOUT, (name, value, context) + -> durationToMillis(value)) + .isEnabled(DatabasePropertyMappers::isMariadbConnectTimeoutEnabled) + .build(), + fromOption(DatabaseOptions.DB_CONNECT_TIMEOUT) + .to(ORACLEDB_CONNECT_TIMEOUT) + .mapFrom(DatabaseOptions.DB_CONNECT_TIMEOUT, (name, value, context) + -> durationToMillis(value)) + .isEnabled(DatabasePropertyMappers::isOracleConnectTimeoutEnabled) + .build(), + fromOption(DatabaseOptions.DB_CONNECT_TIMEOUT) + .to(MSSQL_CONNECT_TIMEOUT) + .mapFrom(DatabaseOptions.DB_CONNECT_TIMEOUT, (name, value, context) + -> durationToSeconds(value)) + .isEnabled(DatabasePropertyMappers::isMssqlLoginTimeoutEnabled) + .build(), + fromOption(DatabaseOptions.DB_CONNECT_TIMEOUT) + .to(CONNECT_TIMEOUT) + .mapFrom(DatabaseOptions.DB_CONNECT_TIMEOUT, (name, value, context) + -> durationToSeconds(value)) + .isEnabled(DatabasePropertyMappers::isPostgresConnectTimeoutEnabled) + .build(), + fromOption(DatabaseOptions.DB_CONNECT_TIMEOUT) + .to(CONNECT_TIMEOUT) + .mapFrom(DatabaseOptions.DB_CONNECT_TIMEOUT, (name, value, context) + -> durationToMillis(value)) + .isEnabled(DatabasePropertyMappers::isTidbConnectTimeoutEnabled) + .build() + )); + return result; } @Override diff --git a/quarkus/runtime/src/test/java/org/keycloak/quarkus/runtime/configuration/DatasourcesConfigurationTest.java b/quarkus/runtime/src/test/java/org/keycloak/quarkus/runtime/configuration/DatasourcesConfigurationTest.java index 158a9bccb5af..f1a3171562f1 100644 --- a/quarkus/runtime/src/test/java/org/keycloak/quarkus/runtime/configuration/DatasourcesConfigurationTest.java +++ b/quarkus/runtime/src/test/java/org/keycloak/quarkus/runtime/configuration/DatasourcesConfigurationTest.java @@ -595,6 +595,11 @@ private static void doDatabaseTlsOptionTest(String dbKind, String dbUrl, assertNullAdditionalJdbcProperty(config, "users", trustStorePasswordProperty); assertNullAdditionalJdbcProperty(config, "users", trustStoreTypeProperty); + // MSSQL-specific property should not leak into non-MSSQL named datasources + if (!"mssql".equals(dbKind)) { + assertNullAdditionalJdbcProperty(config, "users", "sendStringParametersAsUnicode"); + } + // oracle has a different protocol for TLS if ("oracle".equals(dbKind)) { dbUrl = dbUrl.replace("jdbc:oracle:thin:@//", "jdbc:oracle:thin:@tcps://");