diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/jdbc/DataSourceBuilder.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/jdbc/DataSourceBuilder.java index 84b9053223..f9aa8af28d 100644 --- a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/jdbc/DataSourceBuilder.java +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/jdbc/DataSourceBuilder.java @@ -253,20 +253,27 @@ public final class DataSourceBuilder { */ private enum DataSourceProperty { - URL("url", "URL"), + URL(false, "url", "URL"), - DRIVER_CLASS_NAME("driverClassName"), + DRIVER_CLASS_NAME(true, "driverClassName"), - USERNAME("username", "user"), + USERNAME(false, "username", "user"), - PASSWORD("password"); + PASSWORD(false, "password"); + + private boolean optional; private final String[] names; - DataSourceProperty(String... names) { + DataSourceProperty(boolean optional, String... names) { + this.optional = optional; this.names = names; } + boolean isOptional() { + return this.optional; + } + @Override public String toString() { return this.names[0]; @@ -343,18 +350,23 @@ public final class DataSourceBuilder { @Override public void set(T dataSource, DataSourceProperty property, String value) { MappedDataSourceProperty mappedProperty = getMapping(property); - mappedProperty.set(dataSource, value); + if (mappedProperty != null) { + mappedProperty.set(dataSource, value); + } } @Override public String get(T dataSource, DataSourceProperty property) { MappedDataSourceProperty mappedProperty = getMapping(property); - return mappedProperty.get(dataSource); + if (mappedProperty != null) { + return mappedProperty.get(dataSource); + } + return null; } private MappedDataSourceProperty getMapping(DataSourceProperty property) { MappedDataSourceProperty mappedProperty = this.mappedProperties.get(property); - UnsupportedDataSourcePropertyException.throwIf(mappedProperty == null, + UnsupportedDataSourcePropertyException.throwIf(!property.isOptional() && mappedProperty == null, () -> "No mapping found for " + property); return mappedProperty; } @@ -439,8 +451,11 @@ public final class DataSourceBuilder { void set(T dataSource, String value) { try { - UnsupportedDataSourcePropertyException.throwIf(this.setter == null, - () -> "No setter mapped for '" + this.property + "' property"); + if (this.setter == null) { + UnsupportedDataSourcePropertyException.throwIf(!this.property.isOptional(), + () -> "No setter mapped for '" + this.property + "' property"); + return; + } this.setter.set(dataSource, convertFromString(value)); } catch (SQLException ex) { @@ -450,8 +465,11 @@ public final class DataSourceBuilder { String get(T dataSource) { try { - UnsupportedDataSourcePropertyException.throwIf(this.getter == null, - () -> "No getter mapped for '" + this.property + "' property"); + if (this.getter == null) { + UnsupportedDataSourcePropertyException.throwIf(!this.property.isOptional(), + () -> "No getter mapped for '" + this.property + "' property"); + return null; + } return convertToString(this.getter.get(dataSource)); } catch (SQLException ex) { @@ -522,19 +540,27 @@ public final class DataSourceBuilder { @Override public void set(T dataSource, DataSourceProperty property, String value) { Method method = getMethod(property, this.setters); - ReflectionUtils.invokeMethod(method, dataSource, value); + if (method != null) { + ReflectionUtils.invokeMethod(method, dataSource, value); + } } @Override public String get(T dataSource, DataSourceProperty property) { Method method = getMethod(property, this.getters); - return (String) ReflectionUtils.invokeMethod(method, dataSource); + if (method != null) { + return (String) ReflectionUtils.invokeMethod(method, dataSource); + } + return null; } - private Method getMethod(DataSourceProperty property, Map setters2) { - Method method = setters2.get(property); - UnsupportedDataSourcePropertyException.throwIf(method == null, - () -> "Unable to find suitable method for " + property); + private Method getMethod(DataSourceProperty property, Map methods) { + Method method = methods.get(property); + if (method == null) { + UnsupportedDataSourcePropertyException.throwIf(!property.isOptional(), + () -> "Unable to find suitable method for " + property); + return null; + } ReflectionUtils.makeAccessible(method); return method; } diff --git a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/jdbc/DataSourceBuilderTests.java b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/jdbc/DataSourceBuilderTests.java index 468ddd2e42..f8428a8ecf 100644 --- a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/jdbc/DataSourceBuilderTests.java +++ b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/jdbc/DataSourceBuilderTests.java @@ -46,6 +46,7 @@ import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseType; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatExceptionOfType; +import static org.assertj.core.api.Assertions.assertThatNoException; /** * Tests for {@link DataSourceBuilder}. @@ -143,6 +144,17 @@ class DataSourceBuilderTests { assertThat(oracleDataSource.getUser()).isEqualTo("test"); } + @Test // gh-26631 + void buildWhenOracleTypeSpecifiedWithDriverClassReturnsExpectedDataSource() throws SQLException { + this.dataSource = DataSourceBuilder.create().url("jdbc:oracle:thin:@localhost:1521:xe") + .type(OracleDataSource.class).driverClassName("oracle.jdbc.pool.OracleDataSource").username("test") + .build(); + assertThat(this.dataSource).isInstanceOf(OracleDataSource.class); + OracleDataSource oracleDataSource = (OracleDataSource) this.dataSource; + assertThat(oracleDataSource.getURL()).isEqualTo("jdbc:oracle:thin:@localhost:1521:xe"); + assertThat(oracleDataSource.getUser()).isEqualTo("test"); + } + @Test void buildWhenOracleUcpTypeSpecifiedReturnsExpectedDataSource() { this.dataSource = DataSourceBuilder.create().driverClassName("org.hsqldb.jdbc.JDBCDriver") @@ -163,6 +175,16 @@ class DataSourceBuilderTests { assertThat(h2DataSource.getPassword()).isEqualTo("secret"); } + @Test // gh-26631 + void buildWhenH2TypeSpecifiedWithDriverClassReturnsExpectedDataSource() { + this.dataSource = DataSourceBuilder.create().url("jdbc:h2:test").type(JdbcDataSource.class) + .driverClassName("org.h2.jdbcx.JdbcDataSource").username("test").password("secret").build(); + assertThat(this.dataSource).isInstanceOf(JdbcDataSource.class); + JdbcDataSource h2DataSource = (JdbcDataSource) this.dataSource; + assertThat(h2DataSource.getUser()).isEqualTo("test"); + assertThat(h2DataSource.getPassword()).isEqualTo("secret"); + } + @Test void buildWhenPostgresTypeSpecifiedReturnsExpectedDataSource() { this.dataSource = DataSourceBuilder.create().url("jdbc:postgresql://localhost/test") @@ -172,6 +194,16 @@ class DataSourceBuilderTests { assertThat(pgDataSource.getUser()).isEqualTo("test"); } + @Test // gh-26631 + void buildWhenPostgresTypeSpecifiedWithDriverClassReturnsExpectedDataSource() { + this.dataSource = DataSourceBuilder.create().url("jdbc:postgresql://localhost/test") + .type(PGSimpleDataSource.class).driverClassName("org.postgresql.ds.PGSimpleDataSource").username("test") + .build(); + assertThat(this.dataSource).isInstanceOf(PGSimpleDataSource.class); + PGSimpleDataSource pgDataSource = (PGSimpleDataSource) this.dataSource; + assertThat(pgDataSource.getUser()).isEqualTo("test"); + } + @Test // gh-26647 void buildWhenSqlServerTypeSpecifiedReturnsExpectedDataSource() { this.dataSource = DataSourceBuilder.create().url("jdbc:sqlserver://localhost/test") @@ -182,8 +214,8 @@ class DataSourceBuilderTests { } @Test - void buildWhenMappedTypeSpecifiedAndNoSuitableMappingThrowsException() { - assertThatExceptionOfType(UnsupportedDataSourcePropertyException.class).isThrownBy( + void buildWhenMappedTypeSpecifiedAndNoSuitableOptionalMappingBuilds() { + assertThatNoException().isThrownBy( () -> DataSourceBuilder.create().type(OracleDataSource.class).driverClassName("com.example").build()); } @@ -214,9 +246,15 @@ class DataSourceBuilderTests { } @Test - void buildWhenCustomTypeSpecifiedAndNoSuitableSetterThrowsException() { - assertThatExceptionOfType(UnsupportedDataSourcePropertyException.class).isThrownBy(() -> DataSourceBuilder - .create().type(LimitedCustomDataSource.class).driverClassName("com.example").build()); + void buildWhenCustomTypeSpecifiedAndNoSuitableOptionalSetterBuilds() { + assertThatNoException().isThrownBy(() -> DataSourceBuilder.create().type(LimitedCustomDataSource.class) + .driverClassName("com.example").build()); + } + + @Test + void buildWhenCustomTypeSpecifiedAndNoSuitableMandatorySetterThrowsException() { + assertThatExceptionOfType(UnsupportedDataSourcePropertyException.class).isThrownBy( + () -> DataSourceBuilder.create().type(LimitedCustomDataSource.class).url("jdbc:com.example").build()); } @Test @@ -339,8 +377,6 @@ class DataSourceBuilderTests { private String password; - private String url; - @Override public Connection getConnection() throws SQLException { throw new UnsupportedOperationException(); @@ -367,6 +403,14 @@ class DataSourceBuilderTests { this.password = password; } + } + + static class CustomDataSource extends LimitedCustomDataSource { + + private String url; + + private String driverClassName; + String getUrl() { return this.url; } @@ -375,12 +419,6 @@ class DataSourceBuilderTests { this.url = url; } - } - - static class CustomDataSource extends LimitedCustomDataSource { - - private String driverClassName; - String getDriverClassName() { return this.driverClassName; }