diff --git a/spring-boot-devtools/pom.xml b/spring-boot-devtools/pom.xml index f38d55e3ae..a0633e83d3 100644 --- a/spring-boot-devtools/pom.xml +++ b/spring-boot-devtools/pom.xml @@ -86,6 +86,12 @@ true + + org.springframework.boot + spring-boot + test-jar + test + org.springframework.boot spring-boot-test @@ -141,5 +147,10 @@ thymeleaf-layout-dialect test + + org.apache.tomcat + tomcat-jdbc + test + diff --git a/spring-boot-devtools/src/main/java/org/springframework/boot/devtools/autoconfigure/DevToolsDataSourceAutoConfiguration.java b/spring-boot-devtools/src/main/java/org/springframework/boot/devtools/autoconfigure/DevToolsDataSourceAutoConfiguration.java index 32b1ed6363..b07220db39 100644 --- a/spring-boot-devtools/src/main/java/org/springframework/boot/devtools/autoconfigure/DevToolsDataSourceAutoConfiguration.java +++ b/spring-boot-devtools/src/main/java/org/springframework/boot/devtools/autoconfigure/DevToolsDataSourceAutoConfiguration.java @@ -23,18 +23,24 @@ import java.util.Set; import javax.sql.DataSource; import org.springframework.beans.factory.DisposableBean; +import org.springframework.beans.factory.annotation.AnnotatedBeanDefinition; +import org.springframework.beans.factory.config.BeanDefinition; import org.springframework.boot.autoconfigure.AutoConfigureAfter; import org.springframework.boot.autoconfigure.EnableAutoConfiguration; -import org.springframework.boot.autoconfigure.condition.AllNestedConditions; +import org.springframework.boot.autoconfigure.condition.ConditionOutcome; import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; +import org.springframework.boot.autoconfigure.condition.SpringBootCondition; import org.springframework.boot.autoconfigure.data.jpa.EntityManagerFactoryDependsOnPostProcessor; import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration; import org.springframework.boot.autoconfigure.jdbc.DataSourceProperties; import org.springframework.boot.devtools.autoconfigure.DevToolsDataSourceAutoConfiguration.DevToolsDataSourceCondition; import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.ConditionContext; import org.springframework.context.annotation.Conditional; import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.ConfigurationCondition; +import org.springframework.core.type.AnnotatedTypeMetadata; import org.springframework.jdbc.datasource.embedded.EmbeddedDatabase; import org.springframework.orm.jpa.AbstractEntityManagerFactoryBean; import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean; @@ -109,20 +115,38 @@ public class DevToolsDataSourceAutoConfiguration { } - static class DevToolsDataSourceCondition extends AllNestedConditions { + static class DevToolsDataSourceCondition extends SpringBootCondition + implements ConfigurationCondition { - DevToolsDataSourceCondition() { - super(ConfigurationPhase.REGISTER_BEAN); + @Override + public ConfigurationPhase getConfigurationPhase() { + return ConfigurationPhase.REGISTER_BEAN; } - @ConditionalOnBean(DataSource.class) - static final class DataSourceBean { - - } - - @ConditionalOnBean(DataSourceProperties.class) - static final class DataSourcePropertiesBean { - + @Override + public ConditionOutcome getMatchOutcome(ConditionContext context, + AnnotatedTypeMetadata metadata) { + String[] dataSourceBeanNames = context.getBeanFactory() + .getBeanNamesForType(DataSource.class); + if (dataSourceBeanNames.length != 1) { + return ConditionOutcome + .noMatch("A single DataSource bean was not found in the context"); + } + if (context.getBeanFactory() + .getBeanNamesForType(DataSourceProperties.class).length != 1) { + return ConditionOutcome.noMatch( + "A single DataSourceProperties bean was not found in the context"); + } + BeanDefinition dataSourceDefinition = context.getRegistry() + .getBeanDefinition(dataSourceBeanNames[0]); + if (dataSourceDefinition instanceof AnnotatedBeanDefinition + && ((AnnotatedBeanDefinition) dataSourceDefinition) + .getFactoryMethodMetadata().getDeclaringClassName() + .startsWith(DataSourceAutoConfiguration.class.getPackage() + .getName() + ".DataSourceConfiguration$")) { + return ConditionOutcome.match("Found auto-configured DataSource"); + } + return ConditionOutcome.noMatch("DataSource was not auto-configured"); } } diff --git a/spring-boot-devtools/src/test/java/org/springframework/boot/devtools/autoconfigure/AbstractDevToolsDataSourceAutoConfigurationTests.java b/spring-boot-devtools/src/test/java/org/springframework/boot/devtools/autoconfigure/AbstractDevToolsDataSourceAutoConfigurationTests.java new file mode 100644 index 0000000000..ce3e725d33 --- /dev/null +++ b/spring-boot-devtools/src/test/java/org/springframework/boot/devtools/autoconfigure/AbstractDevToolsDataSourceAutoConfigurationTests.java @@ -0,0 +1,161 @@ +/* + * Copyright 2012-2016 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.devtools.autoconfigure; + +import java.sql.Connection; +import java.sql.SQLException; +import java.sql.Statement; +import java.util.Collection; + +import javax.sql.DataSource; + +import org.junit.Test; + +import org.springframework.beans.BeansException; +import org.springframework.beans.factory.config.BeanPostProcessor; +import org.springframework.boot.autoconfigure.jdbc.DataSourceProperties; +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.boot.test.util.EnvironmentTestUtils; +import org.springframework.context.ConfigurableApplicationContext; +import org.springframework.context.annotation.AnnotationConfigApplicationContext; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +import static org.mockito.BDDMockito.given; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; + +/** + * Base class for tests for {@link DevToolsDataSourceAutoConfiguration}. + * + * @author Andy Wilkinson + */ +public class AbstractDevToolsDataSourceAutoConfigurationTests { + + @Test + public void singleManuallyConfiguredDataSourceIsNotClosed() throws SQLException { + ConfigurableApplicationContext context = createContext( + DataSourcePropertiesConfiguration.class, + SingleDataSourceConfiguration.class); + DataSource dataSource = context.getBean(DataSource.class); + Statement statement = configureDataSourceBehaviour(dataSource); + verify(statement, times(0)).execute("SHUTDOWN"); + } + + @Test + public void multipleDataSourcesAreIgnored() throws SQLException { + ConfigurableApplicationContext context = createContext( + DataSourcePropertiesConfiguration.class, + MultipleDataSourcesConfiguration.class); + Collection dataSources = context.getBeansOfType(DataSource.class) + .values(); + for (DataSource dataSource : dataSources) { + Statement statement = configureDataSourceBehaviour(dataSource); + verify(statement, times(0)).execute("SHUTDOWN"); + } + } + + protected final Statement configureDataSourceBehaviour(DataSource dataSource) + throws SQLException { + Connection connection = mock(Connection.class); + Statement statement = mock(Statement.class); + doReturn(connection).when(dataSource).getConnection(); + given(connection.createStatement()).willReturn(statement); + return statement; + } + + protected final ConfigurableApplicationContext createContext(String driverClassName, + Class... classes) { + AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(); + context.register(classes); + context.register(DevToolsDataSourceAutoConfiguration.class); + if (driverClassName != null) { + EnvironmentTestUtils.addEnvironment(context, + "spring.datasource.driver-class-name:" + driverClassName); + } + context.refresh(); + return context; + } + + protected final ConfigurableApplicationContext createContext(Class... classes) { + return this.createContext(null, classes); + } + + @Configuration + static class SingleDataSourceConfiguration { + + @Bean + public DataSource dataSource() { + return mock(DataSource.class); + } + + } + + @Configuration + static class MultipleDataSourcesConfiguration { + + @Bean + public DataSource dataSourceOne() { + return mock(DataSource.class); + } + + @Bean + public DataSource dataSourceTwo() { + return mock(DataSource.class); + } + + } + + @Configuration + @EnableConfigurationProperties(DataSourceProperties.class) + static class DataSourcePropertiesConfiguration { + + } + + @Configuration + static class DataSourceSpyConfiguration { + + @Bean + public DataSourceSpyBeanPostProcessor dataSourceSpyBeanPostProcessor() { + return new DataSourceSpyBeanPostProcessor(); + } + + } + + private static class DataSourceSpyBeanPostProcessor implements BeanPostProcessor { + + @Override + public Object postProcessBeforeInitialization(Object bean, String beanName) + throws BeansException { + if (bean instanceof DataSource) { + bean = spy(bean); + } + return bean; + } + + @Override + public Object postProcessAfterInitialization(Object bean, String beanName) + throws BeansException { + return bean; + } + + } + +} diff --git a/spring-boot-devtools/src/test/java/org/springframework/boot/devtools/autoconfigure/DevToolsDataSourceAutoConfigurationTests.java b/spring-boot-devtools/src/test/java/org/springframework/boot/devtools/autoconfigure/DevToolsDataSourceAutoConfigurationTests.java deleted file mode 100644 index b1248c5885..0000000000 --- a/spring-boot-devtools/src/test/java/org/springframework/boot/devtools/autoconfigure/DevToolsDataSourceAutoConfigurationTests.java +++ /dev/null @@ -1,185 +0,0 @@ -/* - * Copyright 2012-2016 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.devtools.autoconfigure; - -import java.sql.Connection; -import java.sql.SQLException; -import java.sql.Statement; - -import javax.persistence.EntityManagerFactory; -import javax.sql.DataSource; - -import org.junit.Test; -import org.mockito.InOrder; - -import org.springframework.boot.autoconfigure.jdbc.DataSourceProperties; -import org.springframework.boot.context.properties.EnableConfigurationProperties; -import org.springframework.boot.test.util.EnvironmentTestUtils; -import org.springframework.context.ConfigurableApplicationContext; -import org.springframework.context.annotation.AnnotationConfigApplicationContext; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.jdbc.datasource.embedded.EmbeddedDatabase; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.BDDMockito.given; -import static org.mockito.Mockito.inOrder; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.times; -import static org.mockito.Mockito.verify; - -/** - * Tests for {@link DevToolsDataSourceAutoConfiguration}. - * - * @author Andy Wilkinson - */ -public class DevToolsDataSourceAutoConfigurationTests { - - @Test - public void embeddedDatabaseIsNotShutDown() throws SQLException { - ConfigurableApplicationContext context = createContextWithDriver("org.h2.Driver", - EmbeddedDatabaseConfiguration.class); - DataSource dataSource = context.getBean(DataSource.class); - context.close(); - verify(dataSource, times(0)).getConnection(); - } - - @Test - public void externalDatabaseIsNotShutDown() throws SQLException { - ConfigurableApplicationContext context = createContextWithDriver( - "org.postgresql.Driver", DataSourceConfiguration.class); - DataSource dataSource = context.getBean(DataSource.class); - context.close(); - verify(dataSource, times(0)).getConnection(); - } - - @Test - public void nonEmbeddedInMemoryDatabaseConfiguredWithDriverIsShutDown() - throws SQLException { - ConfigurableApplicationContext context = createContextWithDriver("org.h2.Driver", - DataSourceConfiguration.class); - DataSource dataSource = context.getBean(DataSource.class); - Connection connection = mock(Connection.class); - given(dataSource.getConnection()).willReturn(connection); - Statement statement = mock(Statement.class); - given(connection.createStatement()).willReturn(statement); - context.close(); - verify(statement).execute("SHUTDOWN"); - } - - @Test - public void nonEmbeddedInMemoryDatabaseConfiguredWithUrlIsShutDown() - throws SQLException { - ConfigurableApplicationContext context = createContextWithUrl("jdbc:h2:mem:test", - DataSourceConfiguration.class); - DataSource dataSource = context.getBean(DataSource.class); - Connection connection = mock(Connection.class); - given(dataSource.getConnection()).willReturn(connection); - Statement statement = mock(Statement.class); - given(connection.createStatement()).willReturn(statement); - context.close(); - verify(statement).execute("SHUTDOWN"); - } - - @Test - public void configurationBacksOffWithoutDataSourceProperties() throws SQLException { - ConfigurableApplicationContext context = createContext("org.h2.Driver", - NoDataSourcePropertiesConfiguration.class); - assertThat(context.getBeansOfType(DevToolsDataSourceAutoConfiguration.class)) - .isEmpty(); - } - - @Test - public void entityManagerFactoryIsClosedBeforeDatabaseIsShutDown() - throws SQLException { - ConfigurableApplicationContext context = createContextWithUrl("jdbc:h2:mem:test", - DataSourceConfiguration.class, EntityManagerFactoryConfiguration.class); - DataSource dataSource = context.getBean(DataSource.class); - Connection connection = mock(Connection.class); - given(dataSource.getConnection()).willReturn(connection); - Statement statement = mock(Statement.class); - given(connection.createStatement()).willReturn(statement); - EntityManagerFactory entityManagerFactory = context - .getBean(EntityManagerFactory.class); - context.close(); - InOrder inOrder = inOrder(statement, entityManagerFactory); - inOrder.verify(statement).execute("SHUTDOWN"); - inOrder.verify(entityManagerFactory).close(); - } - - private ConfigurableApplicationContext createContextWithDriver(String driver, - Class... classes) { - return createContext("spring.datasource.driver-class-name:" + driver, classes); - } - - private ConfigurableApplicationContext createContextWithUrl(String url, - Class... classes) { - return createContext("spring.datasource.url:" + url, classes); - } - - private ConfigurableApplicationContext createContext(String property, - Class... classes) { - AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(); - context.register(classes); - context.register(DevToolsDataSourceAutoConfiguration.class); - EnvironmentTestUtils.addEnvironment(context, property); - context.refresh(); - return context; - } - - @Configuration - @EnableConfigurationProperties(DataSourceProperties.class) - static class EmbeddedDatabaseConfiguration { - - @Bean - public EmbeddedDatabase embeddedDatabase() { - return mock(EmbeddedDatabase.class); - } - } - - @Configuration - @EnableConfigurationProperties(DataSourceProperties.class) - static class DataSourceConfiguration { - - @Bean - public DataSource dataSource() { - return mock(DataSource.class); - } - - } - - @Configuration - static class NoDataSourcePropertiesConfiguration { - - @Bean - public DataSource dataSource() { - return mock(DataSource.class); - } - - } - - @Configuration - static class EntityManagerFactoryConfiguration { - - @Bean - public EntityManagerFactory entityManagerFactory() { - return mock(EntityManagerFactory.class); - } - - } - -} diff --git a/spring-boot-devtools/src/test/java/org/springframework/boot/devtools/autoconfigure/DevToolsEmbeddedDataSourceAutoConfigurationTests.java b/spring-boot-devtools/src/test/java/org/springframework/boot/devtools/autoconfigure/DevToolsEmbeddedDataSourceAutoConfigurationTests.java new file mode 100644 index 0000000000..4442faa9ff --- /dev/null +++ b/spring-boot-devtools/src/test/java/org/springframework/boot/devtools/autoconfigure/DevToolsEmbeddedDataSourceAutoConfigurationTests.java @@ -0,0 +1,55 @@ +/* + * Copyright 2012-2016 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.devtools.autoconfigure; + +import java.sql.SQLException; +import java.sql.Statement; + +import javax.sql.DataSource; + +import org.junit.Test; +import org.junit.runner.RunWith; + +import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration; +import org.springframework.boot.testutil.ClassPathExclusions; +import org.springframework.boot.testutil.FilteredClassPathRunner; +import org.springframework.context.ConfigurableApplicationContext; + +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; + +/** + * Tests for {@link DevToolsDataSourceAutoConfiguration} with an embedded data source. + * + * @author Andy Wilkinson + */ +@RunWith(FilteredClassPathRunner.class) +@ClassPathExclusions("tomcat-jdbc-*.jar") +public class DevToolsEmbeddedDataSourceAutoConfigurationTests + extends AbstractDevToolsDataSourceAutoConfigurationTests { + + @Test + public void autoConfiguredDataSourceIsNotShutdown() throws SQLException { + ConfigurableApplicationContext context = createContext( + DataSourceAutoConfiguration.class, DataSourceSpyConfiguration.class); + Statement statement = configureDataSourceBehaviour( + context.getBean(DataSource.class)); + context.close(); + verify(statement, times(0)).execute("SHUTDOWN"); + } + +} diff --git a/spring-boot-devtools/src/test/java/org/springframework/boot/devtools/autoconfigure/DevToolsPooledDataSourceAutoConfigurationTests.java b/spring-boot-devtools/src/test/java/org/springframework/boot/devtools/autoconfigure/DevToolsPooledDataSourceAutoConfigurationTests.java new file mode 100644 index 0000000000..3d1612bb19 --- /dev/null +++ b/spring-boot-devtools/src/test/java/org/springframework/boot/devtools/autoconfigure/DevToolsPooledDataSourceAutoConfigurationTests.java @@ -0,0 +1,63 @@ +/* + * Copyright 2012-2016 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.devtools.autoconfigure; + +import java.sql.SQLException; +import java.sql.Statement; + +import javax.sql.DataSource; + +import org.junit.Test; +import org.junit.runner.RunWith; + +import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration; +import org.springframework.boot.testutil.FilteredClassPathRunner; +import org.springframework.context.ConfigurableApplicationContext; + +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; + +/** + * Tests for {@link DevToolsDataSourceAutoConfiguration} with a pooled data source. + * + * @author Andy Wilkinson + */ +@RunWith(FilteredClassPathRunner.class) +public class DevToolsPooledDataSourceAutoConfigurationTests + extends AbstractDevToolsDataSourceAutoConfigurationTests { + + @Test + public void autoConfiguredInMemoryDataSourceIsShutdown() throws SQLException { + ConfigurableApplicationContext context = createContext( + DataSourceAutoConfiguration.class, DataSourceSpyConfiguration.class); + Statement statement = configureDataSourceBehaviour( + context.getBean(DataSource.class)); + context.close(); + verify(statement).execute("SHUTDOWN"); + } + + @Test + public void autoConfiguredExternalDataSourceIsNotShutdown() throws SQLException { + ConfigurableApplicationContext context = createContext("org.postgresql.Driver", + DataSourceAutoConfiguration.class, DataSourceSpyConfiguration.class); + Statement statement = configureDataSourceBehaviour( + context.getBean(DataSource.class)); + context.close(); + verify(statement, times(0)).execute("SHUTDOWN"); + } + +}