From f2b3f1f41f86b625ed76e05facb0755a160b4906 Mon Sep 17 00:00:00 2001 From: Andy Wilkinson Date: Tue, 19 Oct 2021 12:09:57 +0100 Subject: [PATCH] Make URL- and property-based pooling config mutually exclusive Closes gh-28144 --- .../ConnectionFactoryConfigurations.java | 112 +++++++++++------- .../MissingR2dbcPoolDependencyException.java | 32 +++++ ...ingR2dbcPoolDependencyFailureAnalyzer.java | 38 ++++++ ...ConnectionPoolConfigurationsException.java | 32 +++++ ...tionPoolConfigurationsFailureAnalzyer.java | 39 ++++++ .../r2dbc/R2dbcAutoConfiguration.java | 4 +- .../autoconfigure/r2dbc/R2dbcProperties.java | 13 ++ ...itional-spring-configuration-metadata.json | 5 - .../main/resources/META-INF/spring.factories | 2 + ...dbcPoolDependencyFailureAnalyzerTests.java | 42 +++++++ ...oolConfigurationsFailureAnalzyerTests.java | 42 +++++++ .../r2dbc/R2dbcAutoConfigurationTests.java | 49 ++++---- ...nfigurationWithoutConnectionPoolTests.java | 87 ++++++++++++++ 13 files changed, 420 insertions(+), 77 deletions(-) create mode 100644 spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/r2dbc/MissingR2dbcPoolDependencyException.java create mode 100644 spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/r2dbc/MissingR2dbcPoolDependencyFailureAnalyzer.java create mode 100644 spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/r2dbc/MultipleConnectionPoolConfigurationsException.java create mode 100644 spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/r2dbc/MultipleConnectionPoolConfigurationsFailureAnalzyer.java create mode 100644 spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/r2dbc/MissingR2dbcPoolDependencyFailureAnalyzerTests.java create mode 100644 spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/r2dbc/MultipleConnectionPoolConfigurationsFailureAnalzyerTests.java create mode 100644 spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/r2dbc/R2dbcAutoConfigurationWithoutConnectionPoolTests.java diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/r2dbc/ConnectionFactoryConfigurations.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/r2dbc/ConnectionFactoryConfigurations.java index 599b947c1f..e0c271a555 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/r2dbc/ConnectionFactoryConfigurations.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/r2dbc/ConnectionFactoryConfigurations.java @@ -29,15 +29,21 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.boot.autoconfigure.condition.SpringBootCondition; +import org.springframework.boot.autoconfigure.r2dbc.R2dbcProperties.Pool; import org.springframework.boot.context.properties.PropertyMapper; +import org.springframework.boot.context.properties.bind.BindResult; +import org.springframework.boot.context.properties.bind.Bindable; +import org.springframework.boot.context.properties.bind.Binder; import org.springframework.boot.r2dbc.EmbeddedDatabaseConnection; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Condition; import org.springframework.context.annotation.ConditionContext; import org.springframework.context.annotation.Conditional; import org.springframework.context.annotation.Configuration; +import org.springframework.core.env.Environment; import org.springframework.core.io.ResourceLoader; import org.springframework.core.type.AnnotatedTypeMetadata; +import org.springframework.util.ClassUtils; import org.springframework.util.StringUtils; /** @@ -51,39 +57,54 @@ abstract class ConnectionFactoryConfigurations { protected static ConnectionFactory createConnectionFactory(R2dbcProperties properties, ClassLoader classLoader, List optionsCustomizers) { - return org.springframework.boot.r2dbc.ConnectionFactoryBuilder - .withOptions(new ConnectionFactoryOptionsInitializer().initialize(properties, - () -> EmbeddedDatabaseConnection.get(classLoader))) - .configure((options) -> { - for (ConnectionFactoryOptionsBuilderCustomizer optionsCustomizer : optionsCustomizers) { - optionsCustomizer.customize(options); - } - }).build(); + try { + return org.springframework.boot.r2dbc.ConnectionFactoryBuilder + .withOptions(new ConnectionFactoryOptionsInitializer().initialize(properties, + () -> EmbeddedDatabaseConnection.get(classLoader))) + .configure((options) -> { + for (ConnectionFactoryOptionsBuilderCustomizer optionsCustomizer : optionsCustomizers) { + optionsCustomizer.customize(options); + } + }).build(); + } + catch (IllegalStateException ex) { + String message = ex.getMessage(); + if (message != null && message.contains("driver=pool") + && !ClassUtils.isPresent("io.r2dbc.pool.ConnectionPool", classLoader)) { + throw new MissingR2dbcPoolDependencyException(); + } + throw ex; + } } @Configuration(proxyBeanMethods = false) - @ConditionalOnClass(ConnectionPool.class) @Conditional(PooledConnectionFactoryCondition.class) @ConditionalOnMissingBean(ConnectionFactory.class) - static class Pool { + static class PoolConfiguration { + + @Configuration(proxyBeanMethods = false) + @ConditionalOnClass(ConnectionPool.class) + static class PooledConnectionFactoryConfiguration { + + @Bean(destroyMethod = "dispose") + ConnectionPool connectionFactory(R2dbcProperties properties, ResourceLoader resourceLoader, + ObjectProvider customizers) { + ConnectionFactory connectionFactory = createConnectionFactory(properties, + resourceLoader.getClassLoader(), customizers.orderedStream().collect(Collectors.toList())); + R2dbcProperties.Pool pool = properties.getPool(); + PropertyMapper map = PropertyMapper.get().alwaysApplyingWhenNonNull(); + ConnectionPoolConfiguration.Builder builder = ConnectionPoolConfiguration.builder(connectionFactory); + map.from(pool.getMaxIdleTime()).to(builder::maxIdleTime); + map.from(pool.getMaxLifeTime()).to(builder::maxLifeTime); + map.from(pool.getMaxAcquireTime()).to(builder::maxAcquireTime); + map.from(pool.getMaxCreateConnectionTime()).to(builder::maxCreateConnectionTime); + map.from(pool.getInitialSize()).to(builder::initialSize); + map.from(pool.getMaxSize()).to(builder::maxSize); + map.from(pool.getValidationQuery()).whenHasText().to(builder::validationQuery); + map.from(pool.getValidationDepth()).to(builder::validationDepth); + return new ConnectionPool(builder.build()); + } - @Bean(destroyMethod = "dispose") - ConnectionPool connectionFactory(R2dbcProperties properties, ResourceLoader resourceLoader, - ObjectProvider customizers) { - ConnectionFactory connectionFactory = createConnectionFactory(properties, resourceLoader.getClassLoader(), - customizers.orderedStream().collect(Collectors.toList())); - R2dbcProperties.Pool pool = properties.getPool(); - PropertyMapper map = PropertyMapper.get().alwaysApplyingWhenNonNull(); - ConnectionPoolConfiguration.Builder builder = ConnectionPoolConfiguration.builder(connectionFactory); - map.from(pool.getMaxIdleTime()).to(builder::maxIdleTime); - map.from(pool.getMaxLifeTime()).to(builder::maxLifeTime); - map.from(pool.getMaxAcquireTime()).to(builder::maxAcquireTime); - map.from(pool.getMaxCreateConnectionTime()).to(builder::maxCreateConnectionTime); - map.from(pool.getInitialSize()).to(builder::initialSize); - map.from(pool.getMaxSize()).to(builder::maxSize); - map.from(pool.getValidationQuery()).whenHasText().to(builder::validationQuery); - map.from(pool.getValidationDepth()).to(builder::validationDepth); - return new ConnectionPool(builder.build()); } } @@ -92,7 +113,7 @@ abstract class ConnectionFactoryConfigurations { @ConditionalOnProperty(prefix = "spring.r2dbc.pool", value = "enabled", havingValue = "false", matchIfMissing = true) @ConditionalOnMissingBean(ConnectionFactory.class) - static class Generic { + static class GenericConfiguration { @Bean ConnectionFactory connectionFactory(R2dbcProperties properties, ResourceLoader resourceLoader, @@ -105,26 +126,35 @@ abstract class ConnectionFactoryConfigurations { /** * {@link Condition} that checks that a {@link ConnectionPool} is requested. The - * condition matches if pooling was opt-in via configuration and the r2dbc url does - * not contain pooling-related options. + * condition matches if pooling was opt-in via configuration. If any of the + * spring.r2dbc.pool.* properties have been configured, an exception is thrown if the + * URL also contains pooling-related options or io.r2dbc.pool.ConnectionPool is not on + * the class path. */ static class PooledConnectionFactoryCondition extends SpringBootCondition { @Override public ConditionOutcome getMatchOutcome(ConditionContext context, AnnotatedTypeMetadata metadata) { - boolean poolEnabled = context.getEnvironment().getProperty("spring.r2dbc.pool.enabled", Boolean.class, - true); - if (poolEnabled) { - // Make sure the URL does not have pool options - String url = context.getEnvironment().getProperty("spring.r2dbc.url"); - boolean pooledUrl = StringUtils.hasText(url) && url.contains(":pool:"); - if (pooledUrl) { - return ConditionOutcome.noMatch("R2DBC Connection URL contains pooling-related options"); + BindResult pool = Binder.get(context.getEnvironment()).bind("spring.r2dbc.pool", + Bindable.of(Pool.class)); + if (hasPoolUrl(context.getEnvironment())) { + if (pool.isBound()) { + throw new MultipleConnectionPoolConfigurationsException(); } - return ConditionOutcome - .match("Pooling is enabled and R2DBC Connection URL does not contain pooling-related options"); + return ConditionOutcome.noMatch("URL-based pooling has been configured"); + } + if (pool.isBound() && !ClassUtils.isPresent("io.r2dbc.pool.ConnectionPool", context.getClassLoader())) { + throw new MissingR2dbcPoolDependencyException(); + } + if (pool.orElseGet(Pool::new).isEnabled()) { + return ConditionOutcome.match("Property-based pooling is enabled"); } - return ConditionOutcome.noMatch("Pooling is disabled"); + return ConditionOutcome.noMatch("Property-based pooling is disabled"); + } + + private boolean hasPoolUrl(Environment environment) { + String url = environment.getProperty("spring.r2dbc.url"); + return StringUtils.hasText(url) && url.contains(":pool:"); } } diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/r2dbc/MissingR2dbcPoolDependencyException.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/r2dbc/MissingR2dbcPoolDependencyException.java new file mode 100644 index 0000000000..178677f5ad --- /dev/null +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/r2dbc/MissingR2dbcPoolDependencyException.java @@ -0,0 +1,32 @@ +/* + * 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; + +/** + * Exception thrown when R2DBC connection pooling has been configured but the + * {@code io.r2dbc:r2dbc-pool} dependency is missing. + * + * @author Andy Wilkinson + */ +class MissingR2dbcPoolDependencyException extends RuntimeException { + + MissingR2dbcPoolDependencyException() { + super("R2DBC connection pooling has been configured but the io.r2dbc.pool.ConnectionPool class is not " + + "present."); + } + +} diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/r2dbc/MissingR2dbcPoolDependencyFailureAnalyzer.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/r2dbc/MissingR2dbcPoolDependencyFailureAnalyzer.java new file mode 100644 index 0000000000..149f0808ae --- /dev/null +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/r2dbc/MissingR2dbcPoolDependencyFailureAnalyzer.java @@ -0,0 +1,38 @@ +/* + * 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 org.springframework.boot.diagnostics.AbstractFailureAnalyzer; +import org.springframework.boot.diagnostics.FailureAnalysis; +import org.springframework.boot.diagnostics.FailureAnalyzer; + +/** + * {@link FailureAnalyzer} for {@link MissingR2dbcPoolDependencyException}. + * + * @author Andy Wilkinson + */ +class MissingR2dbcPoolDependencyFailureAnalyzer extends AbstractFailureAnalyzer { + + @Override + protected FailureAnalysis analyze(Throwable rootFailure, MissingR2dbcPoolDependencyException cause) { + return new FailureAnalysis(cause.getMessage(), + "Update your application's build to depend on io.r2dbc:r2dbc-pool or your application's configuration " + + "to disable R2DBC connection pooling.", + cause); + } + +} diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/r2dbc/MultipleConnectionPoolConfigurationsException.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/r2dbc/MultipleConnectionPoolConfigurationsException.java new file mode 100644 index 0000000000..017151e414 --- /dev/null +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/r2dbc/MultipleConnectionPoolConfigurationsException.java @@ -0,0 +1,32 @@ +/* + * 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; + +/** + * Exception thrown when R2DBC connection pooling has been configured both by the URL + * ({@code spring.r2dbc.url}) and the pool properties ({@code spring.r2dbc.pool.*}. + * + * @author Andy Wilkinson + */ +class MultipleConnectionPoolConfigurationsException extends RuntimeException { + + MultipleConnectionPoolConfigurationsException() { + super("R2DBC connection pooling configuration should be provided by either the spring.r2dbc.pool.* " + + "properties or the spring.r2dbc.url property but both have been used."); + } + +} diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/r2dbc/MultipleConnectionPoolConfigurationsFailureAnalzyer.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/r2dbc/MultipleConnectionPoolConfigurationsFailureAnalzyer.java new file mode 100644 index 0000000000..34cf93f68b --- /dev/null +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/r2dbc/MultipleConnectionPoolConfigurationsFailureAnalzyer.java @@ -0,0 +1,39 @@ +/* + * 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 org.springframework.boot.diagnostics.AbstractFailureAnalyzer; +import org.springframework.boot.diagnostics.FailureAnalysis; +import org.springframework.boot.diagnostics.FailureAnalyzer; + +/** + * {@link FailureAnalyzer} for {@link MultipleConnectionPoolConfigurationsException}. + * + * @author Andy Wilkinson + */ +class MultipleConnectionPoolConfigurationsFailureAnalzyer + extends AbstractFailureAnalyzer { + + @Override + protected FailureAnalysis analyze(Throwable rootFailure, MultipleConnectionPoolConfigurationsException cause) { + return new FailureAnalysis(cause.getMessage(), + "Update your configuration so that R2DBC connection pooling is configured using either the " + + "spring.r2dbc.url property or the spring.r2dbc.pool.* properties", + cause); + } + +} diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/r2dbc/R2dbcAutoConfiguration.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/r2dbc/R2dbcAutoConfiguration.java index 1ef806e0dc..0074af46d1 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/r2dbc/R2dbcAutoConfiguration.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/r2dbc/R2dbcAutoConfiguration.java @@ -40,8 +40,8 @@ import org.springframework.context.annotation.Import; @ConditionalOnResource(resources = "classpath:META-INF/services/io.r2dbc.spi.ConnectionFactoryProvider") @AutoConfigureBefore({ DataSourceAutoConfiguration.class, SqlInitializationAutoConfiguration.class }) @EnableConfigurationProperties(R2dbcProperties.class) -@Import({ ConnectionFactoryConfigurations.Pool.class, ConnectionFactoryConfigurations.Generic.class, - ConnectionFactoryDependentConfiguration.class }) +@Import({ ConnectionFactoryConfigurations.PoolConfiguration.class, + ConnectionFactoryConfigurations.GenericConfiguration.class, ConnectionFactoryDependentConfiguration.class }) public class R2dbcAutoConfiguration { } diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/r2dbc/R2dbcProperties.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/r2dbc/R2dbcProperties.java index e93add6bb4..77de7d4433 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/r2dbc/R2dbcProperties.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/r2dbc/R2dbcProperties.java @@ -178,6 +178,11 @@ public class R2dbcProperties { */ private ValidationDepth validationDepth = ValidationDepth.LOCAL; + /** + * Whether pooling is enabled. Requires r2dbc-pool. + */ + private boolean enabled = true; + public Duration getMaxIdleTime() { return this.maxIdleTime; } @@ -242,6 +247,14 @@ public class R2dbcProperties { this.validationDepth = validationDepth; } + public boolean isEnabled() { + return this.enabled; + } + + public void setEnabled(boolean enabled) { + this.enabled = enabled; + } + } } diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/resources/META-INF/additional-spring-configuration-metadata.json b/spring-boot-project/spring-boot-autoconfigure/src/main/resources/META-INF/additional-spring-configuration-metadata.json index 038045c1a9..607d04177e 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/resources/META-INF/additional-spring-configuration-metadata.json +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/resources/META-INF/additional-spring-configuration-metadata.json @@ -1688,11 +1688,6 @@ "name": "spring.quartz.scheduler-name", "defaultValue": "quartzScheduler" }, - { - "name": "spring.r2dbc.pool.enabled", - "type": "java.lang.Boolean", - "description": "Whether pooling is enabled. Enabled automatically if \"r2dbc-pool\" is on the classpath." - }, { "name": "spring.r2dbc.pool.validation-depth", "defaultValue": "local" diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/resources/META-INF/spring.factories b/spring-boot-project/spring-boot-autoconfigure/src/main/resources/META-INF/spring.factories index be659a21ec..3f22befcdd 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/resources/META-INF/spring.factories +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/resources/META-INF/spring.factories @@ -165,6 +165,8 @@ org.springframework.boot.autoconfigure.jdbc.DataSourceBeanCreationFailureAnalyze org.springframework.boot.autoconfigure.jdbc.HikariDriverConfigurationFailureAnalyzer,\ org.springframework.boot.autoconfigure.jooq.NoDslContextBeanFailureAnalyzer,\ org.springframework.boot.autoconfigure.r2dbc.ConnectionFactoryBeanCreationFailureAnalyzer,\ +org.springframework.boot.autoconfigure.r2dbc.MissingR2dbcPoolDependencyFailureAnalyzer,\ +org.springframework.boot.autoconfigure.r2dbc.MultipleConnectionPoolConfigurationsFailureAnalzyer,\ org.springframework.boot.autoconfigure.r2dbc.NoConnectionFactoryBeanFailureAnalyzer,\ org.springframework.boot.autoconfigure.session.NonUniqueSessionRepositoryFailureAnalyzer diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/r2dbc/MissingR2dbcPoolDependencyFailureAnalyzerTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/r2dbc/MissingR2dbcPoolDependencyFailureAnalyzerTests.java new file mode 100644 index 0000000000..ea841a8a29 --- /dev/null +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/r2dbc/MissingR2dbcPoolDependencyFailureAnalyzerTests.java @@ -0,0 +1,42 @@ +/* + * 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 org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Tests for {@link MissingR2dbcPoolDependencyFailureAnalyzer} + * + * @author Andy Wilkinson + */ +class MissingR2dbcPoolDependencyFailureAnalyzerTests { + + private final MissingR2dbcPoolDependencyFailureAnalyzer failureAnalyzer = new MissingR2dbcPoolDependencyFailureAnalyzer(); + + @Test + void analyzeWhenDifferentFailureShouldReturnNull() { + assertThat(this.failureAnalyzer.analyze(new Exception())).isNull(); + } + + @Test + void analyzeWhenMissingR2dbcPoolDependencyShouldReturnAnalysis() { + assertThat(this.failureAnalyzer.analyze(new MissingR2dbcPoolDependencyException())).isNotNull(); + } + +} diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/r2dbc/MultipleConnectionPoolConfigurationsFailureAnalzyerTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/r2dbc/MultipleConnectionPoolConfigurationsFailureAnalzyerTests.java new file mode 100644 index 0000000000..5c5d72c663 --- /dev/null +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/r2dbc/MultipleConnectionPoolConfigurationsFailureAnalzyerTests.java @@ -0,0 +1,42 @@ +/* + * 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 org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Tests for {@link MultipleConnectionPoolConfigurationsFailureAnalzyer} + * + * @author Andy Wilkinson + */ +class MultipleConnectionPoolConfigurationsFailureAnalzyerTests { + + private final MultipleConnectionPoolConfigurationsFailureAnalzyer failureAnalyzer = new MultipleConnectionPoolConfigurationsFailureAnalzyer(); + + @Test + void analyzeWhenDifferentFailureShouldReturnNull() { + assertThat(this.failureAnalyzer.analyze(new Exception())).isNull(); + } + + @Test + void analyzeWhenMultipleConnectionPoolConfigurationsShouldReturnAnalysis() { + assertThat(this.failureAnalyzer.analyze(new MultipleConnectionPoolConfigurationsException())).isNotNull(); + } + +} diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/r2dbc/R2dbcAutoConfigurationTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/r2dbc/R2dbcAutoConfigurationTests.java index 2f3aa57be2..3c7d9ac491 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/r2dbc/R2dbcAutoConfigurationTests.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/r2dbc/R2dbcAutoConfigurationTests.java @@ -20,7 +20,6 @@ import java.net.URL; import java.net.URLClassLoader; import java.time.Duration; import java.util.UUID; -import java.util.function.Function; import javax.sql.DataSource; @@ -99,14 +98,31 @@ class R2dbcAutoConfigurationTests { } @Test - void configureWithUrlPoolAndPoolPropertiesApplyUrlPoolOptions() { + void configureWithUrlPoolAndPoolPropertiesFails() { this.contextRunner .withPropertyValues("spring.r2dbc.url:r2dbc:pool:h2:mem:///" + randomDatabaseName() + "?maxSize=12", "spring.r2dbc.pool.max-size=15") + .run((context) -> assertThat(context).getFailure().getRootCause() + .isInstanceOf(MultipleConnectionPoolConfigurationsException.class)); + } + + @Test + void configureWithUrlPoolAndPropertyBasedPoolingDisabledFails() { + this.contextRunner + .withPropertyValues("spring.r2dbc.url:r2dbc:pool:h2:mem:///" + randomDatabaseName() + "?maxSize=12", + "spring.r2dbc.pool.enabled=false") + .run((context) -> assertThat(context).getFailure().getRootCause() + .isInstanceOf(MultipleConnectionPoolConfigurationsException.class)); + } + + @Test + void configureWithUrlPoolAndNoPoolPropertiesCreatesPool() { + this.contextRunner + .withPropertyValues("spring.r2dbc.url:r2dbc:pool:h2:mem:///" + randomDatabaseName() + "?maxSize=12") .run((context) -> { assertThat(context).hasSingleBean(ConnectionFactory.class).hasSingleBean(ConnectionPool.class); - PoolMetrics poolMetrics = context.getBean(ConnectionPool.class).getMetrics().get(); - assertThat(poolMetrics.getMaxAllocatedSize()).isEqualTo(12); + ConnectionPool connectionPool = context.getBean(ConnectionPool.class); + assertThat(connectionPool.getMetrics().get().getMaxAllocatedSize()).isEqualTo(12); }); } @@ -132,27 +148,6 @@ class R2dbcAutoConfigurationTests { }); } - @Test - void configureWithoutR2dbcPoolCreateGenericConnectionFactory() { - this.contextRunner.with(hideConnectionPool()).withPropertyValues("spring.r2dbc.url:r2dbc:h2:mem:///" - + randomDatabaseName() + "?options=DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=FALSE").run((context) -> { - assertThat(context).hasSingleBean(ConnectionFactory.class); - assertThat(context.getBean(ConnectionFactory.class)) - .asInstanceOf(type(OptionsCapableConnectionFactory.class)) - .extracting(Wrapped::unwrap) - .isExactlyInstanceOf(H2ConnectionFactory.class); - }); - } - - @Test - void configureWithoutR2dbcPoolAndPoolEnabledDoesNotCreateConnectionFactory() { - this.contextRunner.with(hideConnectionPool()) - .withPropertyValues("spring.r2dbc.pool.enabled=true", - "spring.r2dbc.url:r2dbc:h2:mem:///" + randomDatabaseName() - + "?options=DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=FALSE") - .run((context) -> assertThat(context).doesNotHaveBean(ConnectionFactory.class)); - } - @Test void configureWithoutPoolInvokeOptionCustomizer() { this.contextRunner @@ -297,10 +292,6 @@ class R2dbcAutoConfigurationTests { return "testdb-" + UUID.randomUUID(); } - private Function hideConnectionPool() { - return (runner) -> runner.withClassLoader(new FilteredClassLoader("io.r2dbc.pool")); - } - private static class DisableEmbeddedDatabaseClassLoader extends URLClassLoader { DisableEmbeddedDatabaseClassLoader() { diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/r2dbc/R2dbcAutoConfigurationWithoutConnectionPoolTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/r2dbc/R2dbcAutoConfigurationWithoutConnectionPoolTests.java new file mode 100644 index 0000000000..e9ef7bf097 --- /dev/null +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/r2dbc/R2dbcAutoConfigurationWithoutConnectionPoolTests.java @@ -0,0 +1,87 @@ +/* + * 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.UUID; + +import io.r2dbc.h2.H2ConnectionFactory; +import io.r2dbc.spi.ConnectionFactory; +import io.r2dbc.spi.Wrapped; +import org.assertj.core.api.InstanceOfAssertFactories; +import org.assertj.core.api.InstanceOfAssertFactory; +import org.assertj.core.api.ObjectAssert; +import org.junit.jupiter.api.Test; + +import org.springframework.boot.autoconfigure.AutoConfigurations; +import org.springframework.boot.r2dbc.OptionsCapableConnectionFactory; +import org.springframework.boot.test.context.runner.ApplicationContextRunner; +import org.springframework.boot.testsupport.classpath.ClassPathExclusions; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Tests for {@link R2dbcAutoConfiguration} without the {@code io.r2dbc:r2dbc-pool} + * dependency. + * + * @author Andy Wilkinson + */ +@ClassPathExclusions("r2dbc-pool-*.jar") +class R2dbcAutoConfigurationWithoutConnectionPoolTests { + + private final ApplicationContextRunner contextRunner = new ApplicationContextRunner() + .withConfiguration(AutoConfigurations.of(R2dbcAutoConfiguration.class)); + + @Test + void configureWithoutR2dbcPoolCreateGenericConnectionFactory() { + this.contextRunner.withPropertyValues("spring.r2dbc.url:r2dbc:h2:mem:///" + randomDatabaseName() + + "?options=DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=FALSE").run((context) -> { + assertThat(context).hasSingleBean(ConnectionFactory.class); + assertThat(context.getBean(ConnectionFactory.class)) + .asInstanceOf(type(OptionsCapableConnectionFactory.class)) + .extracting(Wrapped::unwrap) + .isExactlyInstanceOf(H2ConnectionFactory.class); + }); + } + + @Test + void configureWithoutR2dbcPoolAndPoolEnabledShouldFail() { + this.contextRunner + .withPropertyValues("spring.r2dbc.pool.enabled=true", + "spring.r2dbc.url:r2dbc:h2:mem:///" + randomDatabaseName() + + "?options=DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=FALSE") + .run((context) -> assertThat(context).getFailure().getRootCause() + .isInstanceOf(MissingR2dbcPoolDependencyException.class)); + } + + @Test + void configureWithoutR2dbcPoolAndPoolUrlShouldFail() { + this.contextRunner + .withPropertyValues("spring.r2dbc.url:r2dbc:pool:h2:mem:///" + randomDatabaseName() + + "?options=DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=FALSE") + .run((context) -> assertThat(context).getFailure().getRootCause() + .isInstanceOf(MissingR2dbcPoolDependencyException.class)); + } + + private InstanceOfAssertFactory> type(Class type) { + return InstanceOfAssertFactories.type(type); + } + + private String randomDatabaseName() { + return "testdb-" + UUID.randomUUID(); + } + +}