Apply DataSource aliases only when necessary
This commit makes sure that aliases are only applied when they match the DataSource being bound. This prevent an alias targetted to a DataSource to accidently match an unexpected property of another DataSource. Closes gh-23480
This commit is contained in:
@@ -16,8 +16,13 @@
|
||||
|
||||
package org.springframework.boot.jdbc;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Function;
|
||||
|
||||
import javax.sql.DataSource;
|
||||
|
||||
@@ -46,14 +51,11 @@ import org.springframework.util.ClassUtils;
|
||||
*/
|
||||
public final class DataSourceBuilder<T extends DataSource> {
|
||||
|
||||
private static final String[] DATA_SOURCE_TYPE_NAMES = new String[] { "com.zaxxer.hikari.HikariDataSource",
|
||||
"org.apache.tomcat.jdbc.pool.DataSource", "org.apache.commons.dbcp2.BasicDataSource" };
|
||||
|
||||
private Class<? extends DataSource> type;
|
||||
|
||||
private ClassLoader classLoader;
|
||||
private final DataSourceSettingsResolver settingsResolver;
|
||||
|
||||
private Map<String, String> properties = new HashMap<>();
|
||||
private final Map<String, String> properties = new HashMap<>();
|
||||
|
||||
public static DataSourceBuilder<?> create() {
|
||||
return new DataSourceBuilder<>(null);
|
||||
@@ -64,7 +66,7 @@ public final class DataSourceBuilder<T extends DataSource> {
|
||||
}
|
||||
|
||||
private DataSourceBuilder(ClassLoader classLoader) {
|
||||
this.classLoader = classLoader;
|
||||
this.settingsResolver = new DataSourceSettingsResolver(classLoader);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@@ -87,9 +89,7 @@ public final class DataSourceBuilder<T extends DataSource> {
|
||||
private void bind(DataSource result) {
|
||||
ConfigurationPropertySource source = new MapConfigurationPropertySource(this.properties);
|
||||
ConfigurationPropertyNameAliases aliases = new ConfigurationPropertyNameAliases();
|
||||
aliases.addAliases("driver-class-name", "driver-class");
|
||||
aliases.addAliases("url", "jdbc-url");
|
||||
aliases.addAliases("username", "user");
|
||||
this.settingsResolver.registerAliases(result, aliases);
|
||||
Binder binder = new Binder(source.withAliases(aliases));
|
||||
binder.bind(ConfigurationPropertyName.EMPTY, Bindable.ofInstance(result));
|
||||
}
|
||||
@@ -120,25 +120,124 @@ public final class DataSourceBuilder<T extends DataSource> {
|
||||
return this;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public static Class<? extends DataSource> findType(ClassLoader classLoader) {
|
||||
for (String name : DATA_SOURCE_TYPE_NAMES) {
|
||||
try {
|
||||
return (Class<? extends DataSource>) ClassUtils.forName(name, classLoader);
|
||||
}
|
||||
catch (Exception ex) {
|
||||
// Swallow and continue
|
||||
}
|
||||
}
|
||||
return null;
|
||||
DataSourceSettings preferredDataSourceSettings = new DataSourceSettingsResolver(classLoader)
|
||||
.getPreferredDataSourceSettings();
|
||||
return (preferredDataSourceSettings != null) ? preferredDataSourceSettings.getType() : null;
|
||||
}
|
||||
|
||||
private Class<? extends DataSource> getType() {
|
||||
Class<? extends DataSource> type = (this.type != null) ? this.type : findType(this.classLoader);
|
||||
if (type != null) {
|
||||
return type;
|
||||
if (this.type != null) {
|
||||
return this.type;
|
||||
}
|
||||
DataSourceSettings preferredDataSourceSettings = this.settingsResolver.getPreferredDataSourceSettings();
|
||||
if (preferredDataSourceSettings != null) {
|
||||
return preferredDataSourceSettings.getType();
|
||||
}
|
||||
throw new IllegalStateException("No supported DataSource type found");
|
||||
}
|
||||
|
||||
private static class DataSourceSettings {
|
||||
|
||||
private final Class<? extends DataSource> type;
|
||||
|
||||
private final Consumer<ConfigurationPropertyNameAliases> aliasesCustomizer;
|
||||
|
||||
DataSourceSettings(Class<? extends DataSource> type,
|
||||
Consumer<ConfigurationPropertyNameAliases> aliasesCustomizer) {
|
||||
this.type = type;
|
||||
this.aliasesCustomizer = aliasesCustomizer;
|
||||
}
|
||||
|
||||
DataSourceSettings(Class<? extends DataSource> type) {
|
||||
this(type, (aliases) -> {
|
||||
});
|
||||
}
|
||||
|
||||
Class<? extends DataSource> getType() {
|
||||
return this.type;
|
||||
}
|
||||
|
||||
void registerAliases(DataSource candidate, ConfigurationPropertyNameAliases aliases) {
|
||||
if (this.type != null && this.type.isInstance(candidate)) {
|
||||
this.aliasesCustomizer.accept(aliases);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private static class OracleDataSourceSettings extends DataSourceSettings {
|
||||
|
||||
OracleDataSourceSettings(Class<? extends DataSource> type) {
|
||||
super(type, (aliases) -> aliases.addAliases("username", "user"));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<? extends DataSource> getType() {
|
||||
return null; // Base interface
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private static class DataSourceSettingsResolver {
|
||||
|
||||
private final DataSourceSettings preferredDataSourceSettings;
|
||||
|
||||
private final List<DataSourceSettings> allDataSourceSettings;
|
||||
|
||||
DataSourceSettingsResolver(ClassLoader classLoader) {
|
||||
List<DataSourceSettings> supportedProviders = resolveAvailableDataSourceSettings(classLoader);
|
||||
this.preferredDataSourceSettings = (!supportedProviders.isEmpty()) ? supportedProviders.get(0) : null;
|
||||
this.allDataSourceSettings = new ArrayList<>(supportedProviders);
|
||||
addIfAvailable(this.allDataSourceSettings,
|
||||
create(classLoader, "org.springframework.jdbc.datasource.SimpleDriverDataSource",
|
||||
(type) -> new DataSourceSettings(type,
|
||||
(aliases) -> aliases.addAliases("driver-class-name", "driver-class"))));
|
||||
addIfAvailable(this.allDataSourceSettings, create(classLoader,
|
||||
"oracle.jdbc.datasource.OracleCommonDataSource", OracleDataSourceSettings::new));
|
||||
}
|
||||
|
||||
private static List<DataSourceSettings> resolveAvailableDataSourceSettings(ClassLoader classLoader) {
|
||||
List<DataSourceSettings> providers = new ArrayList<>();
|
||||
addIfAvailable(providers, create(classLoader, "com.zaxxer.hikari.HikariDataSource",
|
||||
(type) -> new DataSourceSettings(type, (aliases) -> aliases.addAliases("url", "jdbc-url"))));
|
||||
addIfAvailable(providers,
|
||||
create(classLoader, "org.apache.tomcat.jdbc.pool.DataSource", DataSourceSettings::new));
|
||||
addIfAvailable(providers,
|
||||
create(classLoader, "org.apache.commons.dbcp2.BasicDataSource", DataSourceSettings::new));
|
||||
return providers;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private static DataSourceSettings create(ClassLoader classLoader, String target,
|
||||
Function<Class<? extends DataSource>, DataSourceSettings> factory) {
|
||||
if (ClassUtils.isPresent(target, classLoader)) {
|
||||
try {
|
||||
Class<? extends DataSource> type = (Class<? extends DataSource>) ClassUtils.forName(target,
|
||||
classLoader);
|
||||
return factory.apply(type);
|
||||
}
|
||||
catch (Exception ex) {
|
||||
// Ignore
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private static void addIfAvailable(Collection<DataSourceSettings> list, DataSourceSettings dataSourceSettings) {
|
||||
if (dataSourceSettings != null) {
|
||||
list.add(dataSourceSettings);
|
||||
}
|
||||
}
|
||||
|
||||
DataSourceSettings getPreferredDataSourceSettings() {
|
||||
return this.preferredDataSourceSettings;
|
||||
}
|
||||
|
||||
void registerAliases(DataSource result, ConfigurationPropertyNameAliases aliases) {
|
||||
this.allDataSourceSettings.forEach((settings) -> settings.registerAliases(result, aliases));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -20,11 +20,13 @@ import java.io.Closeable;
|
||||
import java.io.IOException;
|
||||
import java.net.URL;
|
||||
import java.net.URLClassLoader;
|
||||
import java.sql.SQLException;
|
||||
import java.util.Arrays;
|
||||
|
||||
import javax.sql.DataSource;
|
||||
|
||||
import com.zaxxer.hikari.HikariDataSource;
|
||||
import oracle.jdbc.pool.OracleDataSource;
|
||||
import org.apache.commons.dbcp2.BasicDataSource;
|
||||
import org.h2.Driver;
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
@@ -54,6 +56,8 @@ class DataSourceBuilderTests {
|
||||
void defaultToHikari() {
|
||||
this.dataSource = DataSourceBuilder.create().url("jdbc:h2:test").build();
|
||||
assertThat(this.dataSource).isInstanceOf(HikariDataSource.class);
|
||||
HikariDataSource hikariDataSource = (HikariDataSource) this.dataSource;
|
||||
assertThat(hikariDataSource.getJdbcUrl()).isEqualTo("jdbc:h2:test");
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -81,8 +85,33 @@ class DataSourceBuilderTests {
|
||||
void dataSourceCanBeCreatedWithSimpleDriverDataSource() {
|
||||
this.dataSource = DataSourceBuilder.create().url("jdbc:h2:test").type(SimpleDriverDataSource.class).build();
|
||||
assertThat(this.dataSource).isInstanceOf(SimpleDriverDataSource.class);
|
||||
assertThat(((SimpleDriverDataSource) this.dataSource).getUrl()).isEqualTo("jdbc:h2:test");
|
||||
assertThat(((SimpleDriverDataSource) this.dataSource).getDriver()).isInstanceOf(Driver.class);
|
||||
SimpleDriverDataSource simpleDriverDataSource = (SimpleDriverDataSource) this.dataSource;
|
||||
assertThat(simpleDriverDataSource.getUrl()).isEqualTo("jdbc:h2:test");
|
||||
assertThat(simpleDriverDataSource.getDriver()).isInstanceOf(Driver.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
void dataSourceCanBeCreatedWithOracleDataSource() throws SQLException {
|
||||
this.dataSource = DataSourceBuilder.create().url("jdbc:oracle:thin:@localhost:1521:xe")
|
||||
.type(OracleDataSource.class).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 dataSourceAliasesAreOnlyAppliedToRelevantDataSource() {
|
||||
this.dataSource = DataSourceBuilder.create().url("jdbc:h2:test").type(TestDataSource.class).username("test")
|
||||
.build();
|
||||
assertThat(this.dataSource).isInstanceOf(TestDataSource.class);
|
||||
TestDataSource testDataSource = (TestDataSource) this.dataSource;
|
||||
assertThat(testDataSource.getUrl()).isEqualTo("jdbc:h2:test");
|
||||
assertThat(testDataSource.getJdbcUrl()).isNull();
|
||||
assertThat(testDataSource.getUsername()).isEqualTo("test");
|
||||
assertThat(testDataSource.getUser()).isNull();
|
||||
assertThat(testDataSource.getDriverClassName()).isEqualTo(Driver.class.getName());
|
||||
assertThat(testDataSource.getDriverClass()).isNull();
|
||||
}
|
||||
|
||||
final class HidePackagesClassLoader extends URLClassLoader {
|
||||
@@ -104,4 +133,38 @@ class DataSourceBuilderTests {
|
||||
|
||||
}
|
||||
|
||||
public static class TestDataSource extends org.apache.tomcat.jdbc.pool.DataSource {
|
||||
|
||||
private String jdbcUrl;
|
||||
|
||||
private String user;
|
||||
|
||||
private String driverClass;
|
||||
|
||||
public String getJdbcUrl() {
|
||||
return this.jdbcUrl;
|
||||
}
|
||||
|
||||
public void setJdbcUrl(String jdbcUrl) {
|
||||
this.jdbcUrl = jdbcUrl;
|
||||
}
|
||||
|
||||
public String getUser() {
|
||||
return this.user;
|
||||
}
|
||||
|
||||
public void setUser(String user) {
|
||||
this.user = user;
|
||||
}
|
||||
|
||||
public String getDriverClass() {
|
||||
return this.driverClass;
|
||||
}
|
||||
|
||||
public void setDriverClass(String driverClass) {
|
||||
this.driverClass = driverClass;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user