Skip to content

Commit 57bc232

Browse files
committed
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
1 parent d9b4d7b commit 57bc232

File tree

2 files changed

+186
-24
lines changed

2 files changed

+186
-24
lines changed

spring-boot-project/spring-boot/src/main/java/org/springframework/boot/jdbc/DataSourceBuilder.java

Lines changed: 121 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,13 @@
1616

1717
package org.springframework.boot.jdbc;
1818

19+
import java.util.ArrayList;
20+
import java.util.Collection;
1921
import java.util.HashMap;
22+
import java.util.List;
2023
import java.util.Map;
24+
import java.util.function.Consumer;
25+
import java.util.function.Function;
2126

2227
import javax.sql.DataSource;
2328

@@ -46,14 +51,11 @@
4651
*/
4752
public final class DataSourceBuilder<T extends DataSource> {
4853

49-
private static final String[] DATA_SOURCE_TYPE_NAMES = new String[] { "com.zaxxer.hikari.HikariDataSource",
50-
"org.apache.tomcat.jdbc.pool.DataSource", "org.apache.commons.dbcp2.BasicDataSource" };
51-
5254
private Class<? extends DataSource> type;
5355

54-
private ClassLoader classLoader;
56+
private final DataSourceSettingsResolver settingsResolver;
5557

56-
private Map<String, String> properties = new HashMap<>();
58+
private final Map<String, String> properties = new HashMap<>();
5759

5860
public static DataSourceBuilder<?> create() {
5961
return new DataSourceBuilder<>(null);
@@ -64,7 +66,7 @@ public static DataSourceBuilder<?> create(ClassLoader classLoader) {
6466
}
6567

6668
private DataSourceBuilder(ClassLoader classLoader) {
67-
this.classLoader = classLoader;
69+
this.settingsResolver = new DataSourceSettingsResolver(classLoader);
6870
}
6971

7072
@SuppressWarnings("unchecked")
@@ -87,9 +89,7 @@ private void maybeGetDriverClassName() {
8789
private void bind(DataSource result) {
8890
ConfigurationPropertySource source = new MapConfigurationPropertySource(this.properties);
8991
ConfigurationPropertyNameAliases aliases = new ConfigurationPropertyNameAliases();
90-
aliases.addAliases("driver-class-name", "driver-class");
91-
aliases.addAliases("url", "jdbc-url");
92-
aliases.addAliases("username", "user");
92+
this.settingsResolver.registerAliases(result, aliases);
9393
Binder binder = new Binder(source.withAliases(aliases));
9494
binder.bind(ConfigurationPropertyName.EMPTY, Bindable.ofInstance(result));
9595
}
@@ -120,25 +120,124 @@ public DataSourceBuilder<T> password(String password) {
120120
return this;
121121
}
122122

123-
@SuppressWarnings("unchecked")
124123
public static Class<? extends DataSource> findType(ClassLoader classLoader) {
125-
for (String name : DATA_SOURCE_TYPE_NAMES) {
126-
try {
127-
return (Class<? extends DataSource>) ClassUtils.forName(name, classLoader);
128-
}
129-
catch (Exception ex) {
130-
// Swallow and continue
131-
}
132-
}
133-
return null;
124+
DataSourceSettings preferredDataSourceSettings = new DataSourceSettingsResolver(classLoader)
125+
.getPreferredDataSourceSettings();
126+
return (preferredDataSourceSettings != null) ? preferredDataSourceSettings.getType() : null;
134127
}
135128

136129
private Class<? extends DataSource> getType() {
137-
Class<? extends DataSource> type = (this.type != null) ? this.type : findType(this.classLoader);
138-
if (type != null) {
139-
return type;
130+
if (this.type != null) {
131+
return this.type;
132+
}
133+
DataSourceSettings preferredDataSourceSettings = this.settingsResolver.getPreferredDataSourceSettings();
134+
if (preferredDataSourceSettings != null) {
135+
return preferredDataSourceSettings.getType();
140136
}
141137
throw new IllegalStateException("No supported DataSource type found");
142138
}
143139

140+
private static class DataSourceSettings {
141+
142+
private final Class<? extends DataSource> type;
143+
144+
private final Consumer<ConfigurationPropertyNameAliases> aliasesCustomizer;
145+
146+
DataSourceSettings(Class<? extends DataSource> type,
147+
Consumer<ConfigurationPropertyNameAliases> aliasesCustomizer) {
148+
this.type = type;
149+
this.aliasesCustomizer = aliasesCustomizer;
150+
}
151+
152+
DataSourceSettings(Class<? extends DataSource> type) {
153+
this(type, (aliases) -> {
154+
});
155+
}
156+
157+
Class<? extends DataSource> getType() {
158+
return this.type;
159+
}
160+
161+
void registerAliases(DataSource candidate, ConfigurationPropertyNameAliases aliases) {
162+
if (this.type != null && this.type.isInstance(candidate)) {
163+
this.aliasesCustomizer.accept(aliases);
164+
}
165+
}
166+
167+
}
168+
169+
private static class OracleDataSourceSettings extends DataSourceSettings {
170+
171+
OracleDataSourceSettings(Class<? extends DataSource> type) {
172+
super(type, (aliases) -> aliases.addAliases("username", "user"));
173+
}
174+
175+
@Override
176+
public Class<? extends DataSource> getType() {
177+
return null; // Base interface
178+
}
179+
180+
}
181+
182+
private static class DataSourceSettingsResolver {
183+
184+
private final DataSourceSettings preferredDataSourceSettings;
185+
186+
private final List<DataSourceSettings> allDataSourceSettings;
187+
188+
DataSourceSettingsResolver(ClassLoader classLoader) {
189+
List<DataSourceSettings> supportedProviders = resolveAvailableDataSourceSettings(classLoader);
190+
this.preferredDataSourceSettings = (!supportedProviders.isEmpty()) ? supportedProviders.get(0) : null;
191+
this.allDataSourceSettings = new ArrayList<>(supportedProviders);
192+
addIfAvailable(this.allDataSourceSettings,
193+
create(classLoader, "org.springframework.jdbc.datasource.SimpleDriverDataSource",
194+
(type) -> new DataSourceSettings(type,
195+
(aliases) -> aliases.addAliases("driver-class-name", "driver-class"))));
196+
addIfAvailable(this.allDataSourceSettings, create(classLoader,
197+
"oracle.jdbc.datasource.OracleCommonDataSource", OracleDataSourceSettings::new));
198+
}
199+
200+
private static List<DataSourceSettings> resolveAvailableDataSourceSettings(ClassLoader classLoader) {
201+
List<DataSourceSettings> providers = new ArrayList<>();
202+
addIfAvailable(providers, create(classLoader, "com.zaxxer.hikari.HikariDataSource",
203+
(type) -> new DataSourceSettings(type, (aliases) -> aliases.addAliases("url", "jdbc-url"))));
204+
addIfAvailable(providers,
205+
create(classLoader, "org.apache.tomcat.jdbc.pool.DataSource", DataSourceSettings::new));
206+
addIfAvailable(providers,
207+
create(classLoader, "org.apache.commons.dbcp2.BasicDataSource", DataSourceSettings::new));
208+
return providers;
209+
}
210+
211+
@SuppressWarnings("unchecked")
212+
private static DataSourceSettings create(ClassLoader classLoader, String target,
213+
Function<Class<? extends DataSource>, DataSourceSettings> factory) {
214+
if (ClassUtils.isPresent(target, classLoader)) {
215+
try {
216+
Class<? extends DataSource> type = (Class<? extends DataSource>) ClassUtils.forName(target,
217+
classLoader);
218+
return factory.apply(type);
219+
}
220+
catch (Exception ex) {
221+
// Ignore
222+
}
223+
}
224+
return null;
225+
}
226+
227+
private static void addIfAvailable(Collection<DataSourceSettings> list, DataSourceSettings dataSourceSettings) {
228+
if (dataSourceSettings != null) {
229+
list.add(dataSourceSettings);
230+
}
231+
}
232+
233+
DataSourceSettings getPreferredDataSourceSettings() {
234+
return this.preferredDataSourceSettings;
235+
}
236+
237+
void registerAliases(DataSource result, ConfigurationPropertyNameAliases aliases) {
238+
this.allDataSourceSettings.forEach((settings) -> settings.registerAliases(result, aliases));
239+
}
240+
241+
}
242+
144243
}

spring-boot-project/spring-boot/src/test/java/org/springframework/boot/jdbc/DataSourceBuilderTests.java

Lines changed: 65 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,11 +20,13 @@
2020
import java.io.IOException;
2121
import java.net.URL;
2222
import java.net.URLClassLoader;
23+
import java.sql.SQLException;
2324
import java.util.Arrays;
2425

2526
import javax.sql.DataSource;
2627

2728
import com.zaxxer.hikari.HikariDataSource;
29+
import oracle.jdbc.pool.OracleDataSource;
2830
import org.apache.commons.dbcp2.BasicDataSource;
2931
import org.h2.Driver;
3032
import org.junit.jupiter.api.AfterEach;
@@ -54,6 +56,8 @@ void shutdownDataSource() throws IOException {
5456
void defaultToHikari() {
5557
this.dataSource = DataSourceBuilder.create().url("jdbc:h2:test").build();
5658
assertThat(this.dataSource).isInstanceOf(HikariDataSource.class);
59+
HikariDataSource hikariDataSource = (HikariDataSource) this.dataSource;
60+
assertThat(hikariDataSource.getJdbcUrl()).isEqualTo("jdbc:h2:test");
5761
}
5862

5963
@Test
@@ -81,8 +85,33 @@ void specificTypeOfDataSource() {
8185
void dataSourceCanBeCreatedWithSimpleDriverDataSource() {
8286
this.dataSource = DataSourceBuilder.create().url("jdbc:h2:test").type(SimpleDriverDataSource.class).build();
8387
assertThat(this.dataSource).isInstanceOf(SimpleDriverDataSource.class);
84-
assertThat(((SimpleDriverDataSource) this.dataSource).getUrl()).isEqualTo("jdbc:h2:test");
85-
assertThat(((SimpleDriverDataSource) this.dataSource).getDriver()).isInstanceOf(Driver.class);
88+
SimpleDriverDataSource simpleDriverDataSource = (SimpleDriverDataSource) this.dataSource;
89+
assertThat(simpleDriverDataSource.getUrl()).isEqualTo("jdbc:h2:test");
90+
assertThat(simpleDriverDataSource.getDriver()).isInstanceOf(Driver.class);
91+
}
92+
93+
@Test
94+
void dataSourceCanBeCreatedWithOracleDataSource() throws SQLException {
95+
this.dataSource = DataSourceBuilder.create().url("jdbc:oracle:thin:@localhost:1521:xe")
96+
.type(OracleDataSource.class).username("test").build();
97+
assertThat(this.dataSource).isInstanceOf(OracleDataSource.class);
98+
OracleDataSource oracleDataSource = (OracleDataSource) this.dataSource;
99+
assertThat(oracleDataSource.getURL()).isEqualTo("jdbc:oracle:thin:@localhost:1521:xe");
100+
assertThat(oracleDataSource.getUser()).isEqualTo("test");
101+
}
102+
103+
@Test
104+
void dataSourceAliasesAreOnlyAppliedToRelevantDataSource() {
105+
this.dataSource = DataSourceBuilder.create().url("jdbc:h2:test").type(TestDataSource.class).username("test")
106+
.build();
107+
assertThat(this.dataSource).isInstanceOf(TestDataSource.class);
108+
TestDataSource testDataSource = (TestDataSource) this.dataSource;
109+
assertThat(testDataSource.getUrl()).isEqualTo("jdbc:h2:test");
110+
assertThat(testDataSource.getJdbcUrl()).isNull();
111+
assertThat(testDataSource.getUsername()).isEqualTo("test");
112+
assertThat(testDataSource.getUser()).isNull();
113+
assertThat(testDataSource.getDriverClassName()).isEqualTo(Driver.class.getName());
114+
assertThat(testDataSource.getDriverClass()).isNull();
86115
}
87116

88117
final class HidePackagesClassLoader extends URLClassLoader {
@@ -104,4 +133,38 @@ protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundE
104133

105134
}
106135

136+
public static class TestDataSource extends org.apache.tomcat.jdbc.pool.DataSource {
137+
138+
private String jdbcUrl;
139+
140+
private String user;
141+
142+
private String driverClass;
143+
144+
public String getJdbcUrl() {
145+
return this.jdbcUrl;
146+
}
147+
148+
public void setJdbcUrl(String jdbcUrl) {
149+
this.jdbcUrl = jdbcUrl;
150+
}
151+
152+
public String getUser() {
153+
return this.user;
154+
}
155+
156+
public void setUser(String user) {
157+
this.user = user;
158+
}
159+
160+
public String getDriverClass() {
161+
return this.driverClass;
162+
}
163+
164+
public void setDriverClass(String driverClass) {
165+
this.driverClass = driverClass;
166+
}
167+
168+
}
169+
107170
}

0 commit comments

Comments
 (0)