Commit eed620fc authored by Scott Frederick's avatar Scott Frederick Committed by Phillip Webb

Allow `driverClassName` to be optional

Update `DataSourceBuilder` so that the `driverClassName` may be optional
and silently ignored if it set but the underlying type does not have
a getter/setter.

This restores Spring Boot 2.4 behavior.

Fixes gh-26631
Co-authored-by: 's avatarPhillip Webb <pwebb@vmware.com>
parent c679b4cc
...@@ -253,20 +253,27 @@ public final class DataSourceBuilder<T extends DataSource> { ...@@ -253,20 +253,27 @@ public final class DataSourceBuilder<T extends DataSource> {
*/ */
private enum DataSourceProperty { 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; private final String[] names;
DataSourceProperty(String... names) { DataSourceProperty(boolean optional, String... names) {
this.optional = optional;
this.names = names; this.names = names;
} }
boolean isOptional() {
return this.optional;
}
@Override @Override
public String toString() { public String toString() {
return this.names[0]; return this.names[0];
...@@ -343,18 +350,23 @@ public final class DataSourceBuilder<T extends DataSource> { ...@@ -343,18 +350,23 @@ public final class DataSourceBuilder<T extends DataSource> {
@Override @Override
public void set(T dataSource, DataSourceProperty property, String value) { public void set(T dataSource, DataSourceProperty property, String value) {
MappedDataSourceProperty<T, ?> mappedProperty = getMapping(property); MappedDataSourceProperty<T, ?> mappedProperty = getMapping(property);
mappedProperty.set(dataSource, value); if (mappedProperty != null) {
mappedProperty.set(dataSource, value);
}
} }
@Override @Override
public String get(T dataSource, DataSourceProperty property) { public String get(T dataSource, DataSourceProperty property) {
MappedDataSourceProperty<T, ?> mappedProperty = getMapping(property); MappedDataSourceProperty<T, ?> mappedProperty = getMapping(property);
return mappedProperty.get(dataSource); if (mappedProperty != null) {
return mappedProperty.get(dataSource);
}
return null;
} }
private MappedDataSourceProperty<T, ?> getMapping(DataSourceProperty property) { private MappedDataSourceProperty<T, ?> getMapping(DataSourceProperty property) {
MappedDataSourceProperty<T, ?> mappedProperty = this.mappedProperties.get(property); MappedDataSourceProperty<T, ?> mappedProperty = this.mappedProperties.get(property);
UnsupportedDataSourcePropertyException.throwIf(mappedProperty == null, UnsupportedDataSourcePropertyException.throwIf(!property.isOptional() && mappedProperty == null,
() -> "No mapping found for " + property); () -> "No mapping found for " + property);
return mappedProperty; return mappedProperty;
} }
...@@ -439,8 +451,11 @@ public final class DataSourceBuilder<T extends DataSource> { ...@@ -439,8 +451,11 @@ public final class DataSourceBuilder<T extends DataSource> {
void set(T dataSource, String value) { void set(T dataSource, String value) {
try { try {
UnsupportedDataSourcePropertyException.throwIf(this.setter == null, if (this.setter == null) {
() -> "No setter mapped for '" + this.property + "' property"); UnsupportedDataSourcePropertyException.throwIf(!this.property.isOptional(),
() -> "No setter mapped for '" + this.property + "' property");
return;
}
this.setter.set(dataSource, convertFromString(value)); this.setter.set(dataSource, convertFromString(value));
} }
catch (SQLException ex) { catch (SQLException ex) {
...@@ -450,8 +465,11 @@ public final class DataSourceBuilder<T extends DataSource> { ...@@ -450,8 +465,11 @@ public final class DataSourceBuilder<T extends DataSource> {
String get(T dataSource) { String get(T dataSource) {
try { try {
UnsupportedDataSourcePropertyException.throwIf(this.getter == null, if (this.getter == null) {
() -> "No getter mapped for '" + this.property + "' property"); UnsupportedDataSourcePropertyException.throwIf(!this.property.isOptional(),
() -> "No getter mapped for '" + this.property + "' property");
return null;
}
return convertToString(this.getter.get(dataSource)); return convertToString(this.getter.get(dataSource));
} }
catch (SQLException ex) { catch (SQLException ex) {
...@@ -522,19 +540,27 @@ public final class DataSourceBuilder<T extends DataSource> { ...@@ -522,19 +540,27 @@ public final class DataSourceBuilder<T extends DataSource> {
@Override @Override
public void set(T dataSource, DataSourceProperty property, String value) { public void set(T dataSource, DataSourceProperty property, String value) {
Method method = getMethod(property, this.setters); Method method = getMethod(property, this.setters);
ReflectionUtils.invokeMethod(method, dataSource, value); if (method != null) {
ReflectionUtils.invokeMethod(method, dataSource, value);
}
} }
@Override @Override
public String get(T dataSource, DataSourceProperty property) { public String get(T dataSource, DataSourceProperty property) {
Method method = getMethod(property, this.getters); 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<DataSourceProperty, Method> setters2) { private Method getMethod(DataSourceProperty property, Map<DataSourceProperty, Method> methods) {
Method method = setters2.get(property); Method method = methods.get(property);
UnsupportedDataSourcePropertyException.throwIf(method == null, if (method == null) {
() -> "Unable to find suitable method for " + property); UnsupportedDataSourcePropertyException.throwIf(!property.isOptional(),
() -> "Unable to find suitable method for " + property);
return null;
}
ReflectionUtils.makeAccessible(method); ReflectionUtils.makeAccessible(method);
return method; return method;
} }
......
...@@ -46,6 +46,7 @@ import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseType; ...@@ -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.assertThat;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType; import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
import static org.assertj.core.api.Assertions.assertThatNoException;
/** /**
* Tests for {@link DataSourceBuilder}. * Tests for {@link DataSourceBuilder}.
...@@ -143,6 +144,17 @@ class DataSourceBuilderTests { ...@@ -143,6 +144,17 @@ class DataSourceBuilderTests {
assertThat(oracleDataSource.getUser()).isEqualTo("test"); 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 @Test
void buildWhenOracleUcpTypeSpecifiedReturnsExpectedDataSource() { void buildWhenOracleUcpTypeSpecifiedReturnsExpectedDataSource() {
this.dataSource = DataSourceBuilder.create().driverClassName("org.hsqldb.jdbc.JDBCDriver") this.dataSource = DataSourceBuilder.create().driverClassName("org.hsqldb.jdbc.JDBCDriver")
...@@ -163,6 +175,16 @@ class DataSourceBuilderTests { ...@@ -163,6 +175,16 @@ class DataSourceBuilderTests {
assertThat(h2DataSource.getPassword()).isEqualTo("secret"); 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 @Test
void buildWhenPostgresTypeSpecifiedReturnsExpectedDataSource() { void buildWhenPostgresTypeSpecifiedReturnsExpectedDataSource() {
this.dataSource = DataSourceBuilder.create().url("jdbc:postgresql://localhost/test") this.dataSource = DataSourceBuilder.create().url("jdbc:postgresql://localhost/test")
...@@ -172,6 +194,16 @@ class DataSourceBuilderTests { ...@@ -172,6 +194,16 @@ class DataSourceBuilderTests {
assertThat(pgDataSource.getUser()).isEqualTo("test"); 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 @Test // gh-26647
void buildWhenSqlServerTypeSpecifiedReturnsExpectedDataSource() { void buildWhenSqlServerTypeSpecifiedReturnsExpectedDataSource() {
this.dataSource = DataSourceBuilder.create().url("jdbc:sqlserver://localhost/test") this.dataSource = DataSourceBuilder.create().url("jdbc:sqlserver://localhost/test")
...@@ -182,8 +214,8 @@ class DataSourceBuilderTests { ...@@ -182,8 +214,8 @@ class DataSourceBuilderTests {
} }
@Test @Test
void buildWhenMappedTypeSpecifiedAndNoSuitableMappingThrowsException() { void buildWhenMappedTypeSpecifiedAndNoSuitableOptionalMappingBuilds() {
assertThatExceptionOfType(UnsupportedDataSourcePropertyException.class).isThrownBy( assertThatNoException().isThrownBy(
() -> DataSourceBuilder.create().type(OracleDataSource.class).driverClassName("com.example").build()); () -> DataSourceBuilder.create().type(OracleDataSource.class).driverClassName("com.example").build());
} }
...@@ -214,9 +246,15 @@ class DataSourceBuilderTests { ...@@ -214,9 +246,15 @@ class DataSourceBuilderTests {
} }
@Test @Test
void buildWhenCustomTypeSpecifiedAndNoSuitableSetterThrowsException() { void buildWhenCustomTypeSpecifiedAndNoSuitableOptionalSetterBuilds() {
assertThatExceptionOfType(UnsupportedDataSourcePropertyException.class).isThrownBy(() -> DataSourceBuilder assertThatNoException().isThrownBy(() -> DataSourceBuilder.create().type(LimitedCustomDataSource.class)
.create().type(LimitedCustomDataSource.class).driverClassName("com.example").build()); .driverClassName("com.example").build());
}
@Test
void buildWhenCustomTypeSpecifiedAndNoSuitableMandatorySetterThrowsException() {
assertThatExceptionOfType(UnsupportedDataSourcePropertyException.class).isThrownBy(
() -> DataSourceBuilder.create().type(LimitedCustomDataSource.class).url("jdbc:com.example").build());
} }
@Test @Test
...@@ -339,8 +377,6 @@ class DataSourceBuilderTests { ...@@ -339,8 +377,6 @@ class DataSourceBuilderTests {
private String password; private String password;
private String url;
@Override @Override
public Connection getConnection() throws SQLException { public Connection getConnection() throws SQLException {
throw new UnsupportedOperationException(); throw new UnsupportedOperationException();
...@@ -367,6 +403,14 @@ class DataSourceBuilderTests { ...@@ -367,6 +403,14 @@ class DataSourceBuilderTests {
this.password = password; this.password = password;
} }
}
static class CustomDataSource extends LimitedCustomDataSource {
private String url;
private String driverClassName;
String getUrl() { String getUrl() {
return this.url; return this.url;
} }
...@@ -375,12 +419,6 @@ class DataSourceBuilderTests { ...@@ -375,12 +419,6 @@ class DataSourceBuilderTests {
this.url = url; this.url = url;
} }
}
static class CustomDataSource extends LimitedCustomDataSource {
private String driverClassName;
String getDriverClassName() { String getDriverClassName() {
return this.driverClassName; return this.driverClassName;
} }
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment