diff --git a/spring-boot-project/spring-boot/build.gradle b/spring-boot-project/spring-boot/build.gradle
index 259d8d4c9f..9efa1cf7ea 100644
--- a/spring-boot-project/spring-boot/build.gradle
+++ b/spring-boot-project/spring-boot/build.gradle
@@ -20,8 +20,10 @@ dependencies {
optional("com.atomikos:transactions-jms")
optional("com.atomikos:transactions-jta")
optional("com.fasterxml.jackson.core:jackson-databind")
+ optional("com.h2database:h2")
optional("com.google.code.gson:gson")
optional("com.oracle.database.jdbc:ucp")
+ optional("com.oracle.database.jdbc:ojdbc8")
optional("com.samskivert:jmustache")
optional("com.zaxxer:HikariCP")
optional("io.netty:netty-tcnative-boringssl-static")
@@ -58,6 +60,7 @@ dependencies {
optional("org.jboss:jboss-transaction-spi")
optional("org.jooq:jooq")
optional("org.liquibase:liquibase-core")
+ optional("org.postgresql:postgresql")
optional("org.slf4j:jul-to-slf4j")
optional("org.slf4j:slf4j-api")
optional("org.springframework:spring-messaging")
@@ -76,11 +79,9 @@ dependencies {
testImplementation(project(":spring-boot-project:spring-boot-tools:spring-boot-test-support"))
testImplementation("com.google.appengine:appengine-api-1.0-sdk")
- testImplementation("com.h2database:h2")
testImplementation("com.ibm.db2:jcc")
testImplementation("com.jayway.jsonpath:json-path")
testImplementation("com.microsoft.sqlserver:mssql-jdbc")
- testImplementation("com.oracle.database.jdbc:ojdbc8")
testImplementation("com.squareup.okhttp3:okhttp")
testImplementation("com.sun.xml.messaging.saaj:saaj-impl")
testImplementation("io.projectreactor:reactor-test")
@@ -97,7 +98,6 @@ dependencies {
testImplementation("org.mariadb.jdbc:mariadb-java-client")
testImplementation("org.mockito:mockito-core")
testImplementation("org.mockito:mockito-junit-jupiter")
- testImplementation("org.postgresql:postgresql")
testImplementation("org.springframework:spring-context-support")
testImplementation("org.springframework.data:spring-data-redis")
testImplementation("org.springframework.data:spring-data-r2dbc")
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 0678d0633f..a5896e220c 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
@@ -16,241 +16,652 @@
package org.springframework.boot.jdbc;
-import java.util.ArrayList;
-import java.util.Collection;
+import java.lang.reflect.Method;
+import java.sql.SQLException;
+import java.util.Collections;
import java.util.HashMap;
-import java.util.List;
+import java.util.HashSet;
import java.util.Map;
-import java.util.function.Consumer;
-import java.util.function.Function;
+import java.util.Set;
+import java.util.function.Supplier;
import javax.sql.DataSource;
+import com.zaxxer.hikari.HikariDataSource;
+import oracle.jdbc.datasource.OracleDataSource;
+import oracle.ucp.jdbc.PoolDataSource;
+import oracle.ucp.jdbc.PoolDataSourceImpl;
+import org.apache.commons.dbcp2.BasicDataSource;
+import org.h2.jdbcx.JdbcDataSource;
+import org.postgresql.ds.PGSimpleDataSource;
+
import org.springframework.beans.BeanUtils;
-import org.springframework.boot.context.properties.bind.Bindable;
-import org.springframework.boot.context.properties.bind.Binder;
-import org.springframework.boot.context.properties.source.ConfigurationPropertyName;
-import org.springframework.boot.context.properties.source.ConfigurationPropertyNameAliases;
-import org.springframework.boot.context.properties.source.ConfigurationPropertySource;
-import org.springframework.boot.context.properties.source.MapConfigurationPropertySource;
+import org.springframework.core.ResolvableType;
+import org.springframework.jdbc.datasource.SimpleDriverDataSource;
+import org.springframework.jdbc.datasource.embedded.EmbeddedDatabase;
+import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
+import org.springframework.util.ReflectionUtils;
+import org.springframework.util.StringUtils;
/**
- * Convenience class for building a {@link DataSource} with common implementations and
- * properties. If HikariCP, Tomcat, Commons DBCP or Oracle UCP are on the classpath one of
- * them will be selected (in that order with Hikari first). In the interest of a uniform
- * interface, and so that there can be a fallback to an embedded database if one can be
- * detected on the classpath, only a small set of common configuration properties are
- * supported. To inject additional properties into the result you can downcast it, or use
+ * Convenience class for building a {@link DataSource}. Provides a limited subset of the
+ * properties supported by a typical {@link DataSource} as well as detection logic to pick
+ * the most suitable pooling {@link DataSource} implementation.
+ *
+ * The following pooling {@link DataSource} implementations are supported by this builder.
+ * When no {@link #type(Class) type} has been explicitly set, the first available pool
+ * implementation will be picked:
+ *
+ * - Hikari ({@code com.zaxxer.hikari.HikariDataSource})
+ * - Tomcat JDBC Pool ({@code org.apache.tomcat.jdbc.pool.DataSource})
+ * - Apache DBCP2 ({@code org.apache.commons.dbcp2.BasicDataSource})
+ * - Oracle UCP ({@code oracle.ucp.jdbc.PoolDataSourceImpl})
+ *
+ *
+ * The following non-pooling {@link DataSource} implementations can be used when
+ * explicitly set as a {@link #type(Class) type}:
+ *
+ * - Spring's {@code SimpleDriverDataSource}
+ * ({@code org.springframework.jdbc.datasource.SimpleDriverDataSource})
+ * - Oracle ({@code oracle.jdbc.datasource.OracleDataSource})
+ * - H2 ({@code org.h2.jdbcx.JdbcDataSource})
+ * - Postgres ({@code org.postgresql.ds.PGSimpleDataSource})
+ * - Any {@code DataSource} implementation with appropriately named methods
+ *
+ *
+ * This class is commonly used in an {@code @Bean} method and often combined with
* {@code @ConfigurationProperties}.
*
- * @param type of DataSource produced by the builder
+ * @param the {@link DataSource} type being built
* @author Dave Syer
* @author Madhura Bhave
* @author Fabio Grassi
+ * @author Phillip Webb
* @since 2.0.0
+ * @see #create()
+ * @see #create(ClassLoader)
+ * @see #derivedFrom(DataSource)
*/
public final class DataSourceBuilder {
- private Class extends DataSource> type;
+ private final ClassLoader classLoader;
- private final DataSourceSettingsResolver settingsResolver;
+ private final Map values = new HashMap<>();
- private final Map properties = new HashMap<>();
+ private Class type;
- public static DataSourceBuilder> create() {
- return new DataSourceBuilder<>(null);
+ private final T deriveFrom;
+
+ private DataSourceBuilder(ClassLoader classLoader) {
+ this.classLoader = classLoader;
+ this.deriveFrom = null;
}
+ @SuppressWarnings("unchecked")
+ private DataSourceBuilder(T deriveFrom) {
+ Assert.notNull(deriveFrom, "DataSource must not be null");
+ this.classLoader = deriveFrom.getClass().getClassLoader();
+ this.type = (Class) deriveFrom.getClass();
+ this.deriveFrom = deriveFrom;
+ }
+
+ /**
+ * Set the {@link DataSource} type that should be built.
+ * @param the datasource type
+ * @param type the datasource type
+ * @return this builder
+ */
+ @SuppressWarnings("unchecked")
+ public DataSourceBuilder type(Class type) {
+ this.type = (Class) type;
+ return (DataSourceBuilder) this;
+ }
+
+ /**
+ * Set the URL that should be used when building the datasource.
+ * @param url the JDBC url
+ * @return this builder
+ */
+ public DataSourceBuilder url(String url) {
+ set(DataSourceProperty.URL, url);
+ return this;
+ }
+
+ /**
+ * Set the driver class name that should be used when building the datasource.
+ * @param driverClassName the driver class name
+ * @return this builder
+ */
+ public DataSourceBuilder driverClassName(String driverClassName) {
+ set(DataSourceProperty.DRIVER_CLASS_NAME, driverClassName);
+ return this;
+ }
+
+ /**
+ * Set the username that should be used when building the datasource.
+ * @param username the user name
+ * @return this builder
+ */
+ public DataSourceBuilder username(String username) {
+ set(DataSourceProperty.USERNAME, username);
+ return this;
+ }
+
+ /**
+ * Set the password that should be used when building the datasource.
+ * @param password the password
+ * @return this builder
+ */
+ public DataSourceBuilder password(String password) {
+ set(DataSourceProperty.PASSWORD, password);
+ return this;
+ }
+
+ private void set(DataSourceProperty property, String value) {
+ this.values.put(property, value);
+ }
+
+ /**
+ * Return a newly built {@link DataSource} instance.
+ * @return the built datasource
+ */
+ public T build() {
+ DataSourceProperties properties = DataSourceProperties.forType(this.classLoader, this.type);
+ DataSourceProperties derriveFromProperties = (this.deriveFrom != null)
+ ? DataSourceProperties.forType(this.classLoader, this.type) : null;
+ Class extends T> instanceType = (this.type != null) ? this.type : properties.getDataSourceInstanceType();
+ T dataSource = BeanUtils.instantiateClass(instanceType);
+ Set applied = new HashSet<>();
+ for (DataSourceProperty property : DataSourceProperty.values()) {
+ if (this.values.containsKey(property)) {
+ String value = this.values.get(property);
+ properties.set(dataSource, property, value);
+ applied.add(property);
+ }
+ else if (derriveFromProperties != null && properties.canSet(property)) {
+ String value = derriveFromProperties.get(this.deriveFrom, property);
+ if (value != null) {
+ properties.set(dataSource, property, value);
+ applied.add(property);
+ }
+ }
+ }
+ if (!applied.contains(DataSourceProperty.DRIVER_CLASS_NAME)
+ && properties.canSet(DataSourceProperty.DRIVER_CLASS_NAME)
+ && this.values.containsKey(DataSourceProperty.URL)) {
+ String url = this.values.get(DataSourceProperty.URL);
+ DatabaseDriver driver = DatabaseDriver.fromJdbcUrl(url);
+ properties.set(dataSource, DataSourceProperty.DRIVER_CLASS_NAME, driver.getDriverClassName());
+ }
+ return dataSource;
+ }
+
+ /**
+ * Create a new {@link DataSourceBuilder} instance.
+ * @return a new datasource builder instance
+ */
+ public static DataSourceBuilder> create() {
+ return create(null);
+ }
+
+ /**
+ * Create a new {@link DataSourceBuilder} instance.
+ * @param classLoader the classloader used to discover preferred settings
+ * @return a new {@link DataSource} builder instance
+ */
public static DataSourceBuilder> create(ClassLoader classLoader) {
return new DataSourceBuilder<>(classLoader);
}
- private DataSourceBuilder(ClassLoader classLoader) {
- this.settingsResolver = new DataSourceSettingsResolver(classLoader);
- }
-
- @SuppressWarnings("unchecked")
- public T build() {
- Class extends DataSource> type = getType();
- DataSource result = BeanUtils.instantiateClass(type);
- maybeGetDriverClassName();
- bind(result);
- return (T) result;
- }
-
- private void maybeGetDriverClassName() {
- if (!this.properties.containsKey("driverClassName") && this.properties.containsKey("url")) {
- String url = this.properties.get("url");
- String driverClass = DatabaseDriver.fromJdbcUrl(url).getDriverClassName();
- this.properties.put("driverClassName", driverClass);
- }
- }
-
- private void bind(DataSource result) {
- ConfigurationPropertySource source = new MapConfigurationPropertySource(this.properties);
- ConfigurationPropertyNameAliases aliases = new ConfigurationPropertyNameAliases();
- this.settingsResolver.registerAliases(result, aliases);
- Binder binder = new Binder(source.withAliases(aliases));
- binder.bind(ConfigurationPropertyName.EMPTY, Bindable.ofInstance(result));
- }
-
- @SuppressWarnings("unchecked")
- public DataSourceBuilder type(Class type) {
- this.type = type;
- return (DataSourceBuilder) this;
- }
-
- public DataSourceBuilder url(String url) {
- this.properties.put("url", url);
- return this;
- }
-
- public DataSourceBuilder driverClassName(String driverClassName) {
- this.properties.put("driverClassName", driverClassName);
- return this;
- }
-
- public DataSourceBuilder username(String username) {
- this.properties.put("username", username);
- return this;
- }
-
- public DataSourceBuilder password(String password) {
- this.properties.put("password", password);
- return this;
- }
-
- public static Class extends DataSource> findType(ClassLoader classLoader) {
- DataSourceSettings preferredDataSourceSettings = new DataSourceSettingsResolver(classLoader)
- .getPreferredDataSourceSettings();
- return (preferredDataSourceSettings != null) ? preferredDataSourceSettings.getType() : null;
- }
-
- private Class extends DataSource> getType() {
- 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 aliasesCustomizer;
-
- DataSourceSettings(Class extends DataSource> type,
- Consumer 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);
+ /**
+ * Create a new {@link DataSourceBuilder} instance derived from the specified data
+ * source. The returned builder can be used to build the same type of
+ * {@link DataSource} with {@code username}, {@code password}, {@code url} and
+ * {@code driverClassName} properties copied from the original when not specifically
+ * set.
+ * @param dataSource the source {@link DataSource}
+ * @return a new {@link DataSource} builder
+ * @since 2.5.0
+ */
+ public static DataSourceBuilder> derivedFrom(DataSource dataSource) {
+ if (dataSource instanceof EmbeddedDatabase) {
+ try {
+ dataSource = dataSource.unwrap(DataSource.class);
+ }
+ catch (SQLException ex) {
+ throw new IllegalStateException("Unable to unwap embedded database", ex);
}
}
-
+ return new DataSourceBuilder<>(dataSource);
}
- private static class OracleDataSourceSettings extends DataSourceSettings {
+ /**
+ * Find the {@link DataSource} type preferred for the given classloader.
+ * @param classLoader the classloader used to discover preferred settings
+ * @return the preferred {@link DataSource} type
+ */
+ public static Class extends DataSource> findType(ClassLoader classLoader) {
+ MappedDataSourceProperties> mappings = MappedDataSourceProperties.forType(classLoader, null);
+ return (mappings != null) ? mappings.getDataSourceInstanceType() : null;
+ }
- OracleDataSourceSettings(Class extends DataSource> type) {
- super(type, (aliases) -> aliases.addAliases("username", "user"));
+ /**
+ * An individual DataSource property supported by the builder.
+ */
+ private enum DataSourceProperty {
+
+ URL("url"),
+
+ DRIVER_CLASS_NAME("driverClassName"),
+
+ USERNAME("username"),
+
+ PASSWORD("password");
+
+ private final String name;
+
+ DataSourceProperty(String name) {
+ this.name = name;
}
@Override
- public Class extends DataSource> getType() {
- return null; // Base interface
+ public String toString() {
+ return this.name;
+ }
+
+ Method findSetter(Class> type) {
+ return ReflectionUtils.findMethod(type, "set" + StringUtils.capitalize(this.name), String.class);
+ }
+
+ Method findGetter(Class> type) {
+ return ReflectionUtils.findMethod(type, "get" + StringUtils.capitalize(this.name), String.class);
}
}
- private static class DataSourceSettingsResolver {
+ private interface DataSourceProperties {
- private final DataSourceSettings preferredDataSourceSettings;
+ Class extends T> getDataSourceInstanceType();
- private final List allDataSourceSettings;
+ boolean canSet(DataSourceProperty property);
- DataSourceSettingsResolver(ClassLoader classLoader) {
- List 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.OracleDataSource", OracleDataSourceSettings::new));
- addIfAvailable(this.allDataSourceSettings, create(classLoader, "org.h2.jdbcx.JdbcDataSource",
- (type) -> new DataSourceSettings(type, (aliases) -> aliases.addAliases("username", "user"))));
- addIfAvailable(this.allDataSourceSettings, create(classLoader, "org.postgresql.ds.PGSimpleDataSource",
- (type) -> new DataSourceSettings(type, (aliases) -> aliases.addAliases("username", "user"))));
+ void set(T dataSource, DataSourceProperty property, String value);
+
+ String get(T dataSource, DataSourceProperty property);
+
+ static DataSourceProperties forType(ClassLoader classLoader, Class type) {
+ MappedDataSourceProperties mapped = MappedDataSourceProperties.forType(classLoader, type);
+ return (mapped != null) ? mapped : new ReflectionDataSourceProperties<>(type);
}
- private static List resolveAvailableDataSourceSettings(ClassLoader classLoader) {
- List 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));
- addIfAvailable(providers, create(classLoader, "oracle.ucp.jdbc.PoolDataSourceImpl", (type) -> {
- // Unfortunately Oracle UCP has an import on the Oracle driver itself
- if (ClassUtils.isPresent("oracle.jdbc.OracleConnection", classLoader)) {
- return new DataSourceSettings(type, (aliases) -> {
- aliases.addAliases("username", "user");
- aliases.addAliases("driver-class-name", "connection-factory-class-name");
- });
- }
- return null;
- }));
- return providers;
+ }
+
+ private static class MappedDataSourceProperties implements DataSourceProperties {
+
+ private final Map> mappedProperties = new HashMap<>();
+
+ private final Class dataSourceType;
+
+ @SuppressWarnings("unchecked")
+ MappedDataSourceProperties() {
+ this.dataSourceType = (Class) ResolvableType.forClass(MappedDataSourceProperties.class, getClass())
+ .resolveGeneric();
+ }
+
+ @Override
+ public Class extends T> getDataSourceInstanceType() {
+ return this.dataSourceType;
+ }
+
+ protected void add(DataSourceProperty property, Getter getter, Setter setter) {
+ add(property, String.class, getter, setter);
+ }
+
+ protected void add(DataSourceProperty property, Class type, Getter getter, Setter setter) {
+ this.mappedProperties.put(property, new MappedDataSourceProperty<>(property, type, getter, setter));
+ }
+
+ @Override
+ public boolean canSet(DataSourceProperty property) {
+ return this.mappedProperties.containsKey(property);
+ }
+
+ @Override
+ public void set(T dataSource, DataSourceProperty property, String value) {
+ MappedDataSourceProperty mappedProperty = getMapping(property);
+ mappedProperty.set(dataSource, value);
+ }
+
+ @Override
+ public String get(T dataSource, DataSourceProperty property) {
+ MappedDataSourceProperty mappedProperty = getMapping(property);
+ return mappedProperty.get(dataSource);
+ }
+
+ private MappedDataSourceProperty getMapping(DataSourceProperty property) {
+ MappedDataSourceProperty mappedProperty = this.mappedProperties.get(property);
+ UnsupportedDataSourcePropertyException.throwIf(mappedProperty == null,
+ () -> "No mapping found for " + property);
+ return mappedProperty;
+ }
+
+ static MappedDataSourceProperties forType(ClassLoader classLoader, Class type) {
+ MappedDataSourceProperties pooled = lookupPooled(classLoader, type);
+ if (type == null || pooled != null) {
+ return pooled;
+ }
+ return lookupBasic(classLoader, type);
+ }
+
+ private static MappedDataSourceProperties lookupPooled(ClassLoader classLoader,
+ Class type) {
+ MappedDataSourceProperties result = null;
+ result = lookup(classLoader, type, result, "com.zaxxer.hikari.HikariDataSource",
+ HikariDataSourceProperties::new);
+ result = lookup(classLoader, type, result, "org.apache.tomcat.jdbc.pool.DataSource",
+ TomcatPoolDataSourceProperties::new);
+ result = lookup(classLoader, type, result, "org.apache.commons.dbcp2.BasicDataSource",
+ MappedDbcp2DataSource::new);
+ result = lookup(classLoader, type, result, "oracle.ucp.jdbc.PoolDataSourceImpl",
+ OraclePoolDataSourceProperties::new, "oracle.jdbc.OracleConnection");
+ return result;
+ }
+
+ private static MappedDataSourceProperties lookupBasic(ClassLoader classLoader,
+ Class dataSourceType) {
+ MappedDataSourceProperties result = null;
+ result = lookup(classLoader, dataSourceType, result,
+ "org.springframework.jdbc.datasource.SimpleDriverDataSource",
+ () -> new SimpleDataSourceProperties());
+ result = lookup(classLoader, dataSourceType, result, "oracle.jdbc.datasource.OracleDataSource",
+ OracleDataSourceProperties::new);
+ result = lookup(classLoader, dataSourceType, result, "org.h2.jdbcx.JdbcDataSource",
+ H2DataSourceProperties::new);
+ result = lookup(classLoader, dataSourceType, result, "org.postgresql.ds.PGSimpleDataSource",
+ PostgresDataSourceProperties::new);
+ return result;
}
@SuppressWarnings("unchecked")
- private static DataSourceSettings create(ClassLoader classLoader, String target,
- Function, 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
- }
+ private static MappedDataSourceProperties lookup(ClassLoader classLoader,
+ Class dataSourceType, MappedDataSourceProperties existing, String dataSourceClassName,
+ Supplier> propertyMappingsSupplier, String... requiredClassNames) {
+ if (existing != null || !allPresent(classLoader, dataSourceClassName, requiredClassNames)) {
+ return existing;
}
- return null;
+ MappedDataSourceProperties> propertyMappings = propertyMappingsSupplier.get();
+ return (dataSourceType == null
+ || propertyMappings.getDataSourceInstanceType().isAssignableFrom(dataSourceType))
+ ? (MappedDataSourceProperties) propertyMappings : null;
}
- private static void addIfAvailable(Collection list, DataSourceSettings dataSourceSettings) {
- if (dataSourceSettings != null) {
- list.add(dataSourceSettings);
+ private static boolean allPresent(ClassLoader classLoader, String dataSourceClassName,
+ String[] requiredClassNames) {
+ boolean result = ClassUtils.isPresent(dataSourceClassName, classLoader);
+ for (String requiredClassName : requiredClassNames) {
+ result = result && ClassUtils.isPresent(requiredClassName, classLoader);
+ }
+ return result;
+ }
+
+ }
+
+ private static class MappedDataSourceProperty {
+
+ private final DataSourceProperty property;
+
+ private final Class type;
+
+ private final Getter getter;
+
+ private final Setter setter;
+
+ MappedDataSourceProperty(DataSourceProperty property, Class type, Getter getter, Setter setter) {
+ this.property = property;
+ this.type = type;
+ this.getter = getter;
+ this.setter = setter;
+ }
+
+ void set(T dataSource, String value) {
+ try {
+ UnsupportedDataSourcePropertyException.throwIf(this.setter == null,
+ () -> "No setter mapped for '" + this.property + "' property");
+ this.setter.set(dataSource, convertFromString(value));
+ }
+ catch (SQLException ex) {
+ throw new IllegalStateException(ex);
}
}
- DataSourceSettings getPreferredDataSourceSettings() {
- return this.preferredDataSourceSettings;
+ String get(T dataSource) {
+ try {
+ UnsupportedDataSourcePropertyException.throwIf(this.getter == null,
+ () -> "No getter mapped for '" + this.property + "' property");
+ return convertToString(this.getter.get(dataSource));
+ }
+ catch (SQLException ex) {
+ throw new IllegalStateException(ex);
+ }
}
- void registerAliases(DataSource result, ConfigurationPropertyNameAliases aliases) {
- this.allDataSourceSettings.forEach((settings) -> settings.registerAliases(result, aliases));
+ @SuppressWarnings("unchecked")
+ private V convertFromString(String value) {
+ if (String.class.equals(this.type)) {
+ return (V) value;
+ }
+ if (Class.class.equals(this.type)) {
+ return (V) ClassUtils.resolveClassName(value, null);
+ }
+ throw new IllegalStateException("Unsupported value type " + this.type);
+ }
+
+ private String convertToString(V value) {
+ if (String.class.equals(this.type)) {
+ return (String) value;
+ }
+ if (Class.class.equals(this.type)) {
+ return ((Class>) value).getName();
+ }
+ throw new IllegalStateException("Unsupported value type " + this.type);
+ }
+
+ }
+
+ private static class ReflectionDataSourceProperties implements DataSourceProperties {
+
+ private final Map getters;
+
+ private final Map setters;
+
+ private 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()) {
+ putIfNotNull(getters, property, property.findGetter(dataSourceType));
+ putIfNotNull(setters, property, property.findSetter(dataSourceType));
+ }
+ this.dataSourceType = dataSourceType;
+ this.getters = Collections.unmodifiableMap(getters);
+ this.setters = Collections.unmodifiableMap(setters);
+ }
+
+ private void putIfNotNull(Map map, DataSourceProperty property, Method method) {
+ if (method != null) {
+ map.put(property, method);
+ }
+ }
+
+ @Override
+ public Class getDataSourceInstanceType() {
+ return this.dataSourceType;
+ }
+
+ @Override
+ public boolean canSet(DataSourceProperty property) {
+ return this.setters.containsKey(property);
+ }
+
+ @Override
+ public void set(T dataSource, DataSourceProperty property, String value) {
+ Method method = getMethod(property, this.setters);
+ 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);
+ }
+
+ private Method getMethod(DataSourceProperty property, Map setters2) {
+ Method method = setters2.get(property);
+ UnsupportedDataSourcePropertyException.throwIf(method == null,
+ () -> "Unable to find sutable method for " + property);
+ ReflectionUtils.makeAccessible(method);
+ return method;
+ }
+
+ }
+
+ @FunctionalInterface
+ private interface Getter {
+
+ V get(T instance) throws SQLException;
+
+ }
+
+ @FunctionalInterface
+ private interface Setter {
+
+ void set(T instance, V value) throws SQLException;
+
+ }
+
+ /**
+ * {@link MappedDataSource} for Hikari.
+ */
+ private static class HikariDataSourceProperties extends MappedDataSourceProperties {
+
+ HikariDataSourceProperties() {
+ add(DataSourceProperty.URL, HikariDataSource::getJdbcUrl, HikariDataSource::setJdbcUrl);
+ add(DataSourceProperty.DRIVER_CLASS_NAME, HikariDataSource::getDriverClassName,
+ HikariDataSource::setDriverClassName);
+ add(DataSourceProperty.USERNAME, HikariDataSource::getUsername, HikariDataSource::setUsername);
+ add(DataSourceProperty.PASSWORD, HikariDataSource::getPassword, HikariDataSource::setPassword);
+ }
+
+ }
+
+ /**
+ * {@link MappedDataSource} for Tomcat Pool.
+ */
+ private static class TomcatPoolDataSourceProperties
+ extends MappedDataSourceProperties {
+
+ TomcatPoolDataSourceProperties() {
+ add(DataSourceProperty.URL, org.apache.tomcat.jdbc.pool.DataSource::getUrl,
+ org.apache.tomcat.jdbc.pool.DataSource::setUrl);
+ add(DataSourceProperty.DRIVER_CLASS_NAME, org.apache.tomcat.jdbc.pool.DataSource::getDriverClassName,
+ org.apache.tomcat.jdbc.pool.DataSource::setDriverClassName);
+ add(DataSourceProperty.USERNAME, org.apache.tomcat.jdbc.pool.DataSource::getUsername,
+ org.apache.tomcat.jdbc.pool.DataSource::setUsername);
+ add(DataSourceProperty.PASSWORD, org.apache.tomcat.jdbc.pool.DataSource::getPassword,
+ org.apache.tomcat.jdbc.pool.DataSource::setPassword);
+ }
+
+ }
+
+ /**
+ * {@link MappedDataSource} for DBCP2.
+ */
+ private static class MappedDbcp2DataSource extends MappedDataSourceProperties {
+
+ MappedDbcp2DataSource() {
+ add(DataSourceProperty.URL, BasicDataSource::getUrl, BasicDataSource::setUrl);
+ add(DataSourceProperty.DRIVER_CLASS_NAME, BasicDataSource::getDriverClassName,
+ BasicDataSource::setDriverClassName);
+ add(DataSourceProperty.USERNAME, BasicDataSource::getUsername, BasicDataSource::setUsername);
+ add(DataSourceProperty.PASSWORD, BasicDataSource::getPassword, BasicDataSource::setPassword);
+ }
+
+ }
+
+ /**
+ * {@link MappedDataSource} for Oracle Pool.
+ */
+ private static class OraclePoolDataSourceProperties extends MappedDataSourceProperties {
+
+ @Override
+ public Class extends PoolDataSource> getDataSourceInstanceType() {
+ return PoolDataSourceImpl.class;
+ }
+
+ OraclePoolDataSourceProperties() {
+ add(DataSourceProperty.URL, PoolDataSource::getURL, PoolDataSource::setURL);
+ add(DataSourceProperty.DRIVER_CLASS_NAME, PoolDataSource::getConnectionFactoryClassName,
+ PoolDataSource::setConnectionFactoryClassName);
+ add(DataSourceProperty.USERNAME, PoolDataSource::getUser, PoolDataSource::setUser);
+ add(DataSourceProperty.PASSWORD, PoolDataSource::getPassword, PoolDataSource::setPassword);
+ }
+
+ }
+
+ /**
+ * {@link MappedDataSource} for Spring's {@link SimpleDriverDataSource}.
+ */
+ private static class SimpleDataSourceProperties extends MappedDataSourceProperties {
+
+ @SuppressWarnings("unchecked")
+ SimpleDataSourceProperties() {
+ add(DataSourceProperty.URL, SimpleDriverDataSource::getUrl, SimpleDriverDataSource::setUrl);
+ add(DataSourceProperty.DRIVER_CLASS_NAME, Class.class, (dataSource) -> dataSource.getDriver().getClass(),
+ (dataSource, driverClass) -> dataSource.setDriverClass(driverClass));
+ add(DataSourceProperty.USERNAME, SimpleDriverDataSource::getUsername, SimpleDriverDataSource::setUsername);
+ add(DataSourceProperty.PASSWORD, SimpleDriverDataSource::getPassword, SimpleDriverDataSource::setPassword);
+ }
+
+ }
+
+ /**
+ * {@link MappedDataSource} for Oracle.
+ */
+ private static class OracleDataSourceProperties extends MappedDataSourceProperties {
+
+ OracleDataSourceProperties() {
+ add(DataSourceProperty.URL, OracleDataSource::getURL, OracleDataSource::setURL);
+ add(DataSourceProperty.USERNAME, OracleDataSource::getUser, OracleDataSource::setUser);
+ add(DataSourceProperty.PASSWORD, null, OracleDataSource::setPassword);
+ }
+
+ }
+
+ /**
+ * {@link MappedDataSource} for H2.
+ */
+ private static class H2DataSourceProperties extends MappedDataSourceProperties {
+
+ H2DataSourceProperties() {
+ add(DataSourceProperty.URL, JdbcDataSource::getUrl, JdbcDataSource::setUrl);
+ add(DataSourceProperty.USERNAME, JdbcDataSource::getUser, JdbcDataSource::setUser);
+ add(DataSourceProperty.PASSWORD, JdbcDataSource::getPassword, JdbcDataSource::setPassword);
+ }
+
+ }
+
+ /**
+ * {@link MappedDataSource} for Postgres.
+ */
+ private static class PostgresDataSourceProperties extends MappedDataSourceProperties {
+
+ PostgresDataSourceProperties() {
+ add(DataSourceProperty.URL, PGSimpleDataSource::getUrl, PGSimpleDataSource::setUrl);
+ add(DataSourceProperty.USERNAME, PGSimpleDataSource::getUser, PGSimpleDataSource::setUser);
+ add(DataSourceProperty.PASSWORD, PGSimpleDataSource::getPassword, PGSimpleDataSource::setPassword);
}
}
diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/jdbc/UnsupportedDataSourcePropertyException.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/jdbc/UnsupportedDataSourcePropertyException.java
new file mode 100644
index 0000000000..561dea3fbd
--- /dev/null
+++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/jdbc/UnsupportedDataSourcePropertyException.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright 2012-2021 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.boot.jdbc;
+
+import java.util.function.Supplier;
+
+/**
+ * {@link RuntimeException} thrown from {@link DataSourceBuilder} when an unsupported
+ * property is used.
+ *
+ * @author Phillip Webb
+ * @since 2.5.0
+ */
+public class UnsupportedDataSourcePropertyException extends RuntimeException {
+
+ UnsupportedDataSourcePropertyException(String message) {
+ super(message);
+ }
+
+ static void throwIf(boolean test, Supplier message) {
+ if (test) {
+ throw new UnsupportedDataSourcePropertyException(message.get());
+ }
+ }
+
+}
diff --git a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/jdbc/DataSourceBuilderNoHikariTests.java b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/jdbc/DataSourceBuilderNoHikariTests.java
new file mode 100644
index 0000000000..8e6606ff6e
--- /dev/null
+++ b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/jdbc/DataSourceBuilderNoHikariTests.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright 2012-2021 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.boot.jdbc;
+
+import javax.sql.DataSource;
+
+import org.junit.jupiter.api.Test;
+
+import org.springframework.boot.testsupport.classpath.ClassPathExclusions;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+/**
+ * Tests for {@link DataSourceBuilder} when Hikari is not on the classpath.
+ *
+ * @author Phillip Webb
+ */
+@ClassPathExclusions("Hikari*.jar")
+class DataSourceBuilderNoHikariTests {
+
+ @Test
+ void findTypeReturnsTomcatDataSource() {
+ assertThat(DataSourceBuilder.findType(null)).isEqualTo(org.apache.tomcat.jdbc.pool.DataSource.class);
+ }
+
+ @Test
+ void createAndBuildReturnsTomcatDataSource() {
+ DataSource dataSource = DataSourceBuilder.create().build();
+ assertThat(dataSource).isInstanceOf(org.apache.tomcat.jdbc.pool.DataSource.class);
+ }
+
+}
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 974bced9bc..96a20801b2 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
@@ -20,12 +20,14 @@ import java.io.Closeable;
import java.io.IOException;
import java.net.URL;
import java.net.URLClassLoader;
+import java.sql.Connection;
import java.sql.SQLException;
import java.util.Arrays;
import javax.sql.DataSource;
import com.zaxxer.hikari.HikariDataSource;
+import oracle.jdbc.internal.OpaqueString;
import oracle.jdbc.pool.OracleDataSource;
import oracle.ucp.jdbc.PoolDataSourceImpl;
import org.apache.commons.dbcp2.BasicDataSource;
@@ -35,15 +37,21 @@ import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Test;
import org.postgresql.ds.PGSimpleDataSource;
+import org.springframework.jdbc.datasource.AbstractDataSource;
import org.springframework.jdbc.datasource.SimpleDriverDataSource;
+import org.springframework.jdbc.datasource.embedded.EmbeddedDatabase;
+import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseBuilder;
+import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseType;
import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
/**
* Tests for {@link DataSourceBuilder}.
*
* @author Stephane Nicoll
* @author Fabio Grassi
+ * @author Phillip Webb
*/
class DataSourceBuilderTests {
@@ -57,7 +65,7 @@ class DataSourceBuilderTests {
}
@Test
- void defaultToHikari() {
+ void buildWhenHikariAvailableReturnsHikariDataSource() {
this.dataSource = DataSourceBuilder.create().url("jdbc:h2:test").build();
assertThat(this.dataSource).isInstanceOf(HikariDataSource.class);
HikariDataSource hikariDataSource = (HikariDataSource) this.dataSource;
@@ -65,14 +73,14 @@ class DataSourceBuilderTests {
}
@Test
- void defaultToTomcatIfHikariIsNotAvailable() {
+ void buildWhenHikariNotAvailableReturnsTomcatDataSource() {
this.dataSource = DataSourceBuilder.create(new HidePackagesClassLoader("com.zaxxer.hikari")).url("jdbc:h2:test")
.build();
assertThat(this.dataSource).isInstanceOf(org.apache.tomcat.jdbc.pool.DataSource.class);
}
@Test
- void defaultToCommonsDbcp2IfNeitherHikariNorTomcatIsNotAvailable() {
+ void buildWhenHikariAndTomcatNotAvailableReturnsDbcp2DataSource() {
this.dataSource = DataSourceBuilder
.create(new HidePackagesClassLoader("com.zaxxer.hikari", "org.apache.tomcat.jdbc.pool"))
.url("jdbc:h2:test").build();
@@ -80,20 +88,20 @@ class DataSourceBuilderTests {
}
@Test
- void defaultToOracleUcpAsLastResort() {
+ void buildWhenHikariAndTomcatAndDbcpNotAvailableReturnsOracleUcpDataSource() {
this.dataSource = DataSourceBuilder.create(new HidePackagesClassLoader("com.zaxxer.hikari",
"org.apache.tomcat.jdbc.pool", "org.apache.commons.dbcp2")).url("jdbc:h2:test").build();
assertThat(this.dataSource).isInstanceOf(PoolDataSourceImpl.class);
}
@Test
- void specificTypeOfDataSource() {
+ void buildWhenHikariTypeSpecifiedReturnsExpectedDataSource() {
HikariDataSource hikariDataSource = DataSourceBuilder.create().type(HikariDataSource.class).build();
assertThat(hikariDataSource).isInstanceOf(HikariDataSource.class);
}
@Test
- void dataSourceCanBeCreatedWithSimpleDriverDataSource() {
+ void buildWhenSimpleDriverTypeSpecifiedReturnsExpectedDataSource() {
this.dataSource = DataSourceBuilder.create().url("jdbc:h2:test").type(SimpleDriverDataSource.class).build();
assertThat(this.dataSource).isInstanceOf(SimpleDriverDataSource.class);
SimpleDriverDataSource simpleDriverDataSource = (SimpleDriverDataSource) this.dataSource;
@@ -102,7 +110,7 @@ class DataSourceBuilderTests {
}
@Test
- void dataSourceCanBeCreatedWithOracleDataSource() throws SQLException {
+ void buildWhenOracleTypeSpecifiedReturnsExpectedDataSource() 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);
@@ -112,7 +120,7 @@ class DataSourceBuilderTests {
}
@Test
- void dataSourceCanBeCreatedWithOracleUcpDataSource() {
+ void buildWhenOracleUcpTypeSpecifiedReturnsExpectedDataSource() {
this.dataSource = DataSourceBuilder.create().driverClassName("org.hsqldb.jdbc.JDBCDriver")
.type(PoolDataSourceImpl.class).username("test").build();
assertThat(this.dataSource).isInstanceOf(PoolDataSourceImpl.class);
@@ -122,7 +130,7 @@ class DataSourceBuilderTests {
}
@Test
- void dataSourceCanBeCreatedWithH2JdbcDataSource() {
+ void buildWhenH2TypeSpecifiedReturnsExpectedDataSource() {
this.dataSource = DataSourceBuilder.create().url("jdbc:h2:test").type(JdbcDataSource.class).username("test")
.build();
assertThat(this.dataSource).isInstanceOf(JdbcDataSource.class);
@@ -131,7 +139,7 @@ class DataSourceBuilderTests {
}
@Test
- void dataSourceCanBeCreatedWithPGDataSource() {
+ void buildWhenPostgressTypeSpecifiedReturnsExpectedDataSource() {
this.dataSource = DataSourceBuilder.create().url("jdbc:postgresql://localhost/test")
.type(PGSimpleDataSource.class).username("test").build();
assertThat(this.dataSource).isInstanceOf(PGSimpleDataSource.class);
@@ -140,11 +148,17 @@ class DataSourceBuilderTests {
}
@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;
+ void buildWhenMappedTypeSpecifiedAndNoSuitableMappingThrowsException() {
+ assertThatExceptionOfType(UnsupportedDataSourcePropertyException.class).isThrownBy(
+ () -> DataSourceBuilder.create().type(OracleDataSource.class).driverClassName("com.example").build());
+ }
+
+ @Test
+ void buildWhenCustomSubclassTypeSpecifiedReturnsDataSourceWithOnlyBasePropertiesSet() {
+ this.dataSource = DataSourceBuilder.create().url("jdbc:h2:test").type(CustomTomcatDataSource.class)
+ .username("test").build();
+ assertThat(this.dataSource).isInstanceOf(CustomTomcatDataSource.class);
+ CustomTomcatDataSource testDataSource = (CustomTomcatDataSource) this.dataSource;
assertThat(testDataSource.getUrl()).isEqualTo("jdbc:h2:test");
assertThat(testDataSource.getJdbcUrl()).isNull();
assertThat(testDataSource.getUsername()).isEqualTo("test");
@@ -153,6 +167,85 @@ class DataSourceBuilderTests {
assertThat(testDataSource.getDriverClass()).isNull();
}
+ @Test
+ void buildWhenCustomTypeSpecifiedReturnsDataSourceWithPropertiesSetViaReflection() {
+ this.dataSource = DataSourceBuilder.create().type(CustomDataSource.class).username("test").password("secret")
+ .url("jdbc:h2:test").driverClassName("com.example").build();
+ assertThat(this.dataSource).isInstanceOf(CustomDataSource.class);
+ CustomDataSource testDataSource = (CustomDataSource) this.dataSource;
+ assertThat(testDataSource.getUrl()).isEqualTo("jdbc:h2:test");
+ assertThat(testDataSource.getUsername()).isEqualTo("test");
+ assertThat(testDataSource.getPassword()).isEqualTo("secret");
+ assertThat(testDataSource.getDriverClassName()).isEqualTo("com.example");
+ }
+
+ @Test
+ void buildWhenCustomTypeSpecifiedAndNoSuitableSetterThrowsException() {
+ assertThatExceptionOfType(UnsupportedDataSourcePropertyException.class).isThrownBy(() -> DataSourceBuilder
+ .create().type(LimitedCustomDataSource.class).driverClassName("com.example").build());
+ }
+
+ @Test
+ void buildWhenDerivedWithNewUrlReturnsNewDataSource() {
+ HikariDataSource dataSource = new HikariDataSource();
+ dataSource.setUsername("test");
+ dataSource.setPassword("secret");
+ dataSource.setJdbcUrl("jdbc:h2:test");
+ HikariDataSource built = (HikariDataSource) DataSourceBuilder.derivedFrom(dataSource).url("jdbc:h2:test2")
+ .build();
+ assertThat(built.getUsername()).isEqualTo("test");
+ assertThat(built.getPassword()).isEqualTo("secret");
+ assertThat(built.getJdbcUrl()).isEqualTo("jdbc:h2:test2");
+ }
+
+ @Test
+ void buildWhenDerivedWithNewUsernameAndPasswordReturnsNewDataSource() {
+ HikariDataSource dataSource = new HikariDataSource();
+ dataSource.setUsername("test");
+ dataSource.setPassword("secret");
+ dataSource.setJdbcUrl("jdbc:h2:test");
+ DataSourceBuilder> builder = DataSourceBuilder.derivedFrom(dataSource);
+ HikariDataSource built = (HikariDataSource) builder.username("test2").password("secret2").build();
+ assertThat(built.getUsername()).isEqualTo("test2");
+ assertThat(built.getPassword()).isEqualTo("secret2");
+ assertThat(built.getJdbcUrl()).isEqualTo("jdbc:h2:test");
+ }
+
+ @Test
+ void buildWhenDerivedFromOracleDataSourceWithPasswordNotSetThrowsException() throws Exception {
+ oracle.jdbc.datasource.impl.OracleDataSource dataSource = new oracle.jdbc.datasource.impl.OracleDataSource();
+ dataSource.setUser("test");
+ dataSource.setPassword("secret");
+ dataSource.setURL("example.com");
+ assertThatExceptionOfType(UnsupportedDataSourcePropertyException.class)
+ .isThrownBy(() -> DataSourceBuilder.derivedFrom(dataSource).url("example.org").build());
+ }
+
+ @Test
+ void buildWhenDerivedFromOracleDataSourceWithPasswordSetReturnsDataSource() throws Exception {
+ oracle.jdbc.datasource.impl.OracleDataSource dataSource = new oracle.jdbc.datasource.impl.OracleDataSource();
+ dataSource.setUser("test");
+ dataSource.setPassword("secret");
+ dataSource.setURL("example.com");
+ DataSourceBuilder> builder = DataSourceBuilder.derivedFrom(dataSource);
+ oracle.jdbc.datasource.impl.OracleDataSource built = (oracle.jdbc.datasource.impl.OracleDataSource) builder
+ .username("test2").password("secret2").build();
+ assertThat(built.getUser()).isEqualTo("test2");
+ assertThat(built).extracting("password").extracting((opaque) -> ((OpaqueString) opaque).get())
+ .isEqualTo("secret2");
+ assertThat(built.getURL()).isEqualTo("example.com");
+ }
+
+ @Test
+ void buildWhenDerivedFromEmbeddedDatabase() {
+ EmbeddedDatabase database = new EmbeddedDatabaseBuilder().setType(EmbeddedDatabaseType.HSQL).build();
+ SimpleDriverDataSource built = (SimpleDriverDataSource) DataSourceBuilder.derivedFrom(database).username("test")
+ .password("secret").build();
+ assertThat(built.getUsername()).isEqualTo("test");
+ assertThat(built.getPassword()).isEqualTo("secret");
+ assertThat(built.getUrl()).startsWith("jdbc:hsqldb:mem");
+ }
+
final class HidePackagesClassLoader extends URLClassLoader {
private final String[] hiddenPackages;
@@ -172,7 +265,7 @@ class DataSourceBuilderTests {
}
- public static class TestDataSource extends org.apache.tomcat.jdbc.pool.DataSource {
+ static class CustomTomcatDataSource extends org.apache.tomcat.jdbc.pool.DataSource {
private String jdbcUrl;
@@ -180,30 +273,88 @@ class DataSourceBuilderTests {
private String driverClass;
- public String getJdbcUrl() {
+ String getJdbcUrl() {
return this.jdbcUrl;
}
- public void setJdbcUrl(String jdbcUrl) {
+ void setJdbcUrl(String jdbcUrl) {
this.jdbcUrl = jdbcUrl;
}
- public String getUser() {
+ String getUser() {
return this.user;
}
- public void setUser(String user) {
+ void setUser(String user) {
this.user = user;
}
- public String getDriverClass() {
+ String getDriverClass() {
return this.driverClass;
}
- public void setDriverClass(String driverClass) {
+ void setDriverClass(String driverClass) {
this.driverClass = driverClass;
}
}
+ static class LimitedCustomDataSource extends AbstractDataSource {
+
+ private String username;
+
+ private String password;
+
+ private String url;
+
+ @Override
+ public Connection getConnection() throws SQLException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public Connection getConnection(String username, String password) throws SQLException {
+ throw new UnsupportedOperationException();
+ }
+
+ String getUsername() {
+ return this.username;
+ }
+
+ void setUsername(String username) {
+ this.username = username;
+ }
+
+ String getPassword() {
+ return this.password;
+ }
+
+ void setPassword(String password) {
+ this.password = password;
+ }
+
+ String getUrl() {
+ return this.url;
+ }
+
+ void setUrl(String url) {
+ this.url = url;
+ }
+
+ }
+
+ static class CustomDataSource extends LimitedCustomDataSource {
+
+ private String driverClassName;
+
+ String getDriverClassName() {
+ return this.driverClassName;
+ }
+
+ void setDriverClassName(String driverClassName) {
+ this.driverClassName = driverClassName;
+ }
+
+ }
+
}