Promote ConnectionFactoryBuilder to the "spring-boot" module

Closes gh-25492
pull/25377/head
Stephane Nicoll 4 years ago committed by Andy Wilkinson
parent 9ba2cd3be7
commit 54e7d7f060

@ -1,5 +1,5 @@
/* /*
* Copyright 2012-2020 the original author or authors. * Copyright 2012-2021 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -16,9 +16,10 @@
package org.springframework.boot.autoconfigure.r2dbc; package org.springframework.boot.autoconfigure.r2dbc;
import org.springframework.boot.autoconfigure.r2dbc.ConnectionFactoryBuilder.ConnectionFactoryBeanCreationException; import org.springframework.boot.autoconfigure.r2dbc.ConnectionFactoryOptionsInitializer.ConnectionFactoryBeanCreationException;
import org.springframework.boot.diagnostics.AbstractFailureAnalyzer; import org.springframework.boot.diagnostics.AbstractFailureAnalyzer;
import org.springframework.boot.diagnostics.FailureAnalysis; import org.springframework.boot.diagnostics.FailureAnalysis;
import org.springframework.boot.r2dbc.EmbeddedDatabaseConnection;
import org.springframework.context.EnvironmentAware; import org.springframework.context.EnvironmentAware;
import org.springframework.core.env.Environment; import org.springframework.core.env.Environment;
import org.springframework.util.ObjectUtils; import org.springframework.util.ObjectUtils;

@ -1,5 +1,5 @@
/* /*
* Copyright 2012-2020 the original author or authors. * Copyright 2012-2021 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -17,17 +17,12 @@
package org.springframework.boot.autoconfigure.r2dbc; package org.springframework.boot.autoconfigure.r2dbc;
import java.util.function.Consumer; import java.util.function.Consumer;
import java.util.function.Predicate;
import java.util.function.Supplier; import java.util.function.Supplier;
import io.r2dbc.spi.ConnectionFactories; import io.r2dbc.spi.ConnectionFactories;
import io.r2dbc.spi.ConnectionFactory; import io.r2dbc.spi.ConnectionFactory;
import io.r2dbc.spi.ConnectionFactoryOptions; import io.r2dbc.spi.ConnectionFactoryOptions;
import io.r2dbc.spi.ConnectionFactoryOptions.Builder; import io.r2dbc.spi.ConnectionFactoryOptions.Builder;
import io.r2dbc.spi.Option;
import org.springframework.beans.factory.BeanCreationException;
import org.springframework.util.StringUtils;
/** /**
* Builder for {@link ConnectionFactory}. * Builder for {@link ConnectionFactory}.
@ -36,7 +31,10 @@ import org.springframework.util.StringUtils;
* @author Tadaya Tsuyukubo * @author Tadaya Tsuyukubo
* @author Stephane Nicoll * @author Stephane Nicoll
* @since 2.3.0 * @since 2.3.0
* @deprecated since 2.5.0 in favor of
* {@link org.springframework.boot.r2dbc.ConnectionFactoryBuilder}
*/ */
@Deprecated
public final class ConnectionFactoryBuilder { public final class ConnectionFactoryBuilder {
private final ConnectionFactoryOptions.Builder optionsBuilder; private final ConnectionFactoryOptions.Builder optionsBuilder;
@ -59,7 +57,7 @@ public final class ConnectionFactoryBuilder {
public static ConnectionFactoryBuilder of(R2dbcProperties properties, public static ConnectionFactoryBuilder of(R2dbcProperties properties,
Supplier<EmbeddedDatabaseConnection> embeddedDatabaseConnection) { Supplier<EmbeddedDatabaseConnection> embeddedDatabaseConnection) {
return new ConnectionFactoryBuilder( return new ConnectionFactoryBuilder(
new ConnectionFactoryOptionsInitializer().initializeOptions(properties, embeddedDatabaseConnection)); new ConnectionFactoryOptionsInitializer().initialize(properties, adapt(embeddedDatabaseConnection)));
} }
/** /**
@ -133,127 +131,13 @@ public final class ConnectionFactoryBuilder {
return this.optionsBuilder.build(); return this.optionsBuilder.build();
} }
static class ConnectionFactoryOptionsInitializer { private static Supplier<org.springframework.boot.r2dbc.EmbeddedDatabaseConnection> adapt(
Supplier<EmbeddedDatabaseConnection> embeddedDatabaseConnection) {
/** return () -> {
* Initialize a {@link io.r2dbc.spi.ConnectionFactoryOptions.Builder EmbeddedDatabaseConnection connection = embeddedDatabaseConnection.get();
* ConnectionFactoryOptions.Builder} using the specified properties. return (connection != null)
* @param properties the properties to use to initialize the builder ? org.springframework.boot.r2dbc.EmbeddedDatabaseConnection.valueOf(connection.name()) : null;
* @param embeddedDatabaseConnection the embedded connection to use as a fallback };
* @return an initialized builder
* @throws ConnectionFactoryBeanCreationException if no suitable connection could
* be determined
*/
ConnectionFactoryOptions.Builder initializeOptions(R2dbcProperties properties,
Supplier<EmbeddedDatabaseConnection> embeddedDatabaseConnection) {
if (StringUtils.hasText(properties.getUrl())) {
return initializeRegularOptions(properties);
}
EmbeddedDatabaseConnection embeddedConnection = embeddedDatabaseConnection.get();
if (embeddedConnection != EmbeddedDatabaseConnection.NONE) {
return initializeEmbeddedOptions(properties, embeddedConnection);
}
throw connectionFactoryBeanCreationException("Failed to determine a suitable R2DBC Connection URL",
properties, embeddedConnection);
}
private ConnectionFactoryOptions.Builder initializeRegularOptions(R2dbcProperties properties) {
ConnectionFactoryOptions urlOptions = ConnectionFactoryOptions.parse(properties.getUrl());
Builder optionsBuilder = urlOptions.mutate();
configureIf(optionsBuilder, urlOptions, ConnectionFactoryOptions.USER, properties::getUsername,
StringUtils::hasText);
configureIf(optionsBuilder, urlOptions, ConnectionFactoryOptions.PASSWORD, properties::getPassword,
StringUtils::hasText);
configureIf(optionsBuilder, urlOptions, ConnectionFactoryOptions.DATABASE,
() -> determineDatabaseName(properties), StringUtils::hasText);
if (properties.getProperties() != null) {
properties.getProperties().forEach((key, value) -> optionsBuilder.option(Option.valueOf(key), value));
}
return optionsBuilder;
}
private ConnectionFactoryOptions.Builder initializeEmbeddedOptions(R2dbcProperties properties,
EmbeddedDatabaseConnection embeddedDatabaseConnection) {
String url = embeddedDatabaseConnection.getUrl(determineEmbeddedDatabaseName(properties));
if (url == null) {
throw connectionFactoryBeanCreationException("Failed to determine a suitable R2DBC Connection URL",
properties, embeddedDatabaseConnection);
}
Builder builder = ConnectionFactoryOptions.parse(url).mutate();
String username = determineEmbeddedUsername(properties);
if (StringUtils.hasText(username)) {
builder.option(ConnectionFactoryOptions.USER, username);
}
if (StringUtils.hasText(properties.getPassword())) {
builder.option(ConnectionFactoryOptions.PASSWORD, properties.getPassword());
}
return builder;
}
private String determineDatabaseName(R2dbcProperties properties) {
if (properties.isGenerateUniqueName()) {
return properties.determineUniqueName();
}
if (StringUtils.hasLength(properties.getName())) {
return properties.getName();
}
return null;
}
private String determineEmbeddedDatabaseName(R2dbcProperties properties) {
String databaseName = determineDatabaseName(properties);
return (databaseName != null) ? databaseName : "testdb";
}
private String determineEmbeddedUsername(R2dbcProperties properties) {
String username = ifHasText(properties.getUsername());
return (username != null) ? username : "sa";
}
private <T extends CharSequence> void configureIf(Builder optionsBuilder,
ConnectionFactoryOptions originalOptions, Option<T> option, Supplier<T> valueSupplier,
Predicate<T> setIf) {
if (originalOptions.hasOption(option)) {
return;
}
T value = valueSupplier.get();
if (setIf.test(value)) {
optionsBuilder.option(option, value);
}
}
private ConnectionFactoryBeanCreationException connectionFactoryBeanCreationException(String message,
R2dbcProperties properties, EmbeddedDatabaseConnection embeddedDatabaseConnection) {
return new ConnectionFactoryBeanCreationException(message, properties, embeddedDatabaseConnection);
}
private String ifHasText(String candidate) {
return (StringUtils.hasText(candidate)) ? candidate : null;
}
}
static class ConnectionFactoryBeanCreationException extends BeanCreationException {
private final R2dbcProperties properties;
private final EmbeddedDatabaseConnection embeddedDatabaseConnection;
ConnectionFactoryBeanCreationException(String message, R2dbcProperties properties,
EmbeddedDatabaseConnection embeddedDatabaseConnection) {
super(message);
this.properties = properties;
this.embeddedDatabaseConnection = embeddedDatabaseConnection;
}
EmbeddedDatabaseConnection getEmbeddedDatabaseConnection() {
return this.embeddedDatabaseConnection;
}
R2dbcProperties getProperties() {
return this.properties;
}
} }
} }

@ -1,5 +1,5 @@
/* /*
* Copyright 2012-2020 the original author or authors. * Copyright 2012-2021 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -30,6 +30,7 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.autoconfigure.condition.SpringBootCondition; import org.springframework.boot.autoconfigure.condition.SpringBootCondition;
import org.springframework.boot.context.properties.PropertyMapper; import org.springframework.boot.context.properties.PropertyMapper;
import org.springframework.boot.r2dbc.EmbeddedDatabaseConnection;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Condition; import org.springframework.context.annotation.Condition;
import org.springframework.context.annotation.ConditionContext; import org.springframework.context.annotation.ConditionContext;
@ -50,7 +51,9 @@ abstract class ConnectionFactoryConfigurations {
protected static ConnectionFactory createConnectionFactory(R2dbcProperties properties, ClassLoader classLoader, protected static ConnectionFactory createConnectionFactory(R2dbcProperties properties, ClassLoader classLoader,
List<ConnectionFactoryOptionsBuilderCustomizer> optionsCustomizers) { List<ConnectionFactoryOptionsBuilderCustomizer> optionsCustomizers) {
return ConnectionFactoryBuilder.of(properties, () -> EmbeddedDatabaseConnection.get(classLoader)) return org.springframework.boot.r2dbc.ConnectionFactoryBuilder
.withOptions(new ConnectionFactoryOptionsInitializer().initialize(properties,
() -> EmbeddedDatabaseConnection.get(classLoader)))
.configure((options) -> { .configure((options) -> {
for (ConnectionFactoryOptionsBuilderCustomizer optionsCustomizer : optionsCustomizers) { for (ConnectionFactoryOptionsBuilderCustomizer optionsCustomizer : optionsCustomizers) {
optionsCustomizer.customize(options); optionsCustomizer.customize(options);

@ -0,0 +1,155 @@
/*
* 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.autoconfigure.r2dbc;
import java.util.function.Predicate;
import java.util.function.Supplier;
import io.r2dbc.spi.ConnectionFactoryOptions;
import io.r2dbc.spi.ConnectionFactoryOptions.Builder;
import io.r2dbc.spi.Option;
import org.springframework.beans.factory.BeanCreationException;
import org.springframework.boot.r2dbc.EmbeddedDatabaseConnection;
import org.springframework.util.StringUtils;
/**
* Initialize a {@link ConnectionFactoryOptions.Builder} based on {@link R2dbcProperties}.
*
* @author Stephane Nicoll
*/
class ConnectionFactoryOptionsInitializer {
/**
* Initialize a {@link io.r2dbc.spi.ConnectionFactoryOptions.Builder
* ConnectionFactoryOptions.Builder} using the specified properties.
* @param properties the properties to use to initialize the builder
* @param embeddedDatabaseConnection the embedded connection to use as a fallback
* @return an initialized builder
* @throws ConnectionFactoryBeanCreationException if no suitable connection could be
* determined
*/
ConnectionFactoryOptions.Builder initialize(R2dbcProperties properties,
Supplier<EmbeddedDatabaseConnection> embeddedDatabaseConnection) {
if (StringUtils.hasText(properties.getUrl())) {
return initializeRegularOptions(properties);
}
EmbeddedDatabaseConnection embeddedConnection = embeddedDatabaseConnection.get();
if (embeddedConnection != EmbeddedDatabaseConnection.NONE) {
return initializeEmbeddedOptions(properties, embeddedConnection);
}
throw connectionFactoryBeanCreationException("Failed to determine a suitable R2DBC Connection URL", properties,
embeddedConnection);
}
private ConnectionFactoryOptions.Builder initializeRegularOptions(R2dbcProperties properties) {
ConnectionFactoryOptions urlOptions = ConnectionFactoryOptions.parse(properties.getUrl());
Builder optionsBuilder = urlOptions.mutate();
configureIf(optionsBuilder, urlOptions, ConnectionFactoryOptions.USER, properties::getUsername,
StringUtils::hasText);
configureIf(optionsBuilder, urlOptions, ConnectionFactoryOptions.PASSWORD, properties::getPassword,
StringUtils::hasText);
configureIf(optionsBuilder, urlOptions, ConnectionFactoryOptions.DATABASE,
() -> determineDatabaseName(properties), StringUtils::hasText);
if (properties.getProperties() != null) {
properties.getProperties().forEach((key, value) -> optionsBuilder.option(Option.valueOf(key), value));
}
return optionsBuilder;
}
private Builder initializeEmbeddedOptions(R2dbcProperties properties,
EmbeddedDatabaseConnection embeddedDatabaseConnection) {
String url = embeddedDatabaseConnection.getUrl(determineEmbeddedDatabaseName(properties));
if (url == null) {
throw connectionFactoryBeanCreationException("Failed to determine a suitable R2DBC Connection URL",
properties, embeddedDatabaseConnection);
}
Builder builder = ConnectionFactoryOptions.parse(url).mutate();
String username = determineEmbeddedUsername(properties);
if (StringUtils.hasText(username)) {
builder.option(ConnectionFactoryOptions.USER, username);
}
if (StringUtils.hasText(properties.getPassword())) {
builder.option(ConnectionFactoryOptions.PASSWORD, properties.getPassword());
}
return builder;
}
private String determineDatabaseName(R2dbcProperties properties) {
if (properties.isGenerateUniqueName()) {
return properties.determineUniqueName();
}
if (StringUtils.hasLength(properties.getName())) {
return properties.getName();
}
return null;
}
private String determineEmbeddedDatabaseName(R2dbcProperties properties) {
String databaseName = determineDatabaseName(properties);
return (databaseName != null) ? databaseName : "testdb";
}
private String determineEmbeddedUsername(R2dbcProperties properties) {
String username = ifHasText(properties.getUsername());
return (username != null) ? username : "sa";
}
private <T extends CharSequence> void configureIf(Builder optionsBuilder, ConnectionFactoryOptions originalOptions,
Option<T> option, Supplier<T> valueSupplier, Predicate<T> setIf) {
if (originalOptions.hasOption(option)) {
return;
}
T value = valueSupplier.get();
if (setIf.test(value)) {
optionsBuilder.option(option, value);
}
}
private ConnectionFactoryBeanCreationException connectionFactoryBeanCreationException(String message,
R2dbcProperties properties, EmbeddedDatabaseConnection embeddedDatabaseConnection) {
return new ConnectionFactoryBeanCreationException(message, properties, embeddedDatabaseConnection);
}
private String ifHasText(String candidate) {
return (StringUtils.hasText(candidate)) ? candidate : null;
}
static class ConnectionFactoryBeanCreationException extends BeanCreationException {
private final R2dbcProperties properties;
private final EmbeddedDatabaseConnection embeddedDatabaseConnection;
ConnectionFactoryBeanCreationException(String message, R2dbcProperties properties,
EmbeddedDatabaseConnection embeddedDatabaseConnection) {
super(message);
this.properties = properties;
this.embeddedDatabaseConnection = embeddedDatabaseConnection;
}
EmbeddedDatabaseConnection getEmbeddedDatabaseConnection() {
return this.embeddedDatabaseConnection;
}
R2dbcProperties getProperties() {
return this.properties;
}
}
}

@ -25,7 +25,10 @@ import org.springframework.util.ClassUtils;
* @author Mark Paluch * @author Mark Paluch
* @author Stephane Nicoll * @author Stephane Nicoll
* @since 2.3.0 * @since 2.3.0
* @deprecated since 2.5.0 in favor of
* {@link org.springframework.boot.r2dbc.EmbeddedDatabaseConnection}
*/ */
@Deprecated
public enum EmbeddedDatabaseConnection { public enum EmbeddedDatabaseConnection {
/** /**

@ -1,5 +1,5 @@
/* /*
* Copyright 2012-2020 the original author or authors. * Copyright 2012-2021 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -20,7 +20,7 @@ import io.r2dbc.spi.ConnectionFactoryOptions;
import io.r2dbc.spi.Option; import io.r2dbc.spi.Option;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.springframework.boot.autoconfigure.r2dbc.ConnectionFactoryBuilder.ConnectionFactoryBeanCreationException; import org.springframework.boot.autoconfigure.r2dbc.ConnectionFactoryOptionsInitializer.ConnectionFactoryBeanCreationException;
import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.assertj.core.api.Assertions.assertThatThrownBy;
@ -33,6 +33,7 @@ import static org.assertj.core.api.Assertions.fail;
* @author Tadaya Tsuyukubo * @author Tadaya Tsuyukubo
* @author Stephane Nicoll * @author Stephane Nicoll
*/ */
@Deprecated
class ConnectionFactoryBuilderTests { class ConnectionFactoryBuilderTests {
@Test @Test
@ -51,7 +52,8 @@ class ConnectionFactoryBuilderTests {
fail("Should have thrown a " + ConnectionFactoryBeanCreationException.class.getName()); fail("Should have thrown a " + ConnectionFactoryBeanCreationException.class.getName());
} }
catch (ConnectionFactoryBeanCreationException ex) { catch (ConnectionFactoryBeanCreationException ex) {
assertThat(ex.getEmbeddedDatabaseConnection()).isEqualTo(EmbeddedDatabaseConnection.NONE); assertThat(ex.getEmbeddedDatabaseConnection())
.isEqualTo(org.springframework.boot.r2dbc.EmbeddedDatabaseConnection.NONE);
assertThat(ex.getProperties()).isSameAs(properties); assertThat(ex.getProperties()).isSameAs(properties);
} }
} }

@ -1,5 +1,5 @@
/* /*
* Copyright 2012-2020 the original author or authors. * Copyright 2012-2021 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -35,6 +35,7 @@ import org.springframework.beans.factory.BeanCreationException;
import org.springframework.boot.autoconfigure.AutoConfigurations; import org.springframework.boot.autoconfigure.AutoConfigurations;
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration; import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
import org.springframework.boot.autoconfigure.r2dbc.SimpleConnectionFactoryProvider.SimpleTestConnectionFactory; import org.springframework.boot.autoconfigure.r2dbc.SimpleConnectionFactoryProvider.SimpleTestConnectionFactory;
import org.springframework.boot.r2dbc.EmbeddedDatabaseConnection;
import org.springframework.boot.test.context.FilteredClassLoader; import org.springframework.boot.test.context.FilteredClassLoader;
import org.springframework.boot.test.context.runner.ApplicationContextRunner; import org.springframework.boot.test.context.runner.ApplicationContextRunner;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;

@ -63,6 +63,7 @@ dependencies {
optional("org.springframework:spring-messaging") optional("org.springframework:spring-messaging")
optional("org.springframework:spring-orm") optional("org.springframework:spring-orm")
optional("org.springframework:spring-oxm") optional("org.springframework:spring-oxm")
optional("org.springframework:spring-r2dbc")
optional("org.springframework:spring-test") optional("org.springframework:spring-test")
optional("org.springframework:spring-web") optional("org.springframework:spring-web")
optional("org.springframework:spring-webflux") optional("org.springframework:spring-webflux")
@ -83,6 +84,7 @@ dependencies {
testImplementation("com.squareup.okhttp3:okhttp") testImplementation("com.squareup.okhttp3:okhttp")
testImplementation("com.sun.xml.messaging.saaj:saaj-impl") testImplementation("com.sun.xml.messaging.saaj:saaj-impl")
testImplementation("io.projectreactor:reactor-test") testImplementation("io.projectreactor:reactor-test")
testImplementation("io.r2dbc:r2dbc-h2")
testImplementation("javax.xml.ws:jaxws-api") testImplementation("javax.xml.ws:jaxws-api")
testImplementation("mysql:mysql-connector-java") testImplementation("mysql:mysql-connector-java")
testImplementation("net.sourceforge.jtds:jtds") testImplementation("net.sourceforge.jtds:jtds")

@ -0,0 +1,137 @@
/*
* 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.r2dbc;
import java.util.function.Consumer;
import io.r2dbc.spi.ConnectionFactories;
import io.r2dbc.spi.ConnectionFactory;
import io.r2dbc.spi.ConnectionFactoryOptions;
import io.r2dbc.spi.ConnectionFactoryOptions.Builder;
import org.springframework.util.Assert;
/**
* Builder for {@link ConnectionFactory}.
*
* @author Mark Paluch
* @author Tadaya Tsuyukubo
* @author Stephane Nicoll
* @since 2.5.0
*/
public final class ConnectionFactoryBuilder {
private final Builder optionsBuilder;
private ConnectionFactoryBuilder(Builder optionsBuilder) {
this.optionsBuilder = optionsBuilder;
}
/**
* Initialize a new {@link ConnectionFactoryBuilder} based on the specified R2DBC url.
* @param url the url to use
* @return a new builder initialized with the options exposed in the specified url
* @see EmbeddedDatabaseConnection#getUrl(String)
*/
public static ConnectionFactoryBuilder withUrl(String url) {
Assert.hasText(url, () -> "Url must not be null");
return withOptions(ConnectionFactoryOptions.parse(url).mutate());
}
/**
* Initialize a new {@link ConnectionFactoryBuilder} based on the specified
* {@link Builder options}.
* @param options the options to use to initialize the builder
* @return a new builder initialized with the settings defined in the given
* {@link Builder options}
*/
public static ConnectionFactoryBuilder withOptions(Builder options) {
return new ConnectionFactoryBuilder(options);
}
/**
* Configure additional options.
* @param options a {@link Consumer} to customize the options
* @return this for method chaining
*/
public ConnectionFactoryBuilder configure(Consumer<Builder> options) {
options.accept(this.optionsBuilder);
return this;
}
/**
* Configure the {@linkplain ConnectionFactoryOptions#USER username}.
* @param username the connection factory username
* @return this for method chaining
*/
public ConnectionFactoryBuilder username(String username) {
return configure((options) -> options.option(ConnectionFactoryOptions.USER, username));
}
/**
* Configure the {@linkplain ConnectionFactoryOptions#PASSWORD password}.
* @param password the connection factory password
* @return this for method chaining
*/
public ConnectionFactoryBuilder password(CharSequence password) {
return configure((options) -> options.option(ConnectionFactoryOptions.PASSWORD, password));
}
/**
* Configure the {@linkplain ConnectionFactoryOptions#HOST host name}.
* @param host the connection factory hostname
* @return this for method chaining
*/
public ConnectionFactoryBuilder hostname(String host) {
return configure((options) -> options.option(ConnectionFactoryOptions.HOST, host));
}
/**
* Configure the {@linkplain ConnectionFactoryOptions#PORT port}.
* @param port the connection factory port
* @return this for method chaining
*/
public ConnectionFactoryBuilder port(int port) {
return configure((options) -> options.option(ConnectionFactoryOptions.PORT, port));
}
/**
* Configure the {@linkplain ConnectionFactoryOptions#DATABASE database}.
* @param database the connection factory database
* @return this for method chaining
*/
public ConnectionFactoryBuilder database(String database) {
return configure((options) -> options.option(ConnectionFactoryOptions.DATABASE, database));
}
/**
* Build a {@link ConnectionFactory} based on the state of this builder.
* @return a connection factory
*/
public ConnectionFactory build() {
return ConnectionFactories.get(buildOptions());
}
/**
* Build a {@link ConnectionFactoryOptions} based on the state of this builder.
* @return the options
*/
public ConnectionFactoryOptions buildOptions() {
return this.optionsBuilder.build();
}
}

@ -0,0 +1,84 @@
/*
* 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.r2dbc;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
/**
* Connection details for embedded databases compatible with r2dbc.
*
* @author Mark Paluch
* @author Stephane Nicoll
* @since 2.5.0
*/
public enum EmbeddedDatabaseConnection {
/**
* No Connection.
*/
NONE(null, null),
/**
* H2 Database Connection.
*/
H2("io.r2dbc.h2.H2ConnectionFactoryProvider",
"r2dbc:h2:mem:///%s?options=DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=FALSE");
private final String driverClassName;
private final String url;
EmbeddedDatabaseConnection(String driverClassName, String url) {
this.driverClassName = driverClassName;
this.url = url;
}
/**
* Returns the driver class name.
* @return the driver class name
*/
public String getDriverClassName() {
return this.driverClassName;
}
/**
* Returns the R2DBC URL for the connection using the specified {@code databaseName}.
* @param databaseName the name of the database
* @return the connection URL
*/
public String getUrl(String databaseName) {
Assert.hasText(databaseName, "DatabaseName must not be empty");
return (this.url != null) ? String.format(this.url, databaseName) : null;
}
/**
* Returns the most suitable {@link EmbeddedDatabaseConnection} for the given class
* loader.
* @param classLoader the class loader used to check for classes
* @return an {@link EmbeddedDatabaseConnection} or {@link #NONE}.
*/
public static EmbeddedDatabaseConnection get(ClassLoader classLoader) {
for (EmbeddedDatabaseConnection candidate : EmbeddedDatabaseConnection.values()) {
if (candidate != NONE && ClassUtils.isPresent(candidate.getDriverClassName(), classLoader)) {
return candidate;
}
}
return NONE;
}
}

@ -0,0 +1,20 @@
/*
* 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.
*/
/**
* Support for R2DBC connectivity.
*/
package org.springframework.boot.r2dbc;

@ -0,0 +1,123 @@
/*
* 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.r2dbc;
import java.util.UUID;
import io.r2dbc.h2.H2ConnectionFactoryMetadata;
import io.r2dbc.spi.ConnectionFactory;
import io.r2dbc.spi.ConnectionFactoryOptions;
import io.r2dbc.spi.Option;
import org.junit.jupiter.api.Test;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
/**
* Tests for {@link ConnectionFactoryBuilder}.
*
* @author Mark Paluch
* @author Tadaya Tsuyukubo
* @author Stephane Nicoll
*/
class ConnectionFactoryBuilderTests {
@Test
void createWithNullUrlShouldFail() {
assertThatIllegalArgumentException().isThrownBy(() -> ConnectionFactoryBuilder.withUrl(null));
}
@Test
void createWithEmptyUrlShouldFail() {
assertThatIllegalArgumentException().isThrownBy(() -> ConnectionFactoryBuilder.withUrl(" "));
}
@Test
void createWithEmbeddedConnectionNoneShouldFail() {
assertThatIllegalArgumentException()
.isThrownBy(() -> ConnectionFactoryBuilder.withUrl(EmbeddedDatabaseConnection.NONE.getUrl("test")));
}
@Test
void buildOptionsWithBasicUrlShouldExposeOptions() {
ConnectionFactoryOptions options = ConnectionFactoryBuilder.withUrl("r2dbc:simple://:pool:").buildOptions();
assertThat(options.hasOption(ConnectionFactoryOptions.USER)).isFalse();
assertThat(options.hasOption(ConnectionFactoryOptions.PASSWORD)).isFalse();
assertThat(options.getRequiredValue(ConnectionFactoryOptions.DRIVER)).isEqualTo("simple");
}
@Test
void buildOptionsWithEmbeddedConnectionH2ShouldExposeOptions() {
ConnectionFactoryOptions options = ConnectionFactoryBuilder
.withUrl(EmbeddedDatabaseConnection.H2.getUrl("testdb")).buildOptions();
assertThat(options.getRequiredValue(ConnectionFactoryOptions.DRIVER)).isEqualTo("h2");
assertThat(options.getRequiredValue(ConnectionFactoryOptions.PROTOCOL)).isEqualTo("mem");
assertThat(options.getRequiredValue(ConnectionFactoryOptions.DATABASE)).isEqualTo("testdb");
assertThat(options.hasOption(ConnectionFactoryOptions.HOST)).isFalse();
assertThat(options.hasOption(ConnectionFactoryOptions.PORT)).isFalse();
assertThat(options.hasOption(ConnectionFactoryOptions.USER)).isFalse();
assertThat(options.hasOption(ConnectionFactoryOptions.PASSWORD)).isFalse();
assertThat(options.getValue(Option.<String>valueOf("options")))
.isEqualTo("DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=FALSE");
}
@Test
void buildOptionsWithCompleteUrlShouldExposeOptions() {
ConnectionFactoryOptions options = ConnectionFactoryBuilder
.withUrl("r2dbc:simple:proto://user:password@myhost:4711/mydatabase").buildOptions();
assertThat(options.getRequiredValue(ConnectionFactoryOptions.DRIVER)).isEqualTo("simple");
assertThat(options.getRequiredValue(ConnectionFactoryOptions.PROTOCOL)).isEqualTo("proto");
assertThat(options.getRequiredValue(ConnectionFactoryOptions.USER)).isEqualTo("user");
assertThat(options.getRequiredValue(ConnectionFactoryOptions.PASSWORD)).isEqualTo("password");
assertThat(options.getRequiredValue(ConnectionFactoryOptions.HOST)).isEqualTo("myhost");
assertThat(options.getRequiredValue(ConnectionFactoryOptions.PORT)).isEqualTo(4711);
assertThat(options.getRequiredValue(ConnectionFactoryOptions.DATABASE)).isEqualTo("mydatabase");
}
@Test
void buildOptionsWithSpecificSettingsShouldOverrideUrlOptions() {
ConnectionFactoryOptions options = ConnectionFactoryBuilder
.withUrl("r2dbc:simple://user:password@myhost/mydatabase").username("another-user")
.password("another-password").hostname("another-host").port(1234).database("another-database")
.buildOptions();
assertThat(options.getRequiredValue(ConnectionFactoryOptions.USER)).isEqualTo("another-user");
assertThat(options.getRequiredValue(ConnectionFactoryOptions.PASSWORD)).isEqualTo("another-password");
assertThat(options.getRequiredValue(ConnectionFactoryOptions.HOST)).isEqualTo("another-host");
assertThat(options.getRequiredValue(ConnectionFactoryOptions.PORT)).isEqualTo(1234);
assertThat(options.getRequiredValue(ConnectionFactoryOptions.DATABASE)).isEqualTo("another-database");
}
@Test
void buildOptionsWithDriverPropertiesShouldExposeOptions() {
ConnectionFactoryOptions options = ConnectionFactoryBuilder.withUrl("r2dbc:simple://user:password@myhost")
.configure(
(o) -> o.option(Option.valueOf("simpleOne"), "one").option(Option.valueOf("simpleTwo"), "two"))
.buildOptions();
assertThat(options.getRequiredValue(Option.<String>valueOf("simpleOne"))).isEqualTo("one");
assertThat(options.getRequiredValue(Option.<String>valueOf("simpleTwo"))).isEqualTo("two");
}
@Test
void buildShouldExposeConnectionFactory() {
String databaseName = UUID.randomUUID().toString();
ConnectionFactory connectionFactory = ConnectionFactoryBuilder
.withUrl(EmbeddedDatabaseConnection.H2.getUrl(databaseName)).build();
assertThat(connectionFactory).isNotNull();
assertThat(connectionFactory.getMetadata().getName()).isEqualTo(H2ConnectionFactoryMetadata.NAME);
}
}

@ -0,0 +1,81 @@
/*
* 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.r2dbc;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.Arrays;
import java.util.stream.Stream;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Tests for {@link EmbeddedDatabaseConnection}.
*
* @author Stephane Nicoll
*/
class EmbeddedDatabaseConnectionTests {
@ParameterizedTest
@MethodSource("urlParameters")
void getUrlWithTestDatabase(EmbeddedDatabaseConnection connection, String expectUrl) {
assertThat(connection.getUrl("test-database")).isEqualTo(expectUrl);
}
@Test
void getReturnsH2ByDefault() {
assertThat(EmbeddedDatabaseConnection.get(EmbeddedDatabaseConnectionTests.class.getClassLoader()))
.isEqualTo(EmbeddedDatabaseConnection.H2);
}
@Test
void getWhenH2IsNotOnTheClasspathReturnsNone() {
assertThat(EmbeddedDatabaseConnection.get(new HidePackagesClassLoader("io.r2dbc.h2")))
.isEqualTo(EmbeddedDatabaseConnection.NONE);
}
static Stream<Arguments> urlParameters() {
return Stream.of(Arguments.arguments(EmbeddedDatabaseConnection.NONE, null),
Arguments.arguments(EmbeddedDatabaseConnection.H2,
"r2dbc:h2:mem:///test-database?options=DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=FALSE"));
}
private static class HidePackagesClassLoader extends URLClassLoader {
private final String[] hiddenPackages;
HidePackagesClassLoader(String... hiddenPackages) {
super(new URL[0], EmbeddedDatabaseConnectionTests.HidePackagesClassLoader.class.getClassLoader());
this.hiddenPackages = hiddenPackages;
}
@Override
protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
if (Arrays.stream(this.hiddenPackages).anyMatch(name::startsWith)) {
throw new ClassNotFoundException();
}
return super.loadClass(name, resolve);
}
}
}
Loading…
Cancel
Save