From 5249f54c5a3253c30132d80cf715c9a734db7409 Mon Sep 17 00:00:00 2001 From: Dave Syer Date: Tue, 13 May 2014 15:40:55 +0100 Subject: [PATCH] Integrate @ConfigurationProperties @Beans with DataSource configuration We now have a much simpler DataSourceAutoConfiguration that binds to whatever DataSource concrete type it finds at runtime. To be able to quickly switch between Hikari and the other types of DataSource there's a minute shim for translating the common properties (username, password, url, driverClassName), but actually only url is different. The shim and also DataSource initialization is supported through DataSourceProperties, but the other native properties get bound directly through the concrete runtime type of the DataSource. The /configprops endpoint works (and is exposed in the actuator sample). Fixes gh-840, fixes gh-477, see also gh-808. --- .../HealthIndicatorAutoConfiguration.java | 12 +- ...rtiesReportEndpointSerializationTests.java | 110 ++++++++++ .../jdbc/CommonsDataSourceConfiguration.java | 101 --------- .../jdbc/DataSourceAutoConfiguration.java | 199 ++++-------------- .../autoconfigure/jdbc/DataSourceFactory.java | 133 ++++++++++++ ...uration.java => DataSourceProperties.java} | 125 +++-------- .../jdbc/HikariDataSourceConfiguration.java | 125 ----------- .../jdbc/RelaxedDataSourceFactory.java | 78 ------- .../jdbc/TomcatDataSourceConfiguration.java | 89 -------- .../CommonsDataSourceConfigurationTests.java | 16 ++ .../DataSourceAutoConfigurationTests.java | 53 ++++- .../HikariDataSourceConfigurationTests.java | 47 ++--- .../TomcatDataSourceConfigurationTests.java | 44 ++-- .../spring-boot-sample-actuator/build.gradle | 4 +- .../spring-boot-sample-actuator/pom.xml | 8 + ...pertiesSampleActuatorApplicationTests.java | 4 +- ...gementAddressActuatorApplicationTests.java | 3 +- ...entPortSampleActuatorApplicationTests.java | 3 +- .../SampleActuatorApplicationTests.java | 2 +- 19 files changed, 445 insertions(+), 711 deletions(-) create mode 100644 spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/ConfigurationPropertiesReportEndpointSerializationTests.java delete mode 100644 spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jdbc/CommonsDataSourceConfiguration.java create mode 100644 spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jdbc/DataSourceFactory.java rename spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jdbc/{AbstractDataSourceConfiguration.java => DataSourceProperties.java} (56%) delete mode 100644 spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jdbc/HikariDataSourceConfiguration.java delete mode 100644 spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jdbc/RelaxedDataSourceFactory.java delete mode 100644 spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jdbc/TomcatDataSourceConfiguration.java diff --git a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/HealthIndicatorAutoConfiguration.java b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/HealthIndicatorAutoConfiguration.java index 69b179b7e9..c2e743120c 100644 --- a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/HealthIndicatorAutoConfiguration.java +++ b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/HealthIndicatorAutoConfiguration.java @@ -34,11 +34,7 @@ import org.springframework.boot.autoconfigure.EnableAutoConfiguration; import org.springframework.boot.autoconfigure.amqp.RabbitAutoConfiguration; import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; -import org.springframework.boot.autoconfigure.jdbc.CommonsDataSourceConfiguration; import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration; -import org.springframework.boot.autoconfigure.jdbc.EmbeddedDataSourceConfiguration; -import org.springframework.boot.autoconfigure.jdbc.HikariDataSourceConfiguration; -import org.springframework.boot.autoconfigure.jdbc.TomcatDataSourceConfiguration; import org.springframework.boot.autoconfigure.mongo.MongoAutoConfiguration; import org.springframework.boot.autoconfigure.mongo.MongoDataAutoConfiguration; import org.springframework.boot.autoconfigure.redis.RedisAutoConfiguration; @@ -54,11 +50,9 @@ import org.springframework.data.redis.connection.RedisConnectionFactory; * @since 1.1.0 */ @Configuration -@AutoConfigureAfter({ DataSourceAutoConfiguration.class, - EmbeddedDataSourceConfiguration.class, CommonsDataSourceConfiguration.class, - HikariDataSourceConfiguration.class, TomcatDataSourceConfiguration.class, - MongoAutoConfiguration.class, MongoDataAutoConfiguration.class, - RedisAutoConfiguration.class, RabbitAutoConfiguration.class }) +@AutoConfigureAfter({ DataSourceAutoConfiguration.class, MongoAutoConfiguration.class, + MongoDataAutoConfiguration.class, RedisAutoConfiguration.class, + RabbitAutoConfiguration.class }) public class HealthIndicatorAutoConfiguration { @Bean diff --git a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/ConfigurationPropertiesReportEndpointSerializationTests.java b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/ConfigurationPropertiesReportEndpointSerializationTests.java new file mode 100644 index 0000000000..70ea3b1fa6 --- /dev/null +++ b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/ConfigurationPropertiesReportEndpointSerializationTests.java @@ -0,0 +1,110 @@ +/* + * Copyright 2013-2014 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 + * + * http://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.actuate.endpoint; + +import java.util.Map; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.springframework.boot.context.properties.ConfigurationBeanFactoryMetaData; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.boot.test.EnvironmentTestUtils; +import org.springframework.context.annotation.AnnotationConfigApplicationContext; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; + +public class ConfigurationPropertiesReportEndpointSerializationTests { + + private AnnotationConfigApplicationContext context; + + @Before + public void setup() { + this.context = new AnnotationConfigApplicationContext(); + } + + @After + public void close() { + if (this.context != null) { + this.context.close(); + } + } + + @Test + @SuppressWarnings("unchecked") + public void testNaming() throws Exception { + this.context.register(Config.class); + EnvironmentTestUtils.addEnvironment(this.context, "foo.name:foo"); + this.context.refresh(); + ConfigurationPropertiesReportEndpoint report = this.context + .getBean(ConfigurationPropertiesReportEndpoint.class); + Map properties = report.invoke(); + Map nestedProperties = (Map) properties + .get("foo"); + assertNotNull(nestedProperties); + assertEquals("foo", nestedProperties.get("prefix")); + Map map = (Map) nestedProperties + .get("properties"); + assertNotNull(map); + assertEquals(1, map.size()); + assertEquals("foo", map.get("name")); + } + + @Configuration + @EnableConfigurationProperties + public static class Config { + + @Bean + public ConfigurationPropertiesReportEndpoint endpoint( + ConfigurationBeanFactoryMetaData beanFactoryMetaData) { + ConfigurationPropertiesReportEndpoint endpoint = new ConfigurationPropertiesReportEndpoint(); + endpoint.setConfigurationBeanFactoryMetaData(beanFactoryMetaData); + return endpoint; + } + + @Bean + @ConfigurationProperties(prefix = "foo") + public Foo foo() { + return new Foo(); + } + + } + + public static class Foo { + + private String name = "654321"; + + public String getName() { + return this.name; + } + + public void setName(String name) { + this.name = name; + } + + // No setter so it doesn't appear in the report + public String getSummary() { + return "Name: " + this.name; + } + + } + +} diff --git a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jdbc/CommonsDataSourceConfiguration.java b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jdbc/CommonsDataSourceConfiguration.java deleted file mode 100644 index ff9d65b5e2..0000000000 --- a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jdbc/CommonsDataSourceConfiguration.java +++ /dev/null @@ -1,101 +0,0 @@ -/* - * Copyright 2012-2014 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 - * - * http://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.jdbc; - -import java.sql.SQLException; - -import javax.annotation.PreDestroy; -import javax.sql.DataSource; - -import org.apache.commons.dbcp.BasicDataSource; -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.dao.DataAccessResourceFailureException; - -/** - * Configuration for a Commons DBCP database pool. The DBCP pool is popular but not - * recommended in high volume environments (the Tomcat DataSource is more reliable). - * - * @author Dave Syer - * @see DataSourceAutoConfiguration - */ -@Configuration -public class CommonsDataSourceConfiguration extends AbstractDataSourceConfiguration { - - private static Log logger = LogFactory.getLog(CommonsDataSourceConfiguration.class); - - private BasicDataSource pool; - - public CommonsDataSourceConfiguration() { - // Ensure to set the correct default value for Commons DBCP - setInitialSize(0); - } - - @Bean(destroyMethod = "close") - public DataSource dataSource() { - logger.info("Hint: using Commons DBCP BasicDataSource. It's going to work, " - + "but the Tomcat DataSource is more reliable."); - this.pool = createAndConfigurePool(); - return this.pool; - } - - private BasicDataSource createAndConfigurePool() { - BasicDataSource pool = new BasicDataSource(); - pool.setDriverClassName(getDriverClassName()); - pool.setUrl(getUrl()); - if (getUsername() != null) { - pool.setUsername(getUsername()); - } - if (getPassword() != null) { - pool.setPassword(getPassword()); - } - pool.setInitialSize(getInitialSize()); - pool.setMaxActive(getMaxActive()); - pool.setMaxIdle(getMaxIdle()); - pool.setMinIdle(getMinIdle()); - pool.setTestOnBorrow(isTestOnBorrow()); - pool.setTestOnReturn(isTestOnReturn()); - pool.setTestWhileIdle(isTestWhileIdle()); - pool.setValidationQuery(getValidationQuery()); - if (getTimeBetweenEvictionRunsMillis() != null) { - pool.setTimeBetweenEvictionRunsMillis(getTimeBetweenEvictionRunsMillis()); - } - if (getMinEvictableIdleTimeMillis() != null) { - pool.setMinEvictableIdleTimeMillis(getMinEvictableIdleTimeMillis()); - } - if (getMaxWaitMillis() != null) { - pool.setMaxWait(getMaxWaitMillis()); - } - return pool; - } - - @PreDestroy - public void close() { - if (this.pool != null) { - try { - this.pool.close(); - } - catch (SQLException ex) { - throw new DataAccessResourceFailureException( - "Could not close data source", ex); - } - } - } - -} diff --git a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jdbc/DataSourceAutoConfiguration.java b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jdbc/DataSourceAutoConfiguration.java index dc067c899f..266fcecd63 100644 --- a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jdbc/DataSourceAutoConfiguration.java +++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jdbc/DataSourceAutoConfiguration.java @@ -35,16 +35,15 @@ import org.springframework.boot.autoconfigure.condition.ConditionOutcome; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.autoconfigure.condition.SpringBootCondition; -import org.springframework.boot.bind.RelaxedPropertyResolver; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.context.ApplicationContext; -import org.springframework.context.EnvironmentAware; 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.context.annotation.Import; -import org.springframework.core.env.Environment; import org.springframework.core.io.Resource; import org.springframework.core.type.AnnotatedTypeMetadata; import org.springframework.jdbc.core.JdbcOperations; @@ -54,7 +53,6 @@ import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate; import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseType; import org.springframework.jdbc.datasource.init.DatabasePopulatorUtils; import org.springframework.jdbc.datasource.init.ResourceDatabasePopulator; -import org.springframework.util.ClassUtils; import org.springframework.util.StringUtils; /** @@ -65,7 +63,8 @@ import org.springframework.util.StringUtils; */ @Configuration @ConditionalOnClass(EmbeddedDatabaseType.class) -public class DataSourceAutoConfiguration implements EnvironmentAware { +@EnableConfigurationProperties(DataSourceProperties.class) +public class DataSourceAutoConfiguration { private static Log logger = LogFactory.getLog(DataSourceAutoConfiguration.class); @@ -77,26 +76,20 @@ public class DataSourceAutoConfiguration implements EnvironmentAware { @Autowired private ApplicationContext applicationContext; - private RelaxedPropertyResolver datasourceProperties; - - @Override - public void setEnvironment(Environment environment) { - this.datasourceProperties = new RelaxedPropertyResolver(environment, - CONFIGURATION_PREFIX + "."); - } + @Autowired + private DataSourceProperties properties; @PostConstruct protected void initialize() throws Exception { - boolean initialize = this.datasourceProperties.getProperty("initialize", - Boolean.class, true); + boolean initialize = this.properties.isInitialize(); if (this.dataSource == null || !initialize) { logger.debug("No DataSource found so not initializing"); return; } - String schema = this.datasourceProperties.getProperty("schema"); + String schema = this.properties.getSchema(); if (schema == null) { - String platform = this.datasourceProperties.getProperty("platform", "all"); + String platform = this.properties.getPlatform(); schema = "classpath*:schema-" + platform + ".sql,"; schema += "classpath*:schema.sql,"; schema += "classpath*:data-" + platform + ".sql,"; @@ -109,8 +102,7 @@ public class DataSourceAutoConfiguration implements EnvironmentAware { .getResources(schemaLocation))); } - boolean continueOnError = this.datasourceProperties.getProperty( - "continueOnError", Boolean.class, false); + boolean continueOnError = this.properties.isContinueOnError(); boolean exists = false; ResourceDatabasePopulator populator = new ResourceDatabasePopulator(); for (Resource resource : resources) { @@ -120,7 +112,7 @@ public class DataSourceAutoConfiguration implements EnvironmentAware { populator.setContinueOnError(continueOnError); } } - populator.setSeparator(this.datasourceProperties.getProperty("separator", ";")); + populator.setSeparator(this.properties.getSeparator()); if (exists) { DatabasePopulatorUtils.execute(populator, this.dataSource); @@ -151,24 +143,26 @@ public class DataSourceAutoConfiguration implements EnvironmentAware { } - @Conditional(DataSourceAutoConfiguration.TomcatDatabaseCondition.class) + @Conditional(DataSourceAutoConfiguration.NonEmbeddedDatabaseCondition.class) @ConditionalOnMissingBean(DataSource.class) - @Import(TomcatDataSourceConfiguration.class) - protected static class TomcatConfiguration { + protected static class NonEmbeddedConfiguration { - } + @Autowired + private DataSourceProperties properties; - @Conditional(DataSourceAutoConfiguration.HikariDatabaseCondition.class) - @ConditionalOnMissingBean(DataSource.class) - @Import(HikariDataSourceConfiguration.class) - protected static class HikariConfiguration { - - } - - @Conditional(DataSourceAutoConfiguration.BasicDatabaseCondition.class) - @ConditionalOnMissingBean(DataSource.class) - @Import(CommonsDataSourceConfiguration.class) - protected static class DbcpConfiguration { + @ConfigurationProperties(prefix = DataSourceAutoConfiguration.CONFIGURATION_PREFIX) + @Bean + public DataSource dataSource() { + // @formatter:off + DataSourceFactory factory = DataSourceFactory + .create(this.properties.getClassLoader()) + .driverClassName(this.properties.getDriverClassName()) + .url(this.properties.getUrl()) + .username(this.properties.getUsername()) + .password(this.properties.getPassword()); + // @formatter:on + return factory.build(); + } } @@ -196,35 +190,18 @@ public class DataSourceAutoConfiguration implements EnvironmentAware { /** * Base {@link Condition} for non-embedded database checks. */ - static abstract class NonEmbeddedDatabaseCondition extends SpringBootCondition { - - protected abstract String getDataSourceClassName(); + static class NonEmbeddedDatabaseCondition extends SpringBootCondition { @Override public ConditionOutcome getMatchOutcome(ConditionContext context, AnnotatedTypeMetadata metadata) { - if (!ClassUtils.isPresent(getDataSourceClassName(), context.getClassLoader())) { - return ConditionOutcome.noMatch(getDataSourceClassName() - + " DataSource class not found"); - } - - String driverClassName = getDriverClassName(context.getEnvironment(), - getDataSourceClassLoader(context)); - if (driverClassName == null) { - return ConditionOutcome.noMatch("no database driver"); - } - - String url = getUrl(context.getEnvironment(), context.getClassLoader()); - if (url == null) { - return ConditionOutcome.noMatch("no database URL"); - } - - if (ClassUtils.isPresent(driverClassName, context.getClassLoader())) { - return ConditionOutcome.match("found database driver " + driverClassName); + ClassLoader dataSourceClassLoader = getDataSourceClassLoader(context); + if (dataSourceClassLoader != null) { + return ConditionOutcome.match("Supported DataSource class found"); } - return ConditionOutcome.noMatch("missing database driver " + driverClassName); + return ConditionOutcome.noMatch("missing supported DataSource"); } /** @@ -232,94 +209,12 @@ public class DataSourceAutoConfiguration implements EnvironmentAware { * the driver class can actually be loaded by the data source. */ private ClassLoader getDataSourceClassLoader(ConditionContext context) { - try { - Class dataSourceClass = ClassUtils.forName(getDataSourceClassName(), - context.getClassLoader()); - return dataSourceClass.getClassLoader(); - } - catch (ClassNotFoundException ex) { - throw new IllegalStateException(ex); - } - } - - private String getDriverClassName(Environment environment, ClassLoader classLoader) { - String driverClassName = environment == null ? null : environment - .getProperty(CONFIGURATION_PREFIX + ".driverClassName"); - if (driverClassName == null) { - driverClassName = EmbeddedDatabaseConnection.get(classLoader) - .getDriverClassName(); - } - return driverClassName; - } - - private String getUrl(Environment environment, ClassLoader classLoader) { - String url = (environment == null ? null : environment - .getProperty(CONFIGURATION_PREFIX + ".url")); - if (url == null) { - url = EmbeddedDatabaseConnection.get(classLoader).getUrl(); - } - return url; - } - - } - - /** - * {@link Condition} to detect when a commons-dbcp {@code BasicDataSource} backed - * database is used. - */ - static class BasicDatabaseCondition extends NonEmbeddedDatabaseCondition { - - private final Condition hikariCondition = new HikariDatabaseCondition(); - - private final Condition tomcatCondition = new TomcatDatabaseCondition(); - - @Override - protected String getDataSourceClassName() { - return "org.apache.commons.dbcp.BasicDataSource"; - } - - @Override - public ConditionOutcome getMatchOutcome(ConditionContext context, - AnnotatedTypeMetadata metadata) { - if (anyMatches(context, metadata, this.hikariCondition, this.tomcatCondition)) { - return ConditionOutcome.noMatch("other DataSource"); - } - return super.getMatchOutcome(context, metadata); - } - - } - - /** - * {@link Condition} to detect when a Hikari DataSource backed database is used. - */ - static class HikariDatabaseCondition extends NonEmbeddedDatabaseCondition { - - private final Condition tomcatCondition = new TomcatDatabaseCondition(); - - @Override - protected String getDataSourceClassName() { - return "com.zaxxer.hikari.HikariDataSource"; - } - - @Override - public ConditionOutcome getMatchOutcome(ConditionContext context, - AnnotatedTypeMetadata metadata) { - if (anyMatches(context, metadata, this.tomcatCondition)) { - return ConditionOutcome.noMatch("Tomcat DataSource"); + Class dataSourceClass = new DataSourceFactory(context.getClassLoader()) + .findType(); + if (dataSourceClass == null) { + return null; } - return super.getMatchOutcome(context, metadata); - } - - } - - /** - * {@link Condition} to detect when a Tomcat DataSource backed database is used. - */ - static class TomcatDatabaseCondition extends NonEmbeddedDatabaseCondition { - - @Override - protected String getDataSourceClassName() { - return "org.apache.tomcat.jdbc.pool.DataSource"; + return dataSourceClass.getClassLoader(); } } @@ -329,17 +224,12 @@ public class DataSourceAutoConfiguration implements EnvironmentAware { */ static class EmbeddedDatabaseCondition extends SpringBootCondition { - private final SpringBootCondition hikariCondition = new HikariDatabaseCondition(); - - private final SpringBootCondition tomcatCondition = new TomcatDatabaseCondition(); - - private final SpringBootCondition dbcpCondition = new BasicDatabaseCondition(); + private final SpringBootCondition nonEmbedded = new NonEmbeddedDatabaseCondition(); @Override public ConditionOutcome getMatchOutcome(ConditionContext context, AnnotatedTypeMetadata metadata) { - if (anyMatches(context, metadata, this.hikariCondition, this.tomcatCondition, - this.dbcpCondition)) { + if (anyMatches(context, metadata, this.nonEmbedded)) { return ConditionOutcome .noMatch("existing non-embedded database detected"); } @@ -358,11 +248,7 @@ public class DataSourceAutoConfiguration implements EnvironmentAware { */ static class DatabaseCondition extends SpringBootCondition { - private final SpringBootCondition hikariCondition = new HikariDatabaseCondition(); - - private final SpringBootCondition tomcatCondition = new TomcatDatabaseCondition(); - - private final SpringBootCondition dbcpCondition = new BasicDatabaseCondition(); + private final SpringBootCondition nonEmbedded = new NonEmbeddedDatabaseCondition(); private final SpringBootCondition embeddedCondition = new EmbeddedDatabaseCondition(); @@ -370,8 +256,7 @@ public class DataSourceAutoConfiguration implements EnvironmentAware { public ConditionOutcome getMatchOutcome(ConditionContext context, AnnotatedTypeMetadata metadata) { - if (anyMatches(context, metadata, this.hikariCondition, this.tomcatCondition, - this.dbcpCondition, this.embeddedCondition)) { + if (anyMatches(context, metadata, this.nonEmbedded, this.embeddedCondition)) { return ConditionOutcome.match("existing auto database detected"); } diff --git a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jdbc/DataSourceFactory.java b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jdbc/DataSourceFactory.java new file mode 100644 index 0000000000..139691b5f3 --- /dev/null +++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jdbc/DataSourceFactory.java @@ -0,0 +1,133 @@ +/* + * Copyright 2012-2013 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 + * + * http://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.jdbc; + +import java.util.HashMap; +import java.util.Map; + +import javax.sql.DataSource; + +import org.springframework.beans.BeanUtils; +import org.springframework.beans.MutablePropertyValues; +import org.springframework.beans.PropertyValues; +import org.springframework.boot.bind.RelaxedDataBinder; +import org.springframework.util.ClassUtils; + +/** + * Convenience class for building a {@link DataSource} with common implementations and + * properties. If Tomcat, HikariCP or Commons DBCP are on the classpath one of them will + * be selected (in that order with Tomcat 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 + * @ConfigurationProperties. + * + * @author Dave Syer + */ +public class DataSourceFactory { + + private static final String[] DATA_SOURCE_TYPE_NAMES = new String[] { + "org.apache.tomcat.jdbc.pool.DataSource", + "com.zaxxer.hikari.HikariDataSource", + "org.apache.commons.dbcp.BasicDataSource" }; + + private Class type; + + private ClassLoader classLoader; + + private Map properties = new HashMap(); + + public static DataSourceFactory create() { + return new DataSourceFactory(null); + } + + public static DataSourceFactory create(ClassLoader classLoader) { + return new DataSourceFactory(classLoader); + } + + public DataSourceFactory(ClassLoader classLoader) { + this.classLoader = classLoader; + } + + public DataSource build() { + Class type = getType(); + DataSource result = BeanUtils.instantiate(type); + bind(result); + return result; + } + + private void bind(DataSource result) { + new RelaxedDataBinder(result).bind(getPropertyValues()); + } + + private PropertyValues getPropertyValues() { + if (getType().getName().contains("Hikari") && this.properties.containsKey("url")) { + this.properties.put("jdbcUrl", this.properties.get("url")); + this.properties.remove("url"); + } + return new MutablePropertyValues(this.properties); + } + + public DataSourceFactory type(Class type) { + this.type = type; + return this; + } + + public DataSourceFactory url(String url) { + this.properties.put("url", url); + return this; + } + + public DataSourceFactory driverClassName(String driverClassName) { + this.properties.put("driverClassName", driverClassName); + return this; + } + + public DataSourceFactory username(String username) { + this.properties.put("username", username); + return this; + } + + public DataSourceFactory password(String password) { + this.properties.put("password", password); + return this; + } + + public Class findType() { + if (this.type != null) { + return this.type; + } + for (String name : DATA_SOURCE_TYPE_NAMES) { + if (ClassUtils.isPresent(name, this.classLoader)) { + @SuppressWarnings("unchecked") + Class resolved = (Class) ClassUtils + .resolveClassName(name, this.classLoader); + return resolved; + } + } + return null; + } + + private Class getType() { + Class type = findType(); + if (type != null) { + return type; + } + throw new IllegalStateException("No supported DataSource type found"); + } + +} diff --git a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jdbc/AbstractDataSourceConfiguration.java b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jdbc/DataSourceProperties.java similarity index 56% rename from spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jdbc/AbstractDataSourceConfiguration.java rename to spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jdbc/DataSourceProperties.java index 87c85c8a2f..5be2e7e5e6 100644 --- a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jdbc/AbstractDataSourceConfiguration.java +++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jdbc/DataSourceProperties.java @@ -20,7 +20,6 @@ import org.springframework.beans.factory.BeanClassLoaderAware; import org.springframework.beans.factory.BeanCreationException; import org.springframework.beans.factory.InitializingBean; import org.springframework.boot.context.properties.ConfigurationProperties; -import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.util.StringUtils; /** @@ -29,9 +28,7 @@ import org.springframework.util.StringUtils; * @author Dave Syer */ @ConfigurationProperties(prefix = DataSourceAutoConfiguration.CONFIGURATION_PREFIX) -@EnableConfigurationProperties -public abstract class AbstractDataSourceConfiguration implements BeanClassLoaderAware, - InitializingBean { +public class DataSourceProperties implements BeanClassLoaderAware, InitializingBean { private String driverClassName; @@ -41,29 +38,17 @@ public abstract class AbstractDataSourceConfiguration implements BeanClassLoader private String password; - private int maxActive = 100; - - private int maxIdle = 8; - - private int minIdle = 8; - - private int initialSize = 10; - - private String validationQuery; - - private boolean testOnBorrow; - - private boolean testOnReturn; + private ClassLoader classLoader; - private boolean testWhileIdle; + private boolean initialize = true; - private Integer timeBetweenEvictionRunsMillis; + private String platform = "all"; - private Integer minEvictableIdleTimeMillis; + private String schema; - private Integer maxWaitMillis; + private boolean continueOnError = false; - private ClassLoader classLoader; + private String separator = ";"; private EmbeddedDatabaseConnection embeddedDatabaseConnection = EmbeddedDatabaseConnection.NONE; @@ -78,7 +63,7 @@ public abstract class AbstractDataSourceConfiguration implements BeanClassLoader .get(this.classLoader); } - protected String getDriverClassName() { + public String getDriverClassName() { if (StringUtils.hasText(this.driverClassName)) { return this.driverClassName; } @@ -93,7 +78,7 @@ public abstract class AbstractDataSourceConfiguration implements BeanClassLoader return driverClassName; } - protected String getUrl() { + public String getUrl() { if (StringUtils.hasText(this.url)) { return this.url; } @@ -108,7 +93,7 @@ public abstract class AbstractDataSourceConfiguration implements BeanClassLoader return url; } - protected String getUsername() { + public String getUsername() { if (StringUtils.hasText(this.username)) { return this.username; } @@ -118,7 +103,7 @@ public abstract class AbstractDataSourceConfiguration implements BeanClassLoader return null; } - protected String getPassword() { + public String getPassword() { if (StringUtils.hasText(this.password)) { return this.password; } @@ -132,10 +117,6 @@ public abstract class AbstractDataSourceConfiguration implements BeanClassLoader this.driverClassName = driverClassName; } - public void setInitialSize(int initialSize) { - this.initialSize = initialSize; - } - public void setUrl(String url) { this.url = url; } @@ -148,88 +129,48 @@ public abstract class AbstractDataSourceConfiguration implements BeanClassLoader this.password = password; } - public void setMaxActive(int maxActive) { - this.maxActive = maxActive; - } - - public void setMaxIdle(int maxIdle) { - this.maxIdle = maxIdle; - } - - public void setMinIdle(int minIdle) { - this.minIdle = minIdle; - } - - public void setValidationQuery(String validationQuery) { - this.validationQuery = validationQuery; - } - - public void setTestOnBorrow(boolean testOnBorrow) { - this.testOnBorrow = testOnBorrow; - } - - public void setTestOnReturn(boolean testOnReturn) { - this.testOnReturn = testOnReturn; - } - - public void setTestWhileIdle(boolean testWhileIdle) { - this.testWhileIdle = testWhileIdle; - } - - public void setTimeBetweenEvictionRunsMillis(int timeBetweenEvictionRunsMillis) { - this.timeBetweenEvictionRunsMillis = timeBetweenEvictionRunsMillis; - } - - public void setMinEvictableIdleTimeMillis(int minEvictableIdleTimeMillis) { - this.minEvictableIdleTimeMillis = minEvictableIdleTimeMillis; - } - - public void setMaxWait(int maxWaitMillis) { - this.maxWaitMillis = maxWaitMillis; - } - - public int getInitialSize() { - return this.initialSize; + public boolean isInitialize() { + return this.initialize; } - protected int getMaxActive() { - return this.maxActive; + public void setInitialize(boolean initialize) { + this.initialize = initialize; } - protected int getMaxIdle() { - return this.maxIdle; + public String getPlatform() { + return this.platform; } - protected int getMinIdle() { - return this.minIdle; + public void setPlatform(String platform) { + this.platform = platform; } - protected String getValidationQuery() { - return this.validationQuery; + public String getSchema() { + return this.schema; } - protected boolean isTestOnBorrow() { - return this.testOnBorrow; + public void setSchema(String schema) { + this.schema = schema; } - protected boolean isTestOnReturn() { - return this.testOnReturn; + public boolean isContinueOnError() { + return this.continueOnError; } - protected boolean isTestWhileIdle() { - return this.testWhileIdle; + public void setContinueOnError(boolean continueOnError) { + this.continueOnError = continueOnError; } - protected Integer getTimeBetweenEvictionRunsMillis() { - return this.timeBetweenEvictionRunsMillis; + public String getSeparator() { + return this.separator; } - protected Integer getMinEvictableIdleTimeMillis() { - return this.minEvictableIdleTimeMillis; + public void setSeparator(String separator) { + this.separator = separator; } - protected Integer getMaxWaitMillis() { - return this.maxWaitMillis; + public ClassLoader getClassLoader() { + return this.classLoader; } } diff --git a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jdbc/HikariDataSourceConfiguration.java b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jdbc/HikariDataSourceConfiguration.java deleted file mode 100644 index 00fa795350..0000000000 --- a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jdbc/HikariDataSourceConfiguration.java +++ /dev/null @@ -1,125 +0,0 @@ -/* - * Copyright 2012-2014 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 - * - * http://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.jdbc; - -import java.util.Properties; - -import javax.annotation.PreDestroy; -import javax.sql.DataSource; - -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.util.StringUtils; - -import com.zaxxer.hikari.HikariDataSource; - -/** - * Configuration for a HikariCP database pool. The HikariCP pool is a popular data source - * implementation that provides high performance as well as some useful opinionated - * defaults. For compatibility with other DataSource implementations accepts configuration - * via properties in "spring.datasource.*", e.g. "url", "driverClassName", "username", - * "password" (and some others but the full list supported by the Tomcat pool is not - * applicable). Note that the Hikari team recommends using a "dataSourceClassName" and a - * Properties instance (specified here as "spring.datasource.hikari.*"). This makes the - * binding potentially vendor specific, but gives you full control of all the native - * features in the vendor's DataSource. - * - * @author Dave Syer - * @see DataSourceAutoConfiguration - * @since 1.1.0 - */ -@Configuration -public class HikariDataSourceConfiguration extends AbstractDataSourceConfiguration { - - private String dataSourceClassName; - - private String username; - - private HikariDataSource pool; - - private Properties hikari = new Properties(); - - @Bean(destroyMethod = "shutdown") - public DataSource dataSource() { - this.pool = new HikariDataSource(); - if (this.dataSourceClassName == null) { - this.pool.setDriverClassName(getDriverClassName()); - } - else { - this.pool.setDataSourceClassName(this.dataSourceClassName); - this.pool.setDataSourceProperties(this.hikari); - } - this.pool.setJdbcUrl(getUrl()); - if (getUsername() != null) { - this.pool.setUsername(getUsername()); - } - if (getPassword() != null) { - this.pool.setPassword(getPassword()); - } - this.pool.setMaximumPoolSize(getMaxActive()); - this.pool.setMinimumIdle(getMinIdle()); - if (isTestOnBorrow()) { - this.pool.setConnectionInitSql(getValidationQuery()); - } - else { - this.pool.setConnectionTestQuery(getValidationQuery()); - } - if (getMaxWaitMillis() != null) { - this.pool.setMaxLifetime(getMaxWaitMillis()); - } - return this.pool; - } - - @PreDestroy - public void close() { - if (this.pool != null) { - this.pool.close(); - } - } - - /** - * @param dataSourceClassName the dataSourceClassName to set - */ - public void setDataSourceClassName(String dataSourceClassName) { - this.dataSourceClassName = dataSourceClassName; - } - - @Override - public void setUsername(String username) { - this.username = username; - } - - /** - * @return the hikari data source properties - */ - public Properties getHikari() { - return this.hikari; - } - - @Override - protected String getUsername() { - if (StringUtils.hasText(this.username)) { - return this.username; - } - if (this.dataSourceClassName == null - && EmbeddedDatabaseConnection.isEmbedded(getDriverClassName())) { - return "sa"; - } - return null; - } - -} diff --git a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jdbc/RelaxedDataSourceFactory.java b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jdbc/RelaxedDataSourceFactory.java deleted file mode 100644 index 81fb1715a4..0000000000 --- a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jdbc/RelaxedDataSourceFactory.java +++ /dev/null @@ -1,78 +0,0 @@ -/* - * Copyright 2012-2013 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 - * - * http://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.jdbc; - -import javax.sql.DataSource; - -import org.springframework.beans.BeanUtils; -import org.springframework.boot.bind.PropertySourcesPropertyValues; -import org.springframework.boot.bind.RelaxedDataBinder; -import org.springframework.core.env.ConfigurableEnvironment; -import org.springframework.util.ClassUtils; - -/** - * @author Dave Syer - */ -public class RelaxedDataSourceFactory { - - private static final String[] DATA_SOURCE_TYPE_NAMES = new String[] { - "com.zaxxer.hikari.HikariDataSource", - "org.apache.tomcat.jdbc.pool.DataSource", - "org.apache.commons.dbcp.BasicDataSource" }; - - private Class type; - - private ConfigurableEnvironment environment; - - public static RelaxedDataSourceFactory create(ConfigurableEnvironment environment) { - return new RelaxedDataSourceFactory(environment); - } - - public RelaxedDataSourceFactory(ConfigurableEnvironment environment) { - this.environment = environment; - } - - public DataSource build(String prefix) { - Class type = getType(); - DataSource result = BeanUtils.instantiate(type); - RelaxedDataBinder binder = new RelaxedDataBinder(result, prefix); - binder.bind(new PropertySourcesPropertyValues(this.environment - .getPropertySources())); - return result; - } - - public RelaxedDataSourceFactory type(Class type) { - this.type = type; - return this; - } - - private Class getType() { - if (this.type != null) { - return this.type; - } - for (String name : DATA_SOURCE_TYPE_NAMES) { - if (ClassUtils.isPresent(name, null)) { - @SuppressWarnings("unchecked") - Class resolved = (Class) ClassUtils - .resolveClassName(name, null); - return resolved; - } - } - throw new IllegalStateException("No supported DataSource type found"); - } - -} diff --git a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jdbc/TomcatDataSourceConfiguration.java b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jdbc/TomcatDataSourceConfiguration.java deleted file mode 100644 index 49e2523992..0000000000 --- a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jdbc/TomcatDataSourceConfiguration.java +++ /dev/null @@ -1,89 +0,0 @@ -/* - * Copyright 2012-2014 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 - * - * http://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.jdbc; - -import javax.annotation.PreDestroy; -import javax.sql.DataSource; - -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; - -/** - * Configuration for a Tomcat database pool. The Tomcat pool provides superior performance - * and tends not to deadlock in high volume environments. - * - * @author Dave Syer - * @see DataSourceAutoConfiguration - */ -@Configuration -public class TomcatDataSourceConfiguration extends AbstractDataSourceConfiguration { - - private String jdbcInterceptors; - private long validationInterval = 30000; - private org.apache.tomcat.jdbc.pool.DataSource pool; - - @Bean(destroyMethod = "close") - public DataSource dataSource() { - this.pool = new org.apache.tomcat.jdbc.pool.DataSource(); - this.pool.setDriverClassName(getDriverClassName()); - this.pool.setUrl(getUrl()); - if (getUsername() != null) { - this.pool.setUsername(getUsername()); - } - if (getPassword() != null) { - this.pool.setPassword(getPassword()); - } - this.pool.setInitialSize(getInitialSize()); - this.pool.setMaxActive(getMaxActive()); - this.pool.setMaxIdle(getMaxIdle()); - this.pool.setMinIdle(getMinIdle()); - this.pool.setTestOnBorrow(isTestOnBorrow()); - this.pool.setTestOnReturn(isTestOnReturn()); - this.pool.setTestWhileIdle(isTestWhileIdle()); - if (getTimeBetweenEvictionRunsMillis() != null) { - this.pool - .setTimeBetweenEvictionRunsMillis(getTimeBetweenEvictionRunsMillis()); - } - if (getMinEvictableIdleTimeMillis() != null) { - this.pool.setMinEvictableIdleTimeMillis(getMinEvictableIdleTimeMillis()); - } - this.pool.setValidationQuery(getValidationQuery()); - this.pool.setValidationInterval(this.validationInterval); - if (getMaxWaitMillis() != null) { - this.pool.setMaxWait(getMaxWaitMillis()); - } - if (this.jdbcInterceptors != null) { - this.pool.setJdbcInterceptors(this.jdbcInterceptors); - } - return this.pool; - } - - @PreDestroy - public void close() { - if (this.pool != null) { - this.pool.close(); - } - } - - public void setJdbcInterceptors(String jdbcInterceptors) { - this.jdbcInterceptors = jdbcInterceptors; - } - - public void setValidationInterval(long validationInterval) { - this.validationInterval = validationInterval; - } -} diff --git a/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/jdbc/CommonsDataSourceConfigurationTests.java b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/jdbc/CommonsDataSourceConfigurationTests.java index 2cdb745732..a94fe4841a 100644 --- a/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/jdbc/CommonsDataSourceConfigurationTests.java +++ b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/jdbc/CommonsDataSourceConfigurationTests.java @@ -21,8 +21,12 @@ import javax.sql.DataSource; import org.apache.commons.dbcp.BasicDataSource; import org.apache.commons.pool.impl.GenericObjectPool; import org.junit.Test; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.boot.test.EnvironmentTestUtils; import org.springframework.context.annotation.AnnotationConfigApplicationContext; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; @@ -83,4 +87,16 @@ public class CommonsDataSourceConfigurationTests { assertEquals(GenericObjectPool.DEFAULT_MAX_WAIT, ds.getMaxWait()); } + @Configuration + @EnableConfigurationProperties + protected static class CommonsDataSourceConfiguration { + + @Bean + @ConfigurationProperties(prefix = DataSourceAutoConfiguration.CONFIGURATION_PREFIX) + public DataSource dataSource() { + return DataSourceFactory.create().type(BasicDataSource.class).build(); + } + + } + } diff --git a/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/jdbc/DataSourceAutoConfigurationTests.java b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/jdbc/DataSourceAutoConfigurationTests.java index 885aa73cd3..108d80d43d 100644 --- a/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/jdbc/DataSourceAutoConfigurationTests.java +++ b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/jdbc/DataSourceAutoConfigurationTests.java @@ -23,8 +23,6 @@ import java.sql.Driver; import java.sql.DriverPropertyInfo; import java.sql.SQLException; import java.sql.SQLFeatureNotSupportedException; -import java.util.HashMap; -import java.util.Map; import java.util.Properties; import java.util.Random; import java.util.logging.Logger; @@ -32,15 +30,16 @@ import java.util.logging.Logger; import javax.sql.DataSource; import org.apache.commons.dbcp.BasicDataSource; +import org.junit.After; import org.junit.Before; import org.junit.Test; import org.mockito.Mockito; +import org.springframework.beans.factory.BeanCreationException; import org.springframework.boot.autoconfigure.PropertyPlaceholderAutoConfiguration; import org.springframework.boot.test.EnvironmentTestUtils; import org.springframework.context.annotation.AnnotationConfigApplicationContext; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; -import org.springframework.core.env.MapPropertySource; import org.springframework.jdbc.core.JdbcOperations; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.jdbc.core.namedparam.NamedParameterJdbcOperations; @@ -64,11 +63,17 @@ public class DataSourceAutoConfigurationTests { @Before public void init() { + EmbeddedDatabaseConnection.override = null; EnvironmentTestUtils.addEnvironment(this.context, "spring.datasource.initialize:false", "spring.datasource.url:jdbc:hsqldb:mem:testdb-" + new Random().nextInt()); } + @After + public void restore() { + EmbeddedDatabaseConnection.override = null; + } + @Test public void testDefaultDataSourceExists() throws Exception { this.context.register(DataSourceAutoConfiguration.class, @@ -78,7 +83,36 @@ public class DataSourceAutoConfigurationTests { } @Test - public void testTomcatIsFallback() throws Exception { + public void testDataSourceUrlHasEmbeddedDefault() throws Exception { + this.context.register(DataSourceAutoConfiguration.class, + PropertyPlaceholderAutoConfiguration.class); + this.context.refresh(); + org.apache.tomcat.jdbc.pool.DataSource dataSource = this.context + .getBean(org.apache.tomcat.jdbc.pool.DataSource.class); + assertNotNull(dataSource.getUrl()); + assertNotNull(dataSource.getDriverClassName()); + } + + @Test(expected = BeanCreationException.class) + public void testBadUrl() throws Exception { + EmbeddedDatabaseConnection.override = EmbeddedDatabaseConnection.NONE; + this.context.register(DataSourceAutoConfiguration.class, + PropertyPlaceholderAutoConfiguration.class); + this.context.refresh(); + assertNotNull(this.context.getBean(DataSource.class)); + } + + @Test(expected = BeanCreationException.class) + public void testBadDriverClass() throws Exception { + EmbeddedDatabaseConnection.override = EmbeddedDatabaseConnection.NONE; + this.context.register(DataSourceAutoConfiguration.class, + PropertyPlaceholderAutoConfiguration.class); + this.context.refresh(); + assertNotNull(this.context.getBean(DataSource.class)); + } + + @Test + public void testHikariIsFallback() throws Exception { EnvironmentTestUtils.addEnvironment(this.context, "spring.datasource.driverClassName:org.hsqldb.jdbcDriver", "spring.datasource.url:jdbc:hsqldb:mem:testdb"); @@ -194,11 +228,12 @@ public class DataSourceAutoConfigurationTests { public void testDataSourceInitializedWithExplicitScript() throws Exception { this.context.register(DataSourceAutoConfiguration.class, PropertyPlaceholderAutoConfiguration.class); - Map map = new HashMap(); - map.put(DataSourceAutoConfiguration.CONFIGURATION_PREFIX + ".schema", - ClassUtils.addResourcePathToPackagePath(getClass(), "schema.sql")); - this.context.getEnvironment().getPropertySources() - .addFirst(new MapPropertySource("test", map)); + EnvironmentTestUtils.addEnvironment( + this.context, + "spring.datasource.initialize:true", + "spring.datasource.schema:" + + ClassUtils.addResourcePathToPackagePath(getClass(), + "schema.sql")); this.context.refresh(); DataSource dataSource = this.context.getBean(DataSource.class); assertTrue(dataSource instanceof org.apache.tomcat.jdbc.pool.DataSource); diff --git a/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/jdbc/HikariDataSourceConfigurationTests.java b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/jdbc/HikariDataSourceConfigurationTests.java index 6593a8b46b..7afe94aec9 100644 --- a/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/jdbc/HikariDataSourceConfigurationTests.java +++ b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/jdbc/HikariDataSourceConfigurationTests.java @@ -22,10 +22,12 @@ import javax.sql.DataSource; import org.junit.After; import org.junit.Test; -import org.springframework.beans.factory.BeanCreationException; -import org.springframework.boot.autoconfigure.PropertyPlaceholderAutoConfiguration; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.boot.test.EnvironmentTestUtils; import org.springframework.context.annotation.AnnotationConfigApplicationContext; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; import org.springframework.util.ReflectionUtils; import com.zaxxer.hikari.HikariDataSource; @@ -61,8 +63,8 @@ public class HikariDataSourceConfigurationTests { public void testDataSourcePropertiesOverridden() throws Exception { this.context.register(HikariDataSourceConfiguration.class); EnvironmentTestUtils.addEnvironment(this.context, PREFIX - + "url:jdbc:foo//bar/spam"); - EnvironmentTestUtils.addEnvironment(this.context, PREFIX + "maxWait:1234"); + + "jdbcUrl:jdbc:foo//bar/spam"); + EnvironmentTestUtils.addEnvironment(this.context, PREFIX + "maxLifetime:1234"); this.context.refresh(); HikariDataSource ds = this.context.getBean(HikariDataSource.class); assertEquals("jdbc:foo//bar/spam", ds.getJdbcUrl()); @@ -74,11 +76,11 @@ public class HikariDataSourceConfigurationTests { public void testDataSourceGenericPropertiesOverridden() throws Exception { this.context.register(HikariDataSourceConfiguration.class); EnvironmentTestUtils.addEnvironment(this.context, PREFIX - + "hikari.databaseName:foo", PREFIX - + "dataSourceClassName:org.h2.JDBCDataSource"); + + "dataSourceProperties.dataSourceClassName:org.h2.JDBCDataSource"); this.context.refresh(); HikariDataSource ds = this.context.getBean(HikariDataSource.class); - assertEquals("foo", ds.getDataSourceProperties().getProperty("databaseName")); + assertEquals("org.h2.JDBCDataSource", + ds.getDataSourceProperties().getProperty("dataSourceClassName")); } @Test @@ -89,28 +91,23 @@ public class HikariDataSourceConfigurationTests { assertEquals(1800000, ds.getMaxLifetime()); } - @Test(expected = BeanCreationException.class) - public void testBadUrl() throws Exception { - EmbeddedDatabaseConnection.override = EmbeddedDatabaseConnection.NONE; - this.context.register(HikariDataSourceConfiguration.class, - PropertyPlaceholderAutoConfiguration.class); - this.context.refresh(); - assertNotNull(this.context.getBean(DataSource.class)); - } - - @Test(expected = BeanCreationException.class) - public void testBadDriverClass() throws Exception { - EmbeddedDatabaseConnection.override = EmbeddedDatabaseConnection.NONE; - this.context.register(HikariDataSourceConfiguration.class, - PropertyPlaceholderAutoConfiguration.class); - this.context.refresh(); - assertNotNull(this.context.getBean(DataSource.class)); - } - @SuppressWarnings("unchecked") public static T getField(Class target, String name) { Field field = ReflectionUtils.findField(target, name, null); ReflectionUtils.makeAccessible(field); return (T) ReflectionUtils.getField(field, target); } + + @Configuration + @EnableConfigurationProperties + protected static class HikariDataSourceConfiguration { + + @Bean + @ConfigurationProperties(prefix = DataSourceAutoConfiguration.CONFIGURATION_PREFIX) + public DataSource dataSource() { + return DataSourceFactory.create().type(HikariDataSource.class).build(); + } + + } + } diff --git a/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/jdbc/TomcatDataSourceConfigurationTests.java b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/jdbc/TomcatDataSourceConfigurationTests.java index 720bf3e240..5820a20cc3 100644 --- a/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/jdbc/TomcatDataSourceConfigurationTests.java +++ b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/jdbc/TomcatDataSourceConfigurationTests.java @@ -24,11 +24,14 @@ import org.apache.tomcat.jdbc.pool.DataSourceProxy; import org.apache.tomcat.jdbc.pool.PoolProperties; import org.apache.tomcat.jdbc.pool.interceptor.SlowQueryReport; import org.junit.After; +import org.junit.Before; import org.junit.Test; -import org.springframework.beans.factory.BeanCreationException; -import org.springframework.boot.autoconfigure.PropertyPlaceholderAutoConfiguration; +import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.boot.test.EnvironmentTestUtils; import org.springframework.context.annotation.AnnotationConfigApplicationContext; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Import; import org.springframework.util.ReflectionUtils; import static org.junit.Assert.assertEquals; @@ -47,6 +50,11 @@ public class TomcatDataSourceConfigurationTests { private final AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(); + @Before + public void init() { + EnvironmentTestUtils.addEnvironment(this.context, PREFIX + "initialize:false"); + } + @After public void restore() { EmbeddedDatabaseConnection.override = null; @@ -115,28 +123,24 @@ public class TomcatDataSourceConfigurationTests { assertEquals(30000L, ds.getValidationInterval()); } - @Test(expected = BeanCreationException.class) - public void testBadUrl() throws Exception { - EmbeddedDatabaseConnection.override = EmbeddedDatabaseConnection.NONE; - this.context.register(TomcatDataSourceConfiguration.class, - PropertyPlaceholderAutoConfiguration.class); - this.context.refresh(); - assertNotNull(this.context.getBean(DataSource.class)); - } - - @Test(expected = BeanCreationException.class) - public void testBadDriverClass() throws Exception { - EmbeddedDatabaseConnection.override = EmbeddedDatabaseConnection.NONE; - this.context.register(TomcatDataSourceConfiguration.class, - PropertyPlaceholderAutoConfiguration.class); - this.context.refresh(); - assertNotNull(this.context.getBean(DataSource.class)); - } - @SuppressWarnings("unchecked") public static T getField(Class target, String name) { Field field = ReflectionUtils.findField(target, name, null); ReflectionUtils.makeAccessible(field); return (T) ReflectionUtils.getField(field, target); } + + @Configuration + @Import(DataSourceAutoConfiguration.class) + protected static class TomcatDataSourceConfiguration { + + @Bean + @ConfigurationProperties(prefix = DataSourceAutoConfiguration.CONFIGURATION_PREFIX) + public DataSource dataSource() { + return DataSourceFactory.create() + .type(org.apache.tomcat.jdbc.pool.DataSource.class).build(); + } + + } + } diff --git a/spring-boot-samples/spring-boot-sample-actuator/build.gradle b/spring-boot-samples/spring-boot-sample-actuator/build.gradle index 451a4964b8..1402d417fd 100644 --- a/spring-boot-samples/spring-boot-sample-actuator/build.gradle +++ b/spring-boot-samples/spring-boot-sample-actuator/build.gradle @@ -40,9 +40,11 @@ dependencies { } compile("org.springframework.boot:spring-boot-starter-actuator") + compile("org.springframework.boot:spring-boot-starter-jdbc") compile("org.springframework.boot:spring-boot-starter-security") compile("org.springframework.boot:spring-boot-starter-web") - + compile("com.h2database:h2") + testCompile("org.springframework.boot:spring-boot-starter-test") insecure configurations.runtime diff --git a/spring-boot-samples/spring-boot-sample-actuator/pom.xml b/spring-boot-samples/spring-boot-sample-actuator/pom.xml index 224106f009..1f4fb2c63d 100644 --- a/spring-boot-samples/spring-boot-sample-actuator/pom.xml +++ b/spring-boot-samples/spring-boot-sample-actuator/pom.xml @@ -31,6 +31,10 @@ org.springframework.boot spring-boot-starter-security + + org.springframework.boot + spring-boot-starter-jdbc + org.springframework.boot spring-boot-starter-remote-shell @@ -39,6 +43,10 @@ org.hibernate hibernate-validator + + com.h2database + h2 + org.springframework.boot spring-boot-starter-test diff --git a/spring-boot-samples/spring-boot-sample-actuator/src/test/java/sample/actuator/EndpointsPropertiesSampleActuatorApplicationTests.java b/spring-boot-samples/spring-boot-sample-actuator/src/test/java/sample/actuator/EndpointsPropertiesSampleActuatorApplicationTests.java index 3301d768a8..c81c2a27f7 100644 --- a/spring-boot-samples/spring-boot-sample-actuator/src/test/java/sample/actuator/EndpointsPropertiesSampleActuatorApplicationTests.java +++ b/spring-boot-samples/spring-boot-sample-actuator/src/test/java/sample/actuator/EndpointsPropertiesSampleActuatorApplicationTests.java @@ -32,6 +32,7 @@ import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; import org.springframework.test.context.web.WebAppConfiguration; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; /** * Integration tests for endpoints configuration. @@ -66,7 +67,6 @@ public class EndpointsPropertiesSampleActuatorApplicationTests { ResponseEntity entity = new TestRestTemplate().getForEntity( "http://localhost:" + this.port + "/admin/health", String.class); assertEquals(HttpStatus.OK, entity.getStatusCode()); - String body = entity.getBody(); - assertEquals("{\"status\":\"ok\"}", body); + assertTrue("Wrong body: " + entity.getBody(), entity.getBody().contains("\"status\":\"ok\"")); } } diff --git a/spring-boot-samples/spring-boot-sample-actuator/src/test/java/sample/actuator/ManagementAddressActuatorApplicationTests.java b/spring-boot-samples/spring-boot-sample-actuator/src/test/java/sample/actuator/ManagementAddressActuatorApplicationTests.java index 0c76153f2c..43e1f63f4b 100644 --- a/spring-boot-samples/spring-boot-sample-actuator/src/test/java/sample/actuator/ManagementAddressActuatorApplicationTests.java +++ b/spring-boot-samples/spring-boot-sample-actuator/src/test/java/sample/actuator/ManagementAddressActuatorApplicationTests.java @@ -33,6 +33,7 @@ import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; import org.springframework.test.context.web.WebAppConfiguration; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; /** * Integration tests for separate management and main service ports. @@ -71,7 +72,7 @@ public class ManagementAddressActuatorApplicationTests { "http://localhost:" + this.managementPort + "/admin/health", String.class); assertEquals(HttpStatus.OK, entity.getStatusCode()); - assertEquals("{\"status\":\"ok\"}", entity.getBody()); + assertTrue("Wrong body: " + entity.getBody(), entity.getBody().contains("\"status\":\"ok\"")); } } diff --git a/spring-boot-samples/spring-boot-sample-actuator/src/test/java/sample/actuator/ManagementPortSampleActuatorApplicationTests.java b/spring-boot-samples/spring-boot-sample-actuator/src/test/java/sample/actuator/ManagementPortSampleActuatorApplicationTests.java index fa70671af6..0fec046a4d 100644 --- a/spring-boot-samples/spring-boot-sample-actuator/src/test/java/sample/actuator/ManagementPortSampleActuatorApplicationTests.java +++ b/spring-boot-samples/spring-boot-sample-actuator/src/test/java/sample/actuator/ManagementPortSampleActuatorApplicationTests.java @@ -33,6 +33,7 @@ import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; import org.springframework.test.context.web.WebAppConfiguration; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; /** * Integration tests for separate management and main service ports. @@ -80,7 +81,7 @@ public class ManagementPortSampleActuatorApplicationTests { ResponseEntity entity = new TestRestTemplate().getForEntity( "http://localhost:" + this.managementPort + "/health", String.class); assertEquals(HttpStatus.OK, entity.getStatusCode()); - assertEquals("{\"status\":\"ok\"}", entity.getBody()); + assertTrue("Wrong body: " + entity.getBody(), entity.getBody().contains("\"status\":\"ok\"")); } @Test diff --git a/spring-boot-samples/spring-boot-sample-actuator/src/test/java/sample/actuator/SampleActuatorApplicationTests.java b/spring-boot-samples/spring-boot-sample-actuator/src/test/java/sample/actuator/SampleActuatorApplicationTests.java index 89f61049e1..32ba32ffbe 100644 --- a/spring-boot-samples/spring-boot-sample-actuator/src/test/java/sample/actuator/SampleActuatorApplicationTests.java +++ b/spring-boot-samples/spring-boot-sample-actuator/src/test/java/sample/actuator/SampleActuatorApplicationTests.java @@ -130,7 +130,7 @@ public class SampleActuatorApplicationTests { ResponseEntity entity = new TestRestTemplate().getForEntity( "http://localhost:" + this.port + "/health", String.class); assertEquals(HttpStatus.OK, entity.getStatusCode()); - assertEquals("{\"status\":\"ok\"}", entity.getBody()); + assertTrue("Wrong body: " + entity.getBody(), entity.getBody().contains("\"status\":\"ok\"")); } @Test