From 6c288c9deff4c36e01b7e2c1a04cfa9c3c90099c Mon Sep 17 00:00:00 2001 From: Moritz Halbritter Date: Wed, 20 Aug 2025 15:17:45 +0200 Subject: [PATCH] Improve null-safety of module/spring-boot-r2dbc See gh-46926 --- .../autoconfigure/R2dbcAutoConfiguration.java | 39 +++++++++++++------ .../docker/compose/ClickHouseEnvironment.java | 14 ++++++- .../docker/compose/MariaDbEnvironment.java | 5 ++- .../docker/compose/MySqlEnvironment.java | 7 +++- .../docker/compose/OracleEnvironment.java | 14 ++++++- .../docker/compose/PostgresEnvironment.java | 5 ++- 6 files changed, 65 insertions(+), 19 deletions(-) diff --git a/module/spring-boot-r2dbc/src/main/java/org/springframework/boot/r2dbc/autoconfigure/R2dbcAutoConfiguration.java b/module/spring-boot-r2dbc/src/main/java/org/springframework/boot/r2dbc/autoconfigure/R2dbcAutoConfiguration.java index d9ae0d6bb2b..7ffb70e70d7 100644 --- a/module/spring-boot-r2dbc/src/main/java/org/springframework/boot/r2dbc/autoconfigure/R2dbcAutoConfiguration.java +++ b/module/spring-boot-r2dbc/src/main/java/org/springframework/boot/r2dbc/autoconfigure/R2dbcAutoConfiguration.java @@ -16,7 +16,6 @@ package org.springframework.boot.r2dbc.autoconfigure; -import java.util.function.Predicate; import java.util.function.Supplier; import io.r2dbc.spi.ConnectionFactory; @@ -34,6 +33,7 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnResource; import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Import; +import org.springframework.util.Assert; import org.springframework.util.StringUtils; /** @@ -72,26 +72,43 @@ public final class R2dbcAutoConfiguration { @Override public ConnectionFactoryOptions getConnectionFactoryOptions() { - ConnectionFactoryOptions urlOptions = ConnectionFactoryOptions.parse(this.properties.getUrl()); + String url = this.properties.getUrl(); + Assert.state(url != null, "'url' must not be null"); + ConnectionFactoryOptions urlOptions = ConnectionFactoryOptions.parse(url); Builder optionsBuilder = urlOptions.mutate(); - configureIf(optionsBuilder, urlOptions, ConnectionFactoryOptions.USER, this.properties::getUsername, - StringUtils::hasText); - configureIf(optionsBuilder, urlOptions, ConnectionFactoryOptions.PASSWORD, this.properties::getPassword, - StringUtils::hasText); - configureIf(optionsBuilder, urlOptions, ConnectionFactoryOptions.DATABASE, - () -> determineDatabaseName(this.properties), StringUtils::hasText); + configureUser(optionsBuilder, urlOptions); + configurePassword(optionsBuilder, urlOptions); + configureDatabase(optionsBuilder, urlOptions); this.properties.getProperties().forEach((key, value) -> optionsBuilder.option(Option.valueOf(key), value)); return optionsBuilder.build(); } + // Lambda isn't detected with the correct nullability + @SuppressWarnings("NullAway") + private void configureDatabase(Builder optionsBuilder, ConnectionFactoryOptions urlOptions) { + configureIf(optionsBuilder, urlOptions, ConnectionFactoryOptions.DATABASE, + () -> determineDatabaseName(this.properties)); + } + + // Lambda isn't detected with the correct nullability + @SuppressWarnings("NullAway") + private void configurePassword(Builder optionsBuilder, ConnectionFactoryOptions urlOptions) { + configureIf(optionsBuilder, urlOptions, ConnectionFactoryOptions.PASSWORD, this.properties::getPassword); + } + + // Lambda isn't detected with the correct nullability + @SuppressWarnings("NullAway") + private void configureUser(Builder optionsBuilder, ConnectionFactoryOptions urlOptions) { + configureIf(optionsBuilder, urlOptions, ConnectionFactoryOptions.USER, this.properties::getUsername); + } + private void configureIf(Builder optionsBuilder, - ConnectionFactoryOptions originalOptions, Option option, Supplier valueSupplier, - Predicate setIf) { + ConnectionFactoryOptions originalOptions, Option option, Supplier<@Nullable T> valueSupplier) { if (originalOptions.hasOption(option)) { return; } T value = valueSupplier.get(); - if (setIf.test(value)) { + if (StringUtils.hasText(value)) { optionsBuilder.option(option, value); } } diff --git a/module/spring-boot-r2dbc/src/main/java/org/springframework/boot/r2dbc/docker/compose/ClickHouseEnvironment.java b/module/spring-boot-r2dbc/src/main/java/org/springframework/boot/r2dbc/docker/compose/ClickHouseEnvironment.java index c71c7d40251..7fe1f2f9732 100644 --- a/module/spring-boot-r2dbc/src/main/java/org/springframework/boot/r2dbc/docker/compose/ClickHouseEnvironment.java +++ b/module/spring-boot-r2dbc/src/main/java/org/springframework/boot/r2dbc/docker/compose/ClickHouseEnvironment.java @@ -37,9 +37,19 @@ class ClickHouseEnvironment { private final String database; ClickHouseEnvironment(Map env) { - this.username = env.getOrDefault("CLICKHOUSE_USER", "default"); + this.username = extractUsername(env); this.password = extractPassword(env); - this.database = env.getOrDefault("CLICKHOUSE_DB", "default"); + this.database = extractDatabase(env); + } + + private static String extractDatabase(Map env) { + String result = env.get("CLICKHOUSE_DB"); + return (result != null) ? result : "default"; + } + + private static String extractUsername(Map env) { + String result = env.get("CLICKHOUSE_USER"); + return (result != null) ? result : "default"; } private String extractPassword(Map env) { diff --git a/module/spring-boot-r2dbc/src/main/java/org/springframework/boot/r2dbc/docker/compose/MariaDbEnvironment.java b/module/spring-boot-r2dbc/src/main/java/org/springframework/boot/r2dbc/docker/compose/MariaDbEnvironment.java index e8ab53f69ed..436647aef31 100644 --- a/module/spring-boot-r2dbc/src/main/java/org/springframework/boot/r2dbc/docker/compose/MariaDbEnvironment.java +++ b/module/spring-boot-r2dbc/src/main/java/org/springframework/boot/r2dbc/docker/compose/MariaDbEnvironment.java @@ -47,7 +47,10 @@ class MariaDbEnvironment { private String extractUsername(Map env) { String user = env.get("MARIADB_USER"); - return (user != null) ? user : env.getOrDefault("MYSQL_USER", "root"); + if (user == null) { + user = env.get("MYSQL_USER"); + } + return (user != null) ? user : "root"; } private String extractPassword(Map env) { diff --git a/module/spring-boot-r2dbc/src/main/java/org/springframework/boot/r2dbc/docker/compose/MySqlEnvironment.java b/module/spring-boot-r2dbc/src/main/java/org/springframework/boot/r2dbc/docker/compose/MySqlEnvironment.java index 294d05553a7..fb70d43d5e0 100644 --- a/module/spring-boot-r2dbc/src/main/java/org/springframework/boot/r2dbc/docker/compose/MySqlEnvironment.java +++ b/module/spring-boot-r2dbc/src/main/java/org/springframework/boot/r2dbc/docker/compose/MySqlEnvironment.java @@ -40,11 +40,16 @@ class MySqlEnvironment { private final String database; MySqlEnvironment(Map env) { - this.username = env.getOrDefault("MYSQL_USER", "root"); + this.username = extractUsername(env); this.password = extractPassword(env); this.database = extractDatabase(env); } + private static String extractUsername(Map env) { + String result = env.get("MYSQL_USER"); + return (result != null) ? result : "root"; + } + private String extractPassword(Map env) { Assert.state(!env.containsKey("MYSQL_RANDOM_ROOT_PASSWORD"), "MYSQL_RANDOM_ROOT_PASSWORD is not supported"); boolean allowEmpty = env.containsKey("MYSQL_ALLOW_EMPTY_PASSWORD") || env.containsKey("ALLOW_EMPTY_PASSWORD"); diff --git a/module/spring-boot-r2dbc/src/main/java/org/springframework/boot/r2dbc/docker/compose/OracleEnvironment.java b/module/spring-boot-r2dbc/src/main/java/org/springframework/boot/r2dbc/docker/compose/OracleEnvironment.java index 5077ae97962..0840928b024 100644 --- a/module/spring-boot-r2dbc/src/main/java/org/springframework/boot/r2dbc/docker/compose/OracleEnvironment.java +++ b/module/spring-boot-r2dbc/src/main/java/org/springframework/boot/r2dbc/docker/compose/OracleEnvironment.java @@ -37,9 +37,19 @@ class OracleEnvironment { private final String database; OracleEnvironment(Map env, String defaultDatabase) { - this.username = env.getOrDefault("APP_USER", "system"); + this.username = extractUsername(env); this.password = extractPassword(env); - this.database = env.getOrDefault("ORACLE_DATABASE", defaultDatabase); + this.database = extractDatabase(env, defaultDatabase); + } + + private static String extractDatabase(Map env, String defaultDatabase) { + String result = env.get("ORACLE_DATABASE"); + return (result != null) ? result : defaultDatabase; + } + + private static String extractUsername(Map env) { + String result = env.get("APP_USER"); + return (result != null) ? result : "system"; } private String extractPassword(Map env) { diff --git a/module/spring-boot-r2dbc/src/main/java/org/springframework/boot/r2dbc/docker/compose/PostgresEnvironment.java b/module/spring-boot-r2dbc/src/main/java/org/springframework/boot/r2dbc/docker/compose/PostgresEnvironment.java index 1f79d9510cc..1c2b8ba5e60 100644 --- a/module/spring-boot-r2dbc/src/main/java/org/springframework/boot/r2dbc/docker/compose/PostgresEnvironment.java +++ b/module/spring-boot-r2dbc/src/main/java/org/springframework/boot/r2dbc/docker/compose/PostgresEnvironment.java @@ -57,8 +57,9 @@ class PostgresEnvironment { private String extract(Map env, String[] keys, String defaultValue) { for (String key : keys) { - if (env.containsKey(key)) { - return env.get(key); + String value = env.get(key); + if (value != null) { + return value; } } return defaultValue;