diff --git a/module/spring-boot-jdbc/src/main/java/org/springframework/boot/jdbc/DataSourceBuilder.java b/module/spring-boot-jdbc/src/main/java/org/springframework/boot/jdbc/DataSourceBuilder.java index 1006963dec2..e57a967ad23 100644 --- a/module/spring-boot-jdbc/src/main/java/org/springframework/boot/jdbc/DataSourceBuilder.java +++ b/module/spring-boot-jdbc/src/main/java/org/springframework/boot/jdbc/DataSourceBuilder.java @@ -35,12 +35,14 @@ import oracle.ucp.jdbc.PoolDataSource; import oracle.ucp.jdbc.PoolDataSourceImpl; import org.apache.commons.dbcp2.BasicDataSource; import org.h2.jdbcx.JdbcDataSource; +import org.jspecify.annotations.Nullable; import org.postgresql.ds.PGSimpleDataSource; import org.vibur.dbcp.ViburDBCPDataSource; import org.springframework.beans.BeanUtils; import org.springframework.core.ResolvableType; import org.springframework.jdbc.datasource.SimpleDriverDataSource; +import org.springframework.lang.Contract; import org.springframework.util.Assert; import org.springframework.util.ClassUtils; import org.springframework.util.ReflectionUtils; @@ -89,15 +91,15 @@ import org.springframework.util.StringUtils; */ public final class DataSourceBuilder { - private final ClassLoader classLoader; + private final @Nullable ClassLoader classLoader; private final Map values = new HashMap<>(); - private Class type; + private @Nullable Class type; - private final DataSource deriveFrom; + private final @Nullable DataSource deriveFrom; - private DataSourceBuilder(ClassLoader classLoader) { + private DataSourceBuilder(@Nullable ClassLoader classLoader) { this.classLoader = classLoader; this.deriveFrom = null; } @@ -117,7 +119,7 @@ public final class DataSourceBuilder { * @return this builder */ @SuppressWarnings("unchecked") - public DataSourceBuilder type(Class type) { + public DataSourceBuilder type(@Nullable Class type) { this.type = (Class) type; return (DataSourceBuilder) this; } @@ -147,7 +149,7 @@ public final class DataSourceBuilder { * @param username the user name * @return this builder */ - public DataSourceBuilder username(String username) { + public DataSourceBuilder username(@Nullable String username) { set(DataSourceProperty.USERNAME, username); return this; } @@ -157,12 +159,12 @@ public final class DataSourceBuilder { * @param password the password * @return this builder */ - public DataSourceBuilder password(String password) { + public DataSourceBuilder password(@Nullable String password) { set(DataSourceProperty.PASSWORD, password); return this; } - private void set(DataSourceProperty property, String value) { + private void set(DataSourceProperty property, @Nullable String value) { this.values.put(property, value); } @@ -178,7 +180,8 @@ public final class DataSourceBuilder { Set applied = new HashSet<>(); for (DataSourceProperty property : DataSourceProperty.values()) { String value = this.values.get(property); - if (value == null && deriveFromProperties != null && properties.canSet(property)) { + if (value == null && deriveFromProperties != null && this.deriveFrom != null + && properties.canSet(property)) { value = deriveFromProperties.get(this.deriveFrom, property); } if (value != null) { @@ -193,14 +196,14 @@ public final class DataSourceBuilder { DatabaseDriver driver = DatabaseDriver.fromJdbcUrl(url); String driverClassName = driver.getDriverClassName(); if (driverClassName != null) { - properties.set(dataSource, DataSourceProperty.DRIVER_CLASS_NAME, driver.getDriverClassName()); + properties.set(dataSource, DataSourceProperty.DRIVER_CLASS_NAME, driverClassName); } } return dataSource; } @SuppressWarnings("unchecked") - private DataSourceProperties getDeriveFromProperties() { + private @Nullable DataSourceProperties getDeriveFromProperties() { if (this.deriveFrom == null) { return null; } @@ -220,7 +223,7 @@ public final class DataSourceBuilder { * @param classLoader the classloader used to discover preferred settings * @return a new {@link DataSource} builder instance */ - public static DataSourceBuilder create(ClassLoader classLoader) { + public static DataSourceBuilder create(@Nullable ClassLoader classLoader) { return new DataSourceBuilder<>(classLoader); } @@ -258,7 +261,7 @@ public final class DataSourceBuilder { * @param classLoader the classloader used to discover preferred settings * @return the preferred {@link DataSource} type */ - public static Class findType(ClassLoader classLoader) { + public static @Nullable Class findType(@Nullable ClassLoader classLoader) { MappedDataSourceProperties mappings = MappedDataSourceProperties.forType(classLoader, null); return (mappings != null) ? mappings.getDataSourceInstanceType() : null; } @@ -294,15 +297,15 @@ public final class DataSourceBuilder { return this.names[0]; } - Method findSetter(Class type) { + @Nullable Method findSetter(Class type) { return findMethod("set", type, String.class); } - Method findGetter(Class type) { + @Nullable Method findGetter(Class type) { return findMethod("get", type); } - private Method findMethod(String prefix, Class type, Class... paramTypes) { + private @Nullable Method findMethod(String prefix, Class type, Class... paramTypes) { for (String name : this.names) { String candidate = prefix + StringUtils.capitalize(name); Method method = ReflectionUtils.findMethod(type, candidate, paramTypes); @@ -323,11 +326,16 @@ public final class DataSourceBuilder { void set(T dataSource, DataSourceProperty property, String value); - String get(T dataSource, DataSourceProperty property); + @Nullable String get(T dataSource, DataSourceProperty property); - static DataSourceProperties forType(ClassLoader classLoader, Class type) { + static DataSourceProperties forType(@Nullable ClassLoader classLoader, + @Nullable Class type) { MappedDataSourceProperties mapped = MappedDataSourceProperties.forType(classLoader, type); - return (mapped != null) ? mapped : new ReflectionDataSourceProperties<>(type); + if (mapped != null) { + return mapped; + } + Assert.state(type != null, "No supported DataSource type found"); + return new ReflectionDataSourceProperties<>(type); } } @@ -338,10 +346,16 @@ public final class DataSourceBuilder { private final Class dataSourceType; - @SuppressWarnings("unchecked") MappedDataSourceProperties() { - this.dataSourceType = (Class) ResolvableType.forClass(MappedDataSourceProperties.class, getClass()) + this.dataSourceType = getGeneric(); + } + + @SuppressWarnings("unchecked") + private Class getGeneric() { + Class generic = (Class) ResolvableType.forClass(MappedDataSourceProperties.class, getClass()) .resolveGeneric(); + Assert.state(generic != null, "'generic' must not be null"); + return generic; } @Override @@ -349,11 +363,12 @@ public final class DataSourceBuilder { return this.dataSourceType; } - protected void add(DataSourceProperty property, Getter getter, Setter setter) { + protected void add(DataSourceProperty property, @Nullable Getter getter, Setter setter) { add(property, String.class, getter, setter); } - protected void add(DataSourceProperty property, Class type, Getter getter, Setter setter) { + protected void add(DataSourceProperty property, Class type, @Nullable Getter getter, + Setter setter) { this.mappedProperties.put(property, new MappedDataSourceProperty<>(property, type, getter, setter)); } @@ -371,7 +386,7 @@ public final class DataSourceBuilder { } @Override - public String get(T dataSource, DataSourceProperty property) { + public @Nullable String get(T dataSource, DataSourceProperty property) { MappedDataSourceProperty mappedProperty = getMapping(property); if (mappedProperty != null) { return mappedProperty.get(dataSource); @@ -379,14 +394,15 @@ public final class DataSourceBuilder { return null; } - private MappedDataSourceProperty getMapping(DataSourceProperty property) { + private @Nullable MappedDataSourceProperty getMapping(DataSourceProperty property) { MappedDataSourceProperty mappedProperty = this.mappedProperties.get(property); UnsupportedDataSourcePropertyException.throwIf(!property.isOptional() && mappedProperty == null, () -> "No mapping found for " + property); return mappedProperty; } - static MappedDataSourceProperties forType(ClassLoader classLoader, Class type) { + static @Nullable MappedDataSourceProperties forType(@Nullable ClassLoader classLoader, + @Nullable Class type) { MappedDataSourceProperties pooled = lookupPooled(classLoader, type); if (type == null || pooled != null) { return pooled; @@ -394,8 +410,8 @@ public final class DataSourceBuilder { return lookupBasic(classLoader, type); } - private static MappedDataSourceProperties lookupPooled(ClassLoader classLoader, - Class type) { + private static @Nullable MappedDataSourceProperties lookupPooled( + @Nullable ClassLoader classLoader, @Nullable Class type) { MappedDataSourceProperties result = null; result = lookup(classLoader, type, result, "com.zaxxer.hikari.HikariDataSource", HikariDataSourceProperties::new); @@ -412,8 +428,8 @@ public final class DataSourceBuilder { return result; } - private static MappedDataSourceProperties lookupBasic(ClassLoader classLoader, - Class dataSourceType) { + private static @Nullable MappedDataSourceProperties lookupBasic( + @Nullable ClassLoader classLoader, Class dataSourceType) { MappedDataSourceProperties result = null; result = lookup(classLoader, dataSourceType, result, "org.springframework.jdbc.datasource.SimpleDriverDataSource", SimpleDataSourceProperties::new); @@ -427,8 +443,9 @@ public final class DataSourceBuilder { } @SuppressWarnings("unchecked") - private static MappedDataSourceProperties lookup(ClassLoader classLoader, - Class dataSourceType, MappedDataSourceProperties existing, String dataSourceClassName, + private static @Nullable MappedDataSourceProperties lookup( + @Nullable ClassLoader classLoader, @Nullable Class dataSourceType, + @Nullable MappedDataSourceProperties existing, String dataSourceClassName, Supplier> propertyMappingsSupplier, String... requiredClassNames) { if (existing != null || !allPresent(classLoader, dataSourceClassName, requiredClassNames)) { return existing; @@ -439,7 +456,7 @@ public final class DataSourceBuilder { ? (MappedDataSourceProperties) propertyMappings : null; } - private static boolean allPresent(ClassLoader classLoader, String dataSourceClassName, + private static boolean allPresent(@Nullable ClassLoader classLoader, String dataSourceClassName, String[] requiredClassNames) { boolean result = ClassUtils.isPresent(dataSourceClassName, classLoader); for (String requiredClassName : requiredClassNames) { @@ -456,11 +473,12 @@ public final class DataSourceBuilder { private final Class type; - private final Getter getter; + private final @Nullable Getter getter; - private final Setter setter; + private final @Nullable Setter setter; - MappedDataSourceProperty(DataSourceProperty property, Class type, Getter getter, Setter setter) { + MappedDataSourceProperty(DataSourceProperty property, Class type, @Nullable Getter getter, + @Nullable Setter setter) { this.property = property; this.type = type; this.getter = getter; @@ -481,7 +499,7 @@ public final class DataSourceBuilder { } } - String get(T dataSource) { + @Nullable String get(T dataSource) { try { if (this.getter == null) { UnsupportedDataSourcePropertyException.throwIf(!this.property.isOptional(), @@ -506,7 +524,8 @@ public final class DataSourceBuilder { throw new IllegalStateException("Unsupported value type " + this.type); } - private String convertToString(V value) { + @Contract("!null -> !null") + private @Nullable String convertToString(@Nullable V value) { if (value == null) { return null; } @@ -530,7 +549,6 @@ public final class DataSourceBuilder { private final Class dataSourceType; ReflectionDataSourceProperties(Class dataSourceType) { - Assert.state(dataSourceType != null, "No supported DataSource type found"); Map getters = new HashMap<>(); Map setters = new HashMap<>(); for (DataSourceProperty property : DataSourceProperty.values()) { @@ -542,7 +560,8 @@ public final class DataSourceBuilder { this.setters = Collections.unmodifiableMap(setters); } - private void putIfNotNull(Map map, DataSourceProperty property, Method method) { + private void putIfNotNull(Map map, DataSourceProperty property, + @Nullable Method method) { if (method != null) { map.put(property, method); } @@ -567,7 +586,7 @@ public final class DataSourceBuilder { } @Override - public String get(T dataSource, DataSourceProperty property) { + public @Nullable String get(T dataSource, DataSourceProperty property) { Method method = getMethod(property, this.getters); if (method != null) { return (String) ReflectionUtils.invokeMethod(method, dataSource); @@ -575,7 +594,7 @@ public final class DataSourceBuilder { return null; } - private Method getMethod(DataSourceProperty property, Map methods) { + private @Nullable Method getMethod(DataSourceProperty property, Map methods) { Method method = methods.get(property); if (method == null) { UnsupportedDataSourcePropertyException.throwIf(!property.isOptional(), @@ -591,7 +610,7 @@ public final class DataSourceBuilder { @FunctionalInterface private interface Getter { - V get(T instance) throws SQLException; + @Nullable V get(T instance) throws SQLException; } diff --git a/module/spring-boot-jdbc/src/main/java/org/springframework/boot/jdbc/DataSourceBuilderRuntimeHints.java b/module/spring-boot-jdbc/src/main/java/org/springframework/boot/jdbc/DataSourceBuilderRuntimeHints.java index e046ee55262..68f8c8cadb6 100644 --- a/module/spring-boot-jdbc/src/main/java/org/springframework/boot/jdbc/DataSourceBuilderRuntimeHints.java +++ b/module/spring-boot-jdbc/src/main/java/org/springframework/boot/jdbc/DataSourceBuilderRuntimeHints.java @@ -22,6 +22,8 @@ import java.util.List; import javax.sql.DataSource; +import org.jspecify.annotations.Nullable; + import org.springframework.aot.hint.MemberCategory; import org.springframework.aot.hint.RuntimeHints; import org.springframework.aot.hint.RuntimeHintsRegistrar; @@ -51,7 +53,7 @@ class DataSourceBuilderRuntimeHints implements RuntimeHintsRegistrar { } @Override - public void registerHints(RuntimeHints hints, ClassLoader classLoader) { + public void registerHints(RuntimeHints hints, @Nullable ClassLoader classLoader) { for (String typeName : TYPE_NAMES) { hints.reflection() .registerTypeIfPresent(classLoader, typeName, diff --git a/module/spring-boot-jdbc/src/main/java/org/springframework/boot/jdbc/DataSourceUnwrapper.java b/module/spring-boot-jdbc/src/main/java/org/springframework/boot/jdbc/DataSourceUnwrapper.java index d08faca4ae2..af4cda88066 100644 --- a/module/spring-boot-jdbc/src/main/java/org/springframework/boot/jdbc/DataSourceUnwrapper.java +++ b/module/spring-boot-jdbc/src/main/java/org/springframework/boot/jdbc/DataSourceUnwrapper.java @@ -20,6 +20,8 @@ import java.sql.Wrapper; import javax.sql.DataSource; +import org.jspecify.annotations.Nullable; + import org.springframework.aop.framework.AopProxyUtils; import org.springframework.aop.support.AopUtils; import org.springframework.jdbc.datasource.DelegatingDataSource; @@ -53,7 +55,8 @@ public final class DataSourceUnwrapper { * @since 2.3.8 * @see Wrapper#unwrap(Class) */ - public static T unwrap(DataSource dataSource, Class unwrapInterface, Class target) { + public static @Nullable T unwrap(DataSource dataSource, Class unwrapInterface, + Class target) { if (target.isInstance(dataSource)) { return target.cast(dataSource); } @@ -86,11 +89,11 @@ public final class DataSourceUnwrapper { * @param the target type * @return an object that implements the target type or {@code null} */ - public static T unwrap(DataSource dataSource, Class target) { + public static @Nullable T unwrap(DataSource dataSource, Class target) { return unwrap(dataSource, target, target); } - private static S safeUnwrap(Wrapper wrapper, Class target) { + private static @Nullable S safeUnwrap(Wrapper wrapper, Class target) { try { if (target.isInterface() && wrapper.isWrapperFor(target)) { return wrapper.unwrap(target); @@ -104,7 +107,7 @@ public final class DataSourceUnwrapper { private static final class DelegatingDataSourceUnwrapper { - private static DataSource getTargetDataSource(DataSource dataSource) { + private static @Nullable DataSource getTargetDataSource(DataSource dataSource) { if (dataSource instanceof DelegatingDataSource delegatingDataSource) { return delegatingDataSource.getTargetDataSource(); } diff --git a/module/spring-boot-jdbc/src/main/java/org/springframework/boot/jdbc/DatabaseDriver.java b/module/spring-boot-jdbc/src/main/java/org/springframework/boot/jdbc/DatabaseDriver.java index 3b5303221da..0fbaeaa2cfc 100644 --- a/module/spring-boot-jdbc/src/main/java/org/springframework/boot/jdbc/DatabaseDriver.java +++ b/module/spring-boot-jdbc/src/main/java/org/springframework/boot/jdbc/DatabaseDriver.java @@ -21,6 +21,8 @@ import java.util.Collection; import java.util.Collections; import java.util.Locale; +import org.jspecify.annotations.Nullable; + import org.springframework.util.Assert; import org.springframework.util.StringUtils; @@ -233,23 +235,25 @@ public enum DatabaseDriver { }; - private final String productName; + private final @Nullable String productName; - private final String driverClassName; + private final @Nullable String driverClassName; - private final String xaDataSourceClassName; + private final @Nullable String xaDataSourceClassName; - private final String validationQuery; + private final @Nullable String validationQuery; - DatabaseDriver(String productName, String driverClassName) { + DatabaseDriver(@Nullable String productName, @Nullable String driverClassName) { this(productName, driverClassName, null); } - DatabaseDriver(String productName, String driverClassName, String xaDataSourceClassName) { + DatabaseDriver(@Nullable String productName, @Nullable String driverClassName, + @Nullable String xaDataSourceClassName) { this(productName, driverClassName, xaDataSourceClassName, null); } - DatabaseDriver(String productName, String driverClassName, String xaDataSourceClassName, String validationQuery) { + DatabaseDriver(@Nullable String productName, @Nullable String driverClassName, + @Nullable String xaDataSourceClassName, @Nullable String validationQuery) { this.productName = productName; this.driverClassName = driverClassName; this.xaDataSourceClassName = xaDataSourceClassName; @@ -280,7 +284,7 @@ public enum DatabaseDriver { * Return the driver class name. * @return the class name or {@code null} */ - public String getDriverClassName() { + public @Nullable String getDriverClassName() { return this.driverClassName; } @@ -288,7 +292,7 @@ public enum DatabaseDriver { * Return the XA driver source class name. * @return the class name or {@code null} */ - public String getXaDataSourceClassName() { + public @Nullable String getXaDataSourceClassName() { return this.xaDataSourceClassName; } @@ -296,7 +300,7 @@ public enum DatabaseDriver { * Return the validation query. * @return the validation query or {@code null} */ - public String getValidationQuery() { + public @Nullable String getValidationQuery() { return this.validationQuery; } @@ -305,7 +309,7 @@ public enum DatabaseDriver { * @param url the JDBC URL * @return the database driver or {@link #UNKNOWN} if not found */ - public static DatabaseDriver fromJdbcUrl(String url) { + public static DatabaseDriver fromJdbcUrl(@Nullable String url) { if (StringUtils.hasLength(url)) { Assert.isTrue(url.startsWith("jdbc"), "'url' must start with \"jdbc\""); String urlWithoutPrefix = url.substring("jdbc".length()).toLowerCase(Locale.ENGLISH); @@ -326,7 +330,7 @@ public enum DatabaseDriver { * @param productName product name * @return the database driver or {@link #UNKNOWN} if not found */ - public static DatabaseDriver fromProductName(String productName) { + public static DatabaseDriver fromProductName(@Nullable String productName) { if (StringUtils.hasLength(productName)) { for (DatabaseDriver candidate : values()) { if (candidate.matchProductName(productName)) { diff --git a/module/spring-boot-jdbc/src/main/java/org/springframework/boot/jdbc/EmbeddedDatabaseConnection.java b/module/spring-boot-jdbc/src/main/java/org/springframework/boot/jdbc/EmbeddedDatabaseConnection.java index 9c62a648e73..b5809c6c023 100644 --- a/module/spring-boot-jdbc/src/main/java/org/springframework/boot/jdbc/EmbeddedDatabaseConnection.java +++ b/module/spring-boot-jdbc/src/main/java/org/springframework/boot/jdbc/EmbeddedDatabaseConnection.java @@ -24,6 +24,8 @@ import java.util.stream.Stream; import javax.sql.DataSource; +import org.jspecify.annotations.Nullable; + import org.springframework.dao.DataAccessException; import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseType; import org.springframework.util.Assert; @@ -64,15 +66,15 @@ public enum EmbeddedDatabaseConnection { */ HSQLDB("org.hsqldb.jdbcDriver", "jdbc:hsqldb:mem:%s"); - private final String alternativeDriverClass; + private final @Nullable String alternativeDriverClass; - private final String url; + private final @Nullable String url; - EmbeddedDatabaseConnection(String url) { + EmbeddedDatabaseConnection(@Nullable String url) { this(null, url); } - EmbeddedDatabaseConnection(String fallbackDriverClass, String url) { + EmbeddedDatabaseConnection(@Nullable String fallbackDriverClass, @Nullable String url) { this.alternativeDriverClass = fallbackDriverClass; this.url = url; } @@ -81,7 +83,7 @@ public enum EmbeddedDatabaseConnection { * Returns the driver class name. * @return the driver class name */ - public String getDriverClassName() { + public @Nullable String getDriverClassName() { // See https://github.com/spring-projects/spring-boot/issues/32865 return switch (this) { case NONE -> null; @@ -95,7 +97,7 @@ public enum EmbeddedDatabaseConnection { * Returns the {@link EmbeddedDatabaseType} for the connection. * @return the database type */ - public EmbeddedDatabaseType getType() { + public @Nullable EmbeddedDatabaseType getType() { // See https://github.com/spring-projects/spring-boot/issues/32865 return switch (this) { case NONE -> null; @@ -110,7 +112,7 @@ public enum EmbeddedDatabaseConnection { * @param databaseName the name of the database * @return the connection URL */ - public String getUrl(String databaseName) { + public @Nullable String getUrl(String databaseName) { Assert.hasText(databaseName, "'databaseName' must not be empty"); return (this.url != null) ? String.format(this.url, databaseName) : null; } @@ -125,7 +127,7 @@ public enum EmbeddedDatabaseConnection { }; } - boolean isDriverCompatible(String driverClass) { + boolean isDriverCompatible(@Nullable String driverClass) { return (driverClass != null && (driverClass.equals(getDriverClassName()) || driverClass.equals(this.alternativeDriverClass))); } @@ -138,7 +140,7 @@ public enum EmbeddedDatabaseConnection { * @return true if the driver class and url refer to an embedded database * @since 2.4.0 */ - public static boolean isEmbedded(String driverClass, String url) { + public static boolean isEmbedded(@Nullable String driverClass, @Nullable String url) { if (driverClass == null) { return false; } @@ -178,9 +180,14 @@ public enum EmbeddedDatabaseConnection { * @param classLoader the class loader used to check for classes * @return an {@link EmbeddedDatabaseConnection} or {@link #NONE}. */ - public static EmbeddedDatabaseConnection get(ClassLoader classLoader) { + public static EmbeddedDatabaseConnection get(@Nullable ClassLoader classLoader) { for (EmbeddedDatabaseConnection candidate : EmbeddedDatabaseConnection.values()) { - if (candidate != NONE && ClassUtils.isPresent(candidate.getDriverClassName(), classLoader)) { + if (candidate == NONE) { + continue; + } + String driverClassName = candidate.getDriverClassName(); + Assert.state(driverClassName != null, "'driverClassName' must not be null"); + if (ClassUtils.isPresent(driverClassName, classLoader)) { return candidate; } } @@ -202,7 +209,12 @@ public enum EmbeddedDatabaseConnection { productName = productName.toUpperCase(Locale.ENGLISH); EmbeddedDatabaseConnection[] candidates = EmbeddedDatabaseConnection.values(); for (EmbeddedDatabaseConnection candidate : candidates) { - if (candidate != NONE && productName.contains(candidate.getType().name())) { + if (candidate == NONE) { + continue; + } + EmbeddedDatabaseType type = candidate.getType(); + Assert.state(type != null, "'type' must not be null"); + if (productName.contains(type.name())) { String url = metaData.getURL(); return (url == null || candidate.isEmbeddedUrl(url)); } diff --git a/module/spring-boot-jdbc/src/main/java/org/springframework/boot/jdbc/HikariCheckpointRestoreLifecycle.java b/module/spring-boot-jdbc/src/main/java/org/springframework/boot/jdbc/HikariCheckpointRestoreLifecycle.java index 1fcdac1a18f..e12ee0648d1 100644 --- a/module/spring-boot-jdbc/src/main/java/org/springframework/boot/jdbc/HikariCheckpointRestoreLifecycle.java +++ b/module/spring-boot-jdbc/src/main/java/org/springframework/boot/jdbc/HikariCheckpointRestoreLifecycle.java @@ -33,6 +33,7 @@ import com.zaxxer.hikari.HikariPoolMXBean; import com.zaxxer.hikari.pool.HikariPool; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; +import org.jspecify.annotations.Nullable; import org.springframework.context.ConfigurableApplicationContext; import org.springframework.context.Lifecycle; @@ -71,7 +72,7 @@ public class HikariCheckpointRestoreLifecycle implements Lifecycle { private final Function hasOpenConnections; - private final HikariDataSource dataSource; + private final @Nullable HikariDataSource dataSource; private final ConfigurableApplicationContext applicationContext; @@ -123,15 +124,16 @@ public class HikariCheckpointRestoreLifecycle implements Lifecycle { + "Please configure allow-pool-suspension to fix this!"); } } - closeConnections(Duration.ofMillis(this.dataSource.getConnectionTimeout() + 250)); + closeConnections(this.dataSource, Duration.ofMillis(this.dataSource.getConnectionTimeout() + 250)); } - private void closeConnections(Duration shutdownTimeout) { + private void closeConnections(HikariDataSource dataSource, Duration shutdownTimeout) { logger.info("Evicting Hikari connections"); - this.dataSource.getHikariPoolMXBean().softEvictConnections(); + dataSource.getHikariPoolMXBean().softEvictConnections(); logger.debug(LogMessage.format("Waiting %d seconds for Hikari connections to be closed", shutdownTimeout.toSeconds())); - CompletableFuture allConnectionsClosed = CompletableFuture.runAsync(this::waitForConnectionsToClose); + CompletableFuture allConnectionsClosed = CompletableFuture + .runAsync(() -> this.waitForConnectionsToClose(dataSource)); try { allConnectionsClosed.get(shutdownTimeout.toMillis(), TimeUnit.MILLISECONDS); logger.debug("Hikari connections closed"); @@ -148,8 +150,8 @@ public class HikariCheckpointRestoreLifecycle implements Lifecycle { } } - private void waitForConnectionsToClose() { - while (this.hasOpenConnections.apply((HikariPool) this.dataSource.getHikariPoolMXBean())) { + private void waitForConnectionsToClose(HikariDataSource dataSource) { + while (this.hasOpenConnections.apply((HikariPool) dataSource.getHikariPoolMXBean())) { try { TimeUnit.MILLISECONDS.sleep(50); } diff --git a/module/spring-boot-jdbc/src/main/java/org/springframework/boot/jdbc/autoconfigure/DataSourceConfiguration.java b/module/spring-boot-jdbc/src/main/java/org/springframework/boot/jdbc/autoconfigure/DataSourceConfiguration.java index cd2784bf3a4..c9ce99db97e 100644 --- a/module/spring-boot-jdbc/src/main/java/org/springframework/boot/jdbc/autoconfigure/DataSourceConfiguration.java +++ b/module/spring-boot-jdbc/src/main/java/org/springframework/boot/jdbc/autoconfigure/DataSourceConfiguration.java @@ -23,6 +23,7 @@ import javax.sql.DataSource; import com.zaxxer.hikari.HikariDataSource; import oracle.jdbc.OracleConnection; import oracle.ucp.jdbc.PoolDataSourceImpl; +import org.jspecify.annotations.Nullable; import org.springframework.beans.factory.ObjectProvider; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; @@ -49,14 +50,14 @@ import org.springframework.util.StringUtils; abstract class DataSourceConfiguration { @SuppressWarnings("unchecked") - private static T createDataSource(JdbcConnectionDetails connectionDetails, Class type, - ClassLoader classLoader) { + private static T createDataSource(JdbcConnectionDetails connectionDetails, + @Nullable Class type, ClassLoader classLoader) { return createDataSource(connectionDetails, type, classLoader, true); } @SuppressWarnings("unchecked") - private static T createDataSource(JdbcConnectionDetails connectionDetails, Class type, - ClassLoader classLoader, boolean applyDriverClassName) { + private static T createDataSource(JdbcConnectionDetails connectionDetails, + @Nullable Class type, ClassLoader classLoader, boolean applyDriverClassName) { DataSourceBuilder builder = DataSourceBuilder.create(classLoader).type(type); if (applyDriverClassName) { builder.driverClassName(connectionDetails.getDriverClassName()); diff --git a/module/spring-boot-jdbc/src/main/java/org/springframework/boot/jdbc/autoconfigure/DataSourceInitializationAutoConfiguration.java b/module/spring-boot-jdbc/src/main/java/org/springframework/boot/jdbc/autoconfigure/DataSourceInitializationAutoConfiguration.java index e0b1300bc5e..c2d4facf2c5 100644 --- a/module/spring-boot-jdbc/src/main/java/org/springframework/boot/jdbc/autoconfigure/DataSourceInitializationAutoConfiguration.java +++ b/module/spring-boot-jdbc/src/main/java/org/springframework/boot/jdbc/autoconfigure/DataSourceInitializationAutoConfiguration.java @@ -18,6 +18,8 @@ package org.springframework.boot.jdbc.autoconfigure; import javax.sql.DataSource; +import org.jspecify.annotations.Nullable; + import org.springframework.boot.autoconfigure.AutoConfiguration; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; @@ -57,7 +59,8 @@ public final class DataSourceInitializationAutoConfiguration { determineDataSource(dataSource, properties.getUsername(), properties.getPassword()), properties); } - private static DataSource determineDataSource(DataSource dataSource, String username, String password) { + private static DataSource determineDataSource(DataSource dataSource, @Nullable String username, + @Nullable String password) { if (StringUtils.hasText(username) && StringUtils.hasText(password)) { return DataSourceBuilder.derivedFrom(dataSource) .username(username) diff --git a/module/spring-boot-jdbc/src/main/java/org/springframework/boot/jdbc/autoconfigure/DataSourceJmxConfiguration.java b/module/spring-boot-jdbc/src/main/java/org/springframework/boot/jdbc/autoconfigure/DataSourceJmxConfiguration.java index 53472a0e9e9..40b0949edb7 100644 --- a/module/spring-boot-jdbc/src/main/java/org/springframework/boot/jdbc/autoconfigure/DataSourceJmxConfiguration.java +++ b/module/spring-boot-jdbc/src/main/java/org/springframework/boot/jdbc/autoconfigure/DataSourceJmxConfiguration.java @@ -26,6 +26,7 @@ import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.apache.tomcat.jdbc.pool.DataSourceProxy; import org.apache.tomcat.jdbc.pool.PoolConfiguration; +import org.jspecify.annotations.Nullable; import org.springframework.beans.factory.ObjectProvider; import org.springframework.boot.autoconfigure.condition.ConditionalOnBooleanProperty; @@ -81,7 +82,7 @@ class DataSourceJmxConfiguration { @Bean @ConditionalOnMissingBean(name = "dataSourceMBean") - Object dataSourceMBean(DataSource dataSource) { + @Nullable Object dataSourceMBean(DataSource dataSource) { DataSourceProxy dataSourceProxy = DataSourceUnwrapper.unwrap(dataSource, PoolConfiguration.class, DataSourceProxy.class); if (dataSourceProxy != null) { diff --git a/module/spring-boot-jdbc/src/main/java/org/springframework/boot/jdbc/autoconfigure/DataSourcePoolMetadataProvidersConfiguration.java b/module/spring-boot-jdbc/src/main/java/org/springframework/boot/jdbc/autoconfigure/DataSourcePoolMetadataProvidersConfiguration.java index 7b18fcbca92..dfafe981f41 100644 --- a/module/spring-boot-jdbc/src/main/java/org/springframework/boot/jdbc/autoconfigure/DataSourcePoolMetadataProvidersConfiguration.java +++ b/module/spring-boot-jdbc/src/main/java/org/springframework/boot/jdbc/autoconfigure/DataSourcePoolMetadataProvidersConfiguration.java @@ -23,6 +23,7 @@ import oracle.ucp.jdbc.PoolDataSource; import org.apache.commons.dbcp2.BasicDataSource; import org.apache.commons.dbcp2.BasicDataSourceMXBean; import org.apache.tomcat.jdbc.pool.jmx.ConnectionPoolMBean; +import org.jspecify.annotations.Nullable; import org.springframework.aot.hint.RuntimeHints; import org.springframework.aot.hint.RuntimeHintsRegistrar; @@ -123,7 +124,7 @@ public class DataSourcePoolMetadataProvidersConfiguration { static class HikariDataSourcePoolMetadataRuntimeHints implements RuntimeHintsRegistrar { @Override - public void registerHints(RuntimeHints hints, ClassLoader classLoader) { + public void registerHints(RuntimeHints hints, @Nullable ClassLoader classLoader) { hints.reflection().registerType(HikariDataSource.class, (builder) -> builder.withField("pool")); } diff --git a/module/spring-boot-jdbc/src/main/java/org/springframework/boot/jdbc/autoconfigure/DataSourceProperties.java b/module/spring-boot-jdbc/src/main/java/org/springframework/boot/jdbc/autoconfigure/DataSourceProperties.java index be60068586a..ab4abc3e90f 100644 --- a/module/spring-boot-jdbc/src/main/java/org/springframework/boot/jdbc/autoconfigure/DataSourceProperties.java +++ b/module/spring-boot-jdbc/src/main/java/org/springframework/boot/jdbc/autoconfigure/DataSourceProperties.java @@ -22,6 +22,8 @@ import java.util.UUID; import javax.sql.DataSource; +import org.jspecify.annotations.Nullable; + import org.springframework.beans.factory.BeanClassLoaderAware; import org.springframework.beans.factory.BeanCreationException; import org.springframework.beans.factory.InitializingBean; @@ -47,6 +49,7 @@ import org.springframework.util.StringUtils; @ConfigurationProperties("spring.datasource") public class DataSourceProperties implements BeanClassLoaderAware, InitializingBean { + @SuppressWarnings("NullAway.Init") private ClassLoader classLoader; /** @@ -58,49 +61,50 @@ public class DataSourceProperties implements BeanClassLoaderAware, InitializingB * Datasource name to use if "generate-unique-name" is false. Defaults to "testdb" * when using an embedded database, otherwise null. */ - private String name; + private @Nullable String name; /** * Fully qualified name of the DataSource implementation to use. By default, a * connection pool implementation is auto-detected from the classpath. */ - private Class type; + private @Nullable Class type; /** * Fully qualified name of the JDBC driver. Auto-detected based on the URL by default. */ - private String driverClassName; + private @Nullable String driverClassName; /** * JDBC URL of the database. */ - private String url; + private @Nullable String url; /** * Login username of the database. */ - private String username; + private @Nullable String username; /** * Login password of the database. */ - private String password; + private @Nullable String password; /** * JNDI location of the datasource. Class, url, username and password are ignored when * set. */ - private String jndiName; + private @Nullable String jndiName; /** * Connection details for an embedded database. Defaults to the most suitable embedded * database that is available on the classpath. */ + @SuppressWarnings("NullAway.Init") private EmbeddedDatabaseConnection embeddedDatabaseConnection; private Xa xa = new Xa(); - private String uniqueName; + private @Nullable String uniqueName; @Override public void setBeanClassLoader(ClassLoader classLoader) { @@ -136,19 +140,19 @@ public class DataSourceProperties implements BeanClassLoaderAware, InitializingB this.generateUniqueName = generateUniqueName; } - public String getName() { + public @Nullable String getName() { return this.name; } - public void setName(String name) { + public void setName(@Nullable String name) { this.name = name; } - public Class getType() { + public @Nullable Class getType() { return this.type; } - public void setType(Class type) { + public void setType(@Nullable Class type) { this.type = type; } @@ -157,11 +161,11 @@ public class DataSourceProperties implements BeanClassLoaderAware, InitializingB * @return the configured driver * @see #determineDriverClassName() */ - public String getDriverClassName() { + public @Nullable String getDriverClassName() { return this.driverClassName; } - public void setDriverClassName(String driverClassName) { + public void setDriverClassName(@Nullable String driverClassName) { this.driverClassName = driverClassName; } @@ -178,9 +182,10 @@ public class DataSourceProperties implements BeanClassLoaderAware, InitializingB return driverClassName; } - String findDriverClassName() { + @Nullable String findDriverClassName() { if (StringUtils.hasText(this.driverClassName)) { - Assert.state(driverClassIsLoadable(), () -> "Cannot load driver class: " + this.driverClassName); + Assert.state(driverClassIsLoadable(this.driverClassName), + () -> "Cannot load driver class: " + this.driverClassName); return this.driverClassName; } String driverClassName = null; @@ -193,9 +198,9 @@ public class DataSourceProperties implements BeanClassLoaderAware, InitializingB return driverClassName; } - private boolean driverClassIsLoadable() { + private boolean driverClassIsLoadable(String driverClassName) { try { - ClassUtils.forName(this.driverClassName, null); + ClassUtils.forName(driverClassName, null); return true; } catch (UnsupportedClassVersionError ex) { @@ -212,11 +217,11 @@ public class DataSourceProperties implements BeanClassLoaderAware, InitializingB * @return the configured url * @see #determineUrl() */ - public String getUrl() { + public @Nullable String getUrl() { return this.url; } - public void setUrl(String url) { + public void setUrl(@Nullable String url) { this.url = url; } @@ -241,7 +246,7 @@ public class DataSourceProperties implements BeanClassLoaderAware, InitializingB * Determine the name to used based on this configuration. * @return the database name to use or {@code null} */ - public String determineDatabaseName() { + public @Nullable String determineDatabaseName() { if (this.generateUniqueName) { if (this.uniqueName == null) { this.uniqueName = UUID.randomUUID().toString(); @@ -262,11 +267,11 @@ public class DataSourceProperties implements BeanClassLoaderAware, InitializingB * @return the configured username * @see #determineUsername() */ - public String getUsername() { + public @Nullable String getUsername() { return this.username; } - public void setUsername(String username) { + public void setUsername(@Nullable String username) { this.username = username; } @@ -274,7 +279,7 @@ public class DataSourceProperties implements BeanClassLoaderAware, InitializingB * Determine the username to use based on this configuration and the environment. * @return the username to use */ - public String determineUsername() { + public @Nullable String determineUsername() { if (StringUtils.hasText(this.username)) { return this.username; } @@ -289,11 +294,11 @@ public class DataSourceProperties implements BeanClassLoaderAware, InitializingB * @return the configured password * @see #determinePassword() */ - public String getPassword() { + public @Nullable String getPassword() { return this.password; } - public void setPassword(String password) { + public void setPassword(@Nullable String password) { this.password = password; } @@ -301,7 +306,7 @@ public class DataSourceProperties implements BeanClassLoaderAware, InitializingB * Determine the password to use based on this configuration and the environment. * @return the password to use */ - public String determinePassword() { + public @Nullable String determinePassword() { if (StringUtils.hasText(this.password)) { return this.password; } @@ -311,7 +316,7 @@ public class DataSourceProperties implements BeanClassLoaderAware, InitializingB return null; } - public String getJndiName() { + public @Nullable String getJndiName() { return this.jndiName; } @@ -321,7 +326,7 @@ public class DataSourceProperties implements BeanClassLoaderAware, InitializingB * will be ignored when using JNDI lookups. * @param jndiName the JNDI name */ - public void setJndiName(String jndiName) { + public void setJndiName(@Nullable String jndiName) { this.jndiName = jndiName; } @@ -353,18 +358,18 @@ public class DataSourceProperties implements BeanClassLoaderAware, InitializingB /** * XA datasource fully qualified name. */ - private String dataSourceClassName; + private @Nullable String dataSourceClassName; /** * Properties to pass to the XA data source. */ private Map properties = new LinkedHashMap<>(); - public String getDataSourceClassName() { + public @Nullable String getDataSourceClassName() { return this.dataSourceClassName; } - public void setDataSourceClassName(String dataSourceClassName) { + public void setDataSourceClassName(@Nullable String dataSourceClassName) { this.dataSourceClassName = dataSourceClassName; } diff --git a/module/spring-boot-jdbc/src/main/java/org/springframework/boot/jdbc/autoconfigure/EmbeddedDataSourceConfiguration.java b/module/spring-boot-jdbc/src/main/java/org/springframework/boot/jdbc/autoconfigure/EmbeddedDataSourceConfiguration.java index 312dda3abef..c9101059491 100644 --- a/module/spring-boot-jdbc/src/main/java/org/springframework/boot/jdbc/autoconfigure/EmbeddedDataSourceConfiguration.java +++ b/module/spring-boot-jdbc/src/main/java/org/springframework/boot/jdbc/autoconfigure/EmbeddedDataSourceConfiguration.java @@ -23,6 +23,8 @@ import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.jdbc.datasource.embedded.EmbeddedDatabase; import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseBuilder; +import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseType; +import org.springframework.util.Assert; /** * Configuration for embedded data sources. @@ -36,6 +38,7 @@ import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseBuilder; @EnableConfigurationProperties(DataSourceProperties.class) public class EmbeddedDataSourceConfiguration implements BeanClassLoaderAware { + @SuppressWarnings("NullAway.Init") private ClassLoader classLoader; @Override @@ -45,9 +48,11 @@ public class EmbeddedDataSourceConfiguration implements BeanClassLoaderAware { @Bean(destroyMethod = "shutdown") public EmbeddedDatabase dataSource(DataSourceProperties properties) { - return new EmbeddedDatabaseBuilder().setType(EmbeddedDatabaseConnection.get(this.classLoader).getType()) - .setName(properties.determineDatabaseName()) - .build(); + EmbeddedDatabaseType type = EmbeddedDatabaseConnection.get(this.classLoader).getType(); + String databaseName = properties.determineDatabaseName(); + Assert.state(type != null, "'type' must not be null"); + Assert.state(databaseName != null, "'databaseName' must not be null"); + return new EmbeddedDatabaseBuilder().setType(type).setName(databaseName).build(); } } diff --git a/module/spring-boot-jdbc/src/main/java/org/springframework/boot/jdbc/autoconfigure/HikariDriverConfigurationFailureAnalyzer.java b/module/spring-boot-jdbc/src/main/java/org/springframework/boot/jdbc/autoconfigure/HikariDriverConfigurationFailureAnalyzer.java index b8f888517ca..da8bd2e3ebc 100644 --- a/module/spring-boot-jdbc/src/main/java/org/springframework/boot/jdbc/autoconfigure/HikariDriverConfigurationFailureAnalyzer.java +++ b/module/spring-boot-jdbc/src/main/java/org/springframework/boot/jdbc/autoconfigure/HikariDriverConfigurationFailureAnalyzer.java @@ -16,6 +16,8 @@ package org.springframework.boot.jdbc.autoconfigure; +import org.jspecify.annotations.Nullable; + import org.springframework.boot.diagnostics.AbstractFailureAnalyzer; import org.springframework.boot.diagnostics.FailureAnalysis; import org.springframework.jdbc.CannotGetJdbcConnectionException; @@ -31,7 +33,7 @@ class HikariDriverConfigurationFailureAnalyzer extends AbstractFailureAnalyzer metadataProviders; + @SuppressWarnings("NullAway.Init") private DataSourcePoolMetadataProvider poolMetadataProvider; DataSourceHealthContributorAutoConfiguration(ObjectProvider metadataProviders) { @@ -114,7 +117,7 @@ public final class DataSourceHealthContributorAutoConfiguration implements Initi return new DataSourceHealthIndicator(source, getValidationQuery(source)); } - private String getValidationQuery(DataSource source) { + private @Nullable String getValidationQuery(DataSource source) { DataSourcePoolMetadata poolMetadata = this.poolMetadataProvider.getDataSourcePoolMetadata(source); return (poolMetadata != null) ? poolMetadata.getValidationQuery() : null; } @@ -165,7 +168,7 @@ public final class DataSourceHealthContributorAutoConfiguration implements Initi } @Override - public HealthContributor getContributor(String name) { + public @Nullable HealthContributor getContributor(String name) { return this.delegate.getContributor(name); } diff --git a/module/spring-boot-jdbc/src/main/java/org/springframework/boot/jdbc/autoconfigure/health/package-info.java b/module/spring-boot-jdbc/src/main/java/org/springframework/boot/jdbc/autoconfigure/health/package-info.java index 9f3415f3bfe..64cfd23343c 100644 --- a/module/spring-boot-jdbc/src/main/java/org/springframework/boot/jdbc/autoconfigure/health/package-info.java +++ b/module/spring-boot-jdbc/src/main/java/org/springframework/boot/jdbc/autoconfigure/health/package-info.java @@ -17,4 +17,7 @@ /** * Auto-configuration for JDBC health. */ +@NullMarked package org.springframework.boot.jdbc.autoconfigure.health; + +import org.jspecify.annotations.NullMarked; diff --git a/module/spring-boot-jdbc/src/main/java/org/springframework/boot/jdbc/autoconfigure/metrics/package-info.java b/module/spring-boot-jdbc/src/main/java/org/springframework/boot/jdbc/autoconfigure/metrics/package-info.java index cba531b9eb0..47fb64edf57 100644 --- a/module/spring-boot-jdbc/src/main/java/org/springframework/boot/jdbc/autoconfigure/metrics/package-info.java +++ b/module/spring-boot-jdbc/src/main/java/org/springframework/boot/jdbc/autoconfigure/metrics/package-info.java @@ -17,4 +17,7 @@ /** * Auto-configuration for JDBC metrics. */ +@NullMarked package org.springframework.boot.jdbc.autoconfigure.metrics; + +import org.jspecify.annotations.NullMarked; diff --git a/module/spring-boot-jdbc/src/main/java/org/springframework/boot/jdbc/autoconfigure/package-info.java b/module/spring-boot-jdbc/src/main/java/org/springframework/boot/jdbc/autoconfigure/package-info.java index 18748f5632b..624176d17f1 100644 --- a/module/spring-boot-jdbc/src/main/java/org/springframework/boot/jdbc/autoconfigure/package-info.java +++ b/module/spring-boot-jdbc/src/main/java/org/springframework/boot/jdbc/autoconfigure/package-info.java @@ -17,4 +17,7 @@ /** * Auto-configuration for JDBC. */ +@NullMarked package org.springframework.boot.jdbc.autoconfigure; + +import org.jspecify.annotations.NullMarked; diff --git a/module/spring-boot-jdbc/src/main/java/org/springframework/boot/jdbc/docker/compose/JdbcUrlBuilder.java b/module/spring-boot-jdbc/src/main/java/org/springframework/boot/jdbc/docker/compose/JdbcUrlBuilder.java index b168cde2c79..bb935c7d8b4 100644 --- a/module/spring-boot-jdbc/src/main/java/org/springframework/boot/jdbc/docker/compose/JdbcUrlBuilder.java +++ b/module/spring-boot-jdbc/src/main/java/org/springframework/boot/jdbc/docker/compose/JdbcUrlBuilder.java @@ -16,6 +16,8 @@ package org.springframework.boot.jdbc.docker.compose; +import org.jspecify.annotations.Nullable; + import org.springframework.boot.docker.compose.core.RunningService; import org.springframework.util.Assert; import org.springframework.util.StringUtils; @@ -61,11 +63,11 @@ class JdbcUrlBuilder { * @param database the database to connect to * @return a new JDBC URL */ - String build(RunningService service, String database) { + String build(RunningService service, @Nullable String database) { return urlFor(service, database); } - private String urlFor(RunningService service, String database) { + private String urlFor(RunningService service, @Nullable String database) { Assert.notNull(service, "'service' must not be null"); StringBuilder url = new StringBuilder("jdbc:%s://%s:%d".formatted(this.driverProtocol, service.host(), service.ports().get(this.containerPort))); @@ -91,7 +93,7 @@ class JdbcUrlBuilder { url.append("?").append(parameters); } - private String getParameters(RunningService service) { + private @Nullable String getParameters(RunningService service) { return service.labels().get(PARAMETERS_LABEL); } diff --git a/module/spring-boot-jdbc/src/main/java/org/springframework/boot/jdbc/docker/compose/PostgresEnvironment.java b/module/spring-boot-jdbc/src/main/java/org/springframework/boot/jdbc/docker/compose/PostgresEnvironment.java index d4cc1653a4e..1604b0e490d 100644 --- a/module/spring-boot-jdbc/src/main/java/org/springframework/boot/jdbc/docker/compose/PostgresEnvironment.java +++ b/module/spring-boot-jdbc/src/main/java/org/springframework/boot/jdbc/docker/compose/PostgresEnvironment.java @@ -18,6 +18,8 @@ package org.springframework.boot.jdbc.docker.compose; import java.util.Map; +import org.jspecify.annotations.Nullable; + import org.springframework.util.Assert; import org.springframework.util.StringUtils; @@ -43,7 +45,7 @@ class PostgresEnvironment { private final String username; - private final String password; + private final @Nullable String password; private final String database; @@ -62,7 +64,7 @@ class PostgresEnvironment { return defaultValue; } - private String extractPassword(Map env) { + private @Nullable String extractPassword(Map env) { if (isUsingTrustHostAuthMethod(env)) { return null; } @@ -81,7 +83,7 @@ class PostgresEnvironment { return this.username; } - String getPassword() { + @Nullable String getPassword() { return this.password; } diff --git a/module/spring-boot-jdbc/src/main/java/org/springframework/boot/jdbc/docker/compose/PostgresJdbcDockerComposeConnectionDetailsFactory.java b/module/spring-boot-jdbc/src/main/java/org/springframework/boot/jdbc/docker/compose/PostgresJdbcDockerComposeConnectionDetailsFactory.java index 58e25da83df..55723afca7c 100644 --- a/module/spring-boot-jdbc/src/main/java/org/springframework/boot/jdbc/docker/compose/PostgresJdbcDockerComposeConnectionDetailsFactory.java +++ b/module/spring-boot-jdbc/src/main/java/org/springframework/boot/jdbc/docker/compose/PostgresJdbcDockerComposeConnectionDetailsFactory.java @@ -19,6 +19,8 @@ package org.springframework.boot.jdbc.docker.compose; import java.net.URLEncoder; import java.nio.charset.StandardCharsets; +import org.jspecify.annotations.Nullable; + import org.springframework.boot.docker.compose.core.RunningService; import org.springframework.boot.docker.compose.service.connection.DockerComposeConnectionDetailsFactory; import org.springframework.boot.docker.compose.service.connection.DockerComposeConnectionSource; @@ -74,7 +76,7 @@ class PostgresJdbcDockerComposeConnectionDetailsFactory } @Override - public String getPassword() { + public @Nullable String getPassword() { return this.environment.getPassword(); } diff --git a/module/spring-boot-jdbc/src/main/java/org/springframework/boot/jdbc/docker/compose/package-info.java b/module/spring-boot-jdbc/src/main/java/org/springframework/boot/jdbc/docker/compose/package-info.java index 1cb18cad141..0f46f69878a 100644 --- a/module/spring-boot-jdbc/src/main/java/org/springframework/boot/jdbc/docker/compose/package-info.java +++ b/module/spring-boot-jdbc/src/main/java/org/springframework/boot/jdbc/docker/compose/package-info.java @@ -17,4 +17,7 @@ /** * Support for Docker Compose JDBC service connections. */ +@NullMarked package org.springframework.boot.jdbc.docker.compose; + +import org.jspecify.annotations.NullMarked; diff --git a/module/spring-boot-jdbc/src/main/java/org/springframework/boot/jdbc/health/DataSourceHealthIndicator.java b/module/spring-boot-jdbc/src/main/java/org/springframework/boot/jdbc/health/DataSourceHealthIndicator.java index 855f7a3d93c..2c9eb0c16aa 100644 --- a/module/spring-boot-jdbc/src/main/java/org/springframework/boot/jdbc/health/DataSourceHealthIndicator.java +++ b/module/spring-boot-jdbc/src/main/java/org/springframework/boot/jdbc/health/DataSourceHealthIndicator.java @@ -24,6 +24,8 @@ import java.util.List; import javax.sql.DataSource; +import org.jspecify.annotations.Nullable; + import org.springframework.beans.factory.InitializingBean; import org.springframework.boot.health.contributor.AbstractHealthIndicator; import org.springframework.boot.health.contributor.Health; @@ -51,11 +53,11 @@ import org.springframework.util.StringUtils; */ public class DataSourceHealthIndicator extends AbstractHealthIndicator implements InitializingBean { - private DataSource dataSource; + private @Nullable DataSource dataSource; - private String query; + private @Nullable String query; - private JdbcTemplate jdbcTemplate; + private @Nullable JdbcTemplate jdbcTemplate; /** * Create a new {@link DataSourceHealthIndicator} instance. @@ -69,7 +71,7 @@ public class DataSourceHealthIndicator extends AbstractHealthIndicator implement * {@link DataSource}. * @param dataSource the data source */ - public DataSourceHealthIndicator(DataSource dataSource) { + public DataSourceHealthIndicator(@Nullable DataSource dataSource) { this(dataSource, null); } @@ -79,7 +81,7 @@ public class DataSourceHealthIndicator extends AbstractHealthIndicator implement * @param dataSource the data source * @param query the validation query to use (can be {@code null}) */ - public DataSourceHealthIndicator(DataSource dataSource, String query) { + public DataSourceHealthIndicator(@Nullable DataSource dataSource, @Nullable String query) { super("DataSource health check failed"); this.dataSource = dataSource; this.query = query; @@ -102,7 +104,8 @@ public class DataSourceHealthIndicator extends AbstractHealthIndicator implement } private void doDataSourceHealthCheck(Health.Builder builder) { - builder.up().withDetail("database", getProduct()); + Assert.state(this.jdbcTemplate != null, "'jdbcTemplate' must not be null"); + builder.up().withDetail("database", getProduct(this.jdbcTemplate)); String validationQuery = this.query; if (StringUtils.hasText(validationQuery)) { builder.withDetail("validationQuery", validationQuery); @@ -113,21 +116,21 @@ public class DataSourceHealthIndicator extends AbstractHealthIndicator implement } else { builder.withDetail("validationQuery", "isValid()"); - boolean valid = isConnectionValid(); + boolean valid = isConnectionValid(this.jdbcTemplate); builder.status((valid) ? Status.UP : Status.DOWN); } } - private String getProduct() { - return this.jdbcTemplate.execute((ConnectionCallback) this::getProduct); + private String getProduct(JdbcTemplate jdbcTemplate) { + return jdbcTemplate.execute((ConnectionCallback) this::getProduct); } private String getProduct(Connection connection) throws SQLException { return connection.getMetaData().getDatabaseProductName(); } - private Boolean isConnectionValid() { - return this.jdbcTemplate.execute((ConnectionCallback) this::isConnectionValid); + private Boolean isConnectionValid(JdbcTemplate jdbcTemplate) { + return jdbcTemplate.execute((ConnectionCallback) this::isConnectionValid); } private Boolean isConnectionValid(Connection connection) throws SQLException { @@ -156,7 +159,7 @@ public class DataSourceHealthIndicator extends AbstractHealthIndicator implement * Return the validation query or {@code null}. * @return the query */ - public String getQuery() { + public @Nullable String getQuery() { return this.query; } @@ -172,6 +175,12 @@ public class DataSourceHealthIndicator extends AbstractHealthIndicator implement if (columns != 1) { throw new IncorrectResultSetColumnCountException(1, columns); } + return getResultSetValue(rs); + } + + // RowMapper.mapRow isn't defined as @Nullable return type + @SuppressWarnings("NullAway") + private Object getResultSetValue(ResultSet rs) throws SQLException { return JdbcUtils.getResultSetValue(rs, 1); } diff --git a/module/spring-boot-jdbc/src/main/java/org/springframework/boot/jdbc/health/package-info.java b/module/spring-boot-jdbc/src/main/java/org/springframework/boot/jdbc/health/package-info.java index 8b08ab049fa..17a29b46ccc 100644 --- a/module/spring-boot-jdbc/src/main/java/org/springframework/boot/jdbc/health/package-info.java +++ b/module/spring-boot-jdbc/src/main/java/org/springframework/boot/jdbc/health/package-info.java @@ -17,4 +17,7 @@ /** * Health integration for JDBC. */ +@NullMarked package org.springframework.boot.jdbc.health; + +import org.jspecify.annotations.NullMarked; diff --git a/module/spring-boot-jdbc/src/main/java/org/springframework/boot/jdbc/init/package-info.java b/module/spring-boot-jdbc/src/main/java/org/springframework/boot/jdbc/init/package-info.java index bf8710e6a77..e643e1efdb1 100644 --- a/module/spring-boot-jdbc/src/main/java/org/springframework/boot/jdbc/init/package-info.java +++ b/module/spring-boot-jdbc/src/main/java/org/springframework/boot/jdbc/init/package-info.java @@ -18,4 +18,7 @@ * Support for initialization of an SQL database using a JDBC {@link javax.sql.DataSource * DataSource}. */ +@NullMarked package org.springframework.boot.jdbc.init; + +import org.jspecify.annotations.NullMarked; diff --git a/module/spring-boot-jdbc/src/main/java/org/springframework/boot/jdbc/metadata/AbstractDataSourcePoolMetadata.java b/module/spring-boot-jdbc/src/main/java/org/springframework/boot/jdbc/metadata/AbstractDataSourcePoolMetadata.java index ef9b6169274..ee9c66c7476 100644 --- a/module/spring-boot-jdbc/src/main/java/org/springframework/boot/jdbc/metadata/AbstractDataSourcePoolMetadata.java +++ b/module/spring-boot-jdbc/src/main/java/org/springframework/boot/jdbc/metadata/AbstractDataSourcePoolMetadata.java @@ -18,6 +18,8 @@ package org.springframework.boot.jdbc.metadata; import javax.sql.DataSource; +import org.jspecify.annotations.Nullable; + /** * A base {@link DataSourcePoolMetadata} implementation. * @@ -38,7 +40,7 @@ public abstract class AbstractDataSourcePoolMetadata imple } @Override - public Float getUsage() { + public @Nullable Float getUsage() { Integer maxSize = getMax(); Integer currentSize = getActive(); if (maxSize == null || currentSize == null) { diff --git a/module/spring-boot-jdbc/src/main/java/org/springframework/boot/jdbc/metadata/CompositeDataSourcePoolMetadataProvider.java b/module/spring-boot-jdbc/src/main/java/org/springframework/boot/jdbc/metadata/CompositeDataSourcePoolMetadataProvider.java index 50fb9212c80..5b9a16a1b0e 100644 --- a/module/spring-boot-jdbc/src/main/java/org/springframework/boot/jdbc/metadata/CompositeDataSourcePoolMetadataProvider.java +++ b/module/spring-boot-jdbc/src/main/java/org/springframework/boot/jdbc/metadata/CompositeDataSourcePoolMetadataProvider.java @@ -22,6 +22,8 @@ import java.util.List; import javax.sql.DataSource; +import org.jspecify.annotations.Nullable; + /** * A {@link DataSourcePoolMetadataProvider} implementation that returns the first * {@link DataSourcePoolMetadata} that is found by one of its delegate. @@ -38,12 +40,13 @@ public class CompositeDataSourcePoolMetadataProvider implements DataSourcePoolMe * collection of delegates to use. * @param providers the data source pool metadata providers */ - public CompositeDataSourcePoolMetadataProvider(Collection providers) { + public CompositeDataSourcePoolMetadataProvider( + @Nullable Collection providers) { this.providers = (providers != null) ? List.copyOf(providers) : Collections.emptyList(); } @Override - public DataSourcePoolMetadata getDataSourcePoolMetadata(DataSource dataSource) { + public @Nullable DataSourcePoolMetadata getDataSourcePoolMetadata(DataSource dataSource) { for (DataSourcePoolMetadataProvider provider : this.providers) { DataSourcePoolMetadata metadata = provider.getDataSourcePoolMetadata(dataSource); if (metadata != null) { diff --git a/module/spring-boot-jdbc/src/main/java/org/springframework/boot/jdbc/metadata/DataSourcePoolMetadata.java b/module/spring-boot-jdbc/src/main/java/org/springframework/boot/jdbc/metadata/DataSourcePoolMetadata.java index 82e31e913f1..905f8e18a9b 100644 --- a/module/spring-boot-jdbc/src/main/java/org/springframework/boot/jdbc/metadata/DataSourcePoolMetadata.java +++ b/module/spring-boot-jdbc/src/main/java/org/springframework/boot/jdbc/metadata/DataSourcePoolMetadata.java @@ -18,6 +18,8 @@ package org.springframework.boot.jdbc.metadata; import javax.sql.DataSource; +import org.jspecify.annotations.Nullable; + /** * Provides access meta-data that is commonly available from most pooled * {@link DataSource} implementations. @@ -41,14 +43,14 @@ public interface DataSourcePoolMetadata { * information to compute the poll usage. * @return the usage value or {@code null} */ - Float getUsage(); + @Nullable Float getUsage(); /** * Return the current number of active connections that have been allocated from the * data source or {@code null} if that information is not available. * @return the number of active connections or {@code null} */ - Integer getActive(); + @Nullable Integer getActive(); /** * Return the number of established but idle connections. Can also return {@code null} @@ -57,7 +59,7 @@ public interface DataSourcePoolMetadata { * @since 2.2.0 * @see #getActive() */ - default Integer getIdle() { + default @Nullable Integer getIdle() { return null; } @@ -67,21 +69,21 @@ public interface DataSourcePoolMetadata { * information is not available. * @return the maximum number of active connections or {@code null} */ - Integer getMax(); + @Nullable Integer getMax(); /** * Return the minimum number of idle connections in the pool or {@code null} if that * information is not available. * @return the minimum number of active connections or {@code null} */ - Integer getMin(); + @Nullable Integer getMin(); /** * Return the query to use to validate that a connection is valid or {@code null} if * that information is not available. * @return the validation query or {@code null} */ - String getValidationQuery(); + @Nullable String getValidationQuery(); /** * The default auto-commit state of connections created by this pool. If not set @@ -89,6 +91,6 @@ public interface DataSourcePoolMetadata { * java.sql.Connection.setAutoCommit(boolean) method will not be called.) * @return the default auto-commit state or {@code null} */ - Boolean getDefaultAutoCommit(); + @Nullable Boolean getDefaultAutoCommit(); } diff --git a/module/spring-boot-jdbc/src/main/java/org/springframework/boot/jdbc/metadata/DataSourcePoolMetadataProvider.java b/module/spring-boot-jdbc/src/main/java/org/springframework/boot/jdbc/metadata/DataSourcePoolMetadataProvider.java index 890e4be7e63..970b17f2722 100644 --- a/module/spring-boot-jdbc/src/main/java/org/springframework/boot/jdbc/metadata/DataSourcePoolMetadataProvider.java +++ b/module/spring-boot-jdbc/src/main/java/org/springframework/boot/jdbc/metadata/DataSourcePoolMetadataProvider.java @@ -18,6 +18,8 @@ package org.springframework.boot.jdbc.metadata; import javax.sql.DataSource; +import org.jspecify.annotations.Nullable; + /** * Provide a {@link DataSourcePoolMetadata} based on a {@link DataSource}. * @@ -33,6 +35,6 @@ public interface DataSourcePoolMetadataProvider { * @param dataSource the data source * @return the data source pool metadata */ - DataSourcePoolMetadata getDataSourcePoolMetadata(DataSource dataSource); + @Nullable DataSourcePoolMetadata getDataSourcePoolMetadata(DataSource dataSource); } diff --git a/module/spring-boot-jdbc/src/main/java/org/springframework/boot/jdbc/metadata/HikariDataSourcePoolMetadata.java b/module/spring-boot-jdbc/src/main/java/org/springframework/boot/jdbc/metadata/HikariDataSourcePoolMetadata.java index e491d329b7d..95ee8c8db5c 100644 --- a/module/spring-boot-jdbc/src/main/java/org/springframework/boot/jdbc/metadata/HikariDataSourcePoolMetadata.java +++ b/module/spring-boot-jdbc/src/main/java/org/springframework/boot/jdbc/metadata/HikariDataSourcePoolMetadata.java @@ -20,6 +20,7 @@ import javax.sql.DataSource; import com.zaxxer.hikari.HikariDataSource; import com.zaxxer.hikari.pool.HikariPool; +import org.jspecify.annotations.Nullable; import org.springframework.beans.DirectFieldAccessor; @@ -36,9 +37,10 @@ public class HikariDataSourcePoolMetadata extends AbstractDataSourcePoolMetadata } @Override - public Integer getActive() { + public @Nullable Integer getActive() { try { - return getHikariPool().getActiveConnections(); + HikariPool hikariPool = getHikariPool(); + return (hikariPool != null) ? hikariPool.getActiveConnections() : null; } catch (Exception ex) { return null; @@ -46,16 +48,17 @@ public class HikariDataSourcePoolMetadata extends AbstractDataSourcePoolMetadata } @Override - public Integer getIdle() { + public @Nullable Integer getIdle() { try { - return getHikariPool().getIdleConnections(); + HikariPool hikariPool = getHikariPool(); + return (hikariPool != null) ? hikariPool.getIdleConnections() : null; } catch (Exception ex) { return null; } } - private HikariPool getHikariPool() { + private @Nullable HikariPool getHikariPool() { return (HikariPool) new DirectFieldAccessor(getDataSource()).getPropertyValue("pool"); } diff --git a/module/spring-boot-jdbc/src/main/java/org/springframework/boot/jdbc/metadata/OracleUcpDataSourcePoolMetadata.java b/module/spring-boot-jdbc/src/main/java/org/springframework/boot/jdbc/metadata/OracleUcpDataSourcePoolMetadata.java index 578d33db257..fb077feebba 100644 --- a/module/spring-boot-jdbc/src/main/java/org/springframework/boot/jdbc/metadata/OracleUcpDataSourcePoolMetadata.java +++ b/module/spring-boot-jdbc/src/main/java/org/springframework/boot/jdbc/metadata/OracleUcpDataSourcePoolMetadata.java @@ -21,6 +21,7 @@ import java.sql.SQLException; import javax.sql.DataSource; import oracle.ucp.jdbc.PoolDataSource; +import org.jspecify.annotations.Nullable; import org.springframework.util.StringUtils; @@ -37,7 +38,7 @@ public class OracleUcpDataSourcePoolMetadata extends AbstractDataSourcePoolMetad } @Override - public Integer getActive() { + public @Nullable Integer getActive() { try { return getDataSource().getBorrowedConnectionsCount(); } @@ -47,7 +48,7 @@ public class OracleUcpDataSourcePoolMetadata extends AbstractDataSourcePoolMetad } @Override - public Integer getIdle() { + public @Nullable Integer getIdle() { try { return getDataSource().getAvailableConnectionsCount(); } @@ -72,7 +73,7 @@ public class OracleUcpDataSourcePoolMetadata extends AbstractDataSourcePoolMetad } @Override - public Boolean getDefaultAutoCommit() { + public @Nullable Boolean getDefaultAutoCommit() { String autoCommit = getDataSource().getConnectionProperty("autoCommit"); return StringUtils.hasText(autoCommit) ? Boolean.valueOf(autoCommit) : null; } diff --git a/module/spring-boot-jdbc/src/main/java/org/springframework/boot/jdbc/metadata/package-info.java b/module/spring-boot-jdbc/src/main/java/org/springframework/boot/jdbc/metadata/package-info.java index 3c82c100502..0865a42e778 100644 --- a/module/spring-boot-jdbc/src/main/java/org/springframework/boot/jdbc/metadata/package-info.java +++ b/module/spring-boot-jdbc/src/main/java/org/springframework/boot/jdbc/metadata/package-info.java @@ -17,4 +17,7 @@ /** * Support for accessing JDBC {@link javax.sql.DataSource} metadata. */ +@NullMarked package org.springframework.boot.jdbc.metadata; + +import org.jspecify.annotations.NullMarked; diff --git a/module/spring-boot-jdbc/src/main/java/org/springframework/boot/jdbc/metrics/DataSourcePoolMetrics.java b/module/spring-boot-jdbc/src/main/java/org/springframework/boot/jdbc/metrics/DataSourcePoolMetrics.java index a496a8699dc..18d5267e066 100644 --- a/module/spring-boot-jdbc/src/main/java/org/springframework/boot/jdbc/metrics/DataSourcePoolMetrics.java +++ b/module/spring-boot-jdbc/src/main/java/org/springframework/boot/jdbc/metrics/DataSourcePoolMetrics.java @@ -27,6 +27,7 @@ import io.micrometer.core.instrument.MeterRegistry; import io.micrometer.core.instrument.Tag; import io.micrometer.core.instrument.Tags; import io.micrometer.core.instrument.binder.MeterBinder; +import org.jspecify.annotations.Nullable; import org.springframework.boot.jdbc.metadata.CompositeDataSourcePoolMetadataProvider; import org.springframework.boot.jdbc.metadata.DataSourcePoolMetadata; @@ -64,6 +65,7 @@ public class DataSourcePoolMetrics implements MeterBinder { } @Override + @SuppressWarnings("NullAway") // Lambda isn't detected with the correct nullability public void bindTo(MeterRegistry registry) { if (this.metadataProvider.getDataSourcePoolMetadata(this.dataSource) != null) { bindPoolMetadata(registry, "active", @@ -79,18 +81,20 @@ public class DataSourcePoolMetrics implements MeterBinder { } } + @SuppressWarnings("NullAway") // Lambda isn't detected with the correct nullability private void bindPoolMetadata(MeterRegistry registry, String metricName, String description, - Function function) { + Function function) { bindDataSource(registry, metricName, description, this.metadataProvider.getValueFunction(function)); } private void bindDataSource(MeterRegistry registry, String metricName, String description, - Function function) { + Function function) { if (function.apply(this.dataSource) != null) { - Gauge.builder("jdbc.connections." + metricName, this.dataSource, (m) -> function.apply(m).doubleValue()) - .tags(this.tags) - .description(description) - .register(registry); + Gauge.builder("jdbc.connections." + metricName, this.dataSource, (m) -> { + Number value = function.apply(m); + Assert.state(value != null, "'value' must not be null"); + return value.doubleValue(); + }).tags(this.tags).description(description).register(registry); } } @@ -104,12 +108,17 @@ public class DataSourcePoolMetrics implements MeterBinder { this.metadataProvider = metadataProvider; } - Function getValueFunction(Function function) { - return (dataSource) -> function.apply(getDataSourcePoolMetadata(dataSource)); + Function getValueFunction( + Function function) { + return (dataSource) -> { + DataSourcePoolMetadata dataSourcePoolMetadata = getDataSourcePoolMetadata(dataSource); + Assert.state(dataSourcePoolMetadata != null, "'dataSourcePoolMetadata' must not be null"); + return function.apply(dataSourcePoolMetadata); + }; } @Override - public DataSourcePoolMetadata getDataSourcePoolMetadata(DataSource dataSource) { + public @Nullable DataSourcePoolMetadata getDataSourcePoolMetadata(DataSource dataSource) { return cache.computeIfAbsent(dataSource, (key) -> this.metadataProvider.getDataSourcePoolMetadata(dataSource)); } diff --git a/module/spring-boot-jdbc/src/main/java/org/springframework/boot/jdbc/metrics/package-info.java b/module/spring-boot-jdbc/src/main/java/org/springframework/boot/jdbc/metrics/package-info.java index 6f9f61e739e..b0901680d2f 100644 --- a/module/spring-boot-jdbc/src/main/java/org/springframework/boot/jdbc/metrics/package-info.java +++ b/module/spring-boot-jdbc/src/main/java/org/springframework/boot/jdbc/metrics/package-info.java @@ -17,4 +17,7 @@ /** * Metrics for JDBC. */ +@NullMarked package org.springframework.boot.jdbc.metrics; + +import org.jspecify.annotations.NullMarked; diff --git a/module/spring-boot-jdbc/src/main/java/org/springframework/boot/jdbc/package-info.java b/module/spring-boot-jdbc/src/main/java/org/springframework/boot/jdbc/package-info.java index 9edb08fedc5..d5b12b9bf35 100644 --- a/module/spring-boot-jdbc/src/main/java/org/springframework/boot/jdbc/package-info.java +++ b/module/spring-boot-jdbc/src/main/java/org/springframework/boot/jdbc/package-info.java @@ -17,4 +17,7 @@ /** * Support for Java Database Connectivity (JDBC). */ +@NullMarked package org.springframework.boot.jdbc; + +import org.jspecify.annotations.NullMarked; diff --git a/module/spring-boot-jdbc/src/main/java/org/springframework/boot/jdbc/testcontainers/package-info.java b/module/spring-boot-jdbc/src/main/java/org/springframework/boot/jdbc/testcontainers/package-info.java index 03956e55e9f..0260522a250 100644 --- a/module/spring-boot-jdbc/src/main/java/org/springframework/boot/jdbc/testcontainers/package-info.java +++ b/module/spring-boot-jdbc/src/main/java/org/springframework/boot/jdbc/testcontainers/package-info.java @@ -17,4 +17,7 @@ /** * Support for testcontainers JDBC service connections. */ +@NullMarked package org.springframework.boot.jdbc.testcontainers; + +import org.jspecify.annotations.NullMarked;