Commit a19eeaf9 authored by Andy Wilkinson's avatar Andy Wilkinson

DevTools should only shut down single, auto-configured DataSource

Previously, there were two problems with DevTools’ DataSource
auto-configuration:

1. It did not tolerate a context with multiple DataSources
2. It would attempt to shut down a DataSource that had not been created
   by DataSourceAutoConfiguration and, therefore, where we could not be
   sure of its configuration.

This commit updates DevToolsDataSourceAutoConfiguration so that it backs
off unless the context contains DataSourceProperties and a single
DataSource created by DataSourceAutoConfiguration. This ensures that it
can safely use DataSourceProperties to get the DataSource’s
driver class name and accurately determine if it’s an in-memory or
external database. Shutdown is only called for an in-memory database.

Closes gh-5540
parent 432969e6
...@@ -116,6 +116,11 @@ ...@@ -116,6 +116,11 @@
<artifactId>postgresql</artifactId> <artifactId>postgresql</artifactId>
<scope>test</scope> <scope>test</scope>
</dependency> </dependency>
<dependency>
<groupId>org.apache.tomcat</groupId>
<artifactId>tomcat-jdbc</artifactId>
<scope>test</scope>
</dependency>
<dependency> <dependency>
<groupId>org.springframework.boot</groupId> <groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId> <artifactId>spring-boot-starter-thymeleaf</artifactId>
......
...@@ -23,18 +23,24 @@ import java.util.Set; ...@@ -23,18 +23,24 @@ import java.util.Set;
import javax.sql.DataSource; import javax.sql.DataSource;
import org.springframework.beans.factory.DisposableBean; 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.AutoConfigureAfter;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration; 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.ConditionalOnBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; 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.data.jpa.EntityManagerFactoryDependsOnPostProcessor;
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration; import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
import org.springframework.boot.autoconfigure.jdbc.DataSourceProperties; import org.springframework.boot.autoconfigure.jdbc.DataSourceProperties;
import org.springframework.boot.devtools.autoconfigure.DevToolsDataSourceAutoConfiguration.DevToolsDataSourceCondition; import org.springframework.boot.devtools.autoconfigure.DevToolsDataSourceAutoConfiguration.DevToolsDataSourceCondition;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ConditionContext;
import org.springframework.context.annotation.Conditional; import org.springframework.context.annotation.Conditional;
import org.springframework.context.annotation.Configuration; 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.jdbc.datasource.embedded.EmbeddedDatabase;
import org.springframework.orm.jpa.AbstractEntityManagerFactoryBean; import org.springframework.orm.jpa.AbstractEntityManagerFactoryBean;
import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean; import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean;
...@@ -109,20 +115,37 @@ public class DevToolsDataSourceAutoConfiguration { ...@@ -109,20 +115,37 @@ public class DevToolsDataSourceAutoConfiguration {
} }
static class DevToolsDataSourceCondition extends AllNestedConditions { static class DevToolsDataSourceCondition extends SpringBootCondition
implements ConfigurationCondition {
DevToolsDataSourceCondition() {
super(ConfigurationPhase.REGISTER_BEAN);
}
@ConditionalOnBean(DataSource.class)
static final class DataSourceBean {
@Override
public ConfigurationPhase getConfigurationPhase() {
return ConfigurationPhase.REGISTER_BEAN;
} }
@ConditionalOnBean(DataSourceProperties.class) @Override
static final class DataSourcePropertiesBean { 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.getName())) {
return ConditionOutcome.match("Found auto-configured DataSource");
}
return ConditionOutcome.noMatch("DataSource was not auto-configured");
} }
} }
......
...@@ -19,13 +19,14 @@ package org.springframework.boot.devtools.autoconfigure; ...@@ -19,13 +19,14 @@ package org.springframework.boot.devtools.autoconfigure;
import java.sql.Connection; import java.sql.Connection;
import java.sql.SQLException; import java.sql.SQLException;
import java.sql.Statement; import java.sql.Statement;
import java.util.Collection;
import javax.persistence.EntityManagerFactory;
import javax.sql.DataSource; import javax.sql.DataSource;
import org.junit.Test; import org.junit.Test;
import org.mockito.InOrder;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.boot.autoconfigure.jdbc.DataSourceProperties; import org.springframework.boot.autoconfigure.jdbc.DataSourceProperties;
import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.boot.test.EnvironmentTestUtils; import org.springframework.boot.test.EnvironmentTestUtils;
...@@ -33,129 +34,72 @@ import org.springframework.context.ConfigurableApplicationContext; ...@@ -33,129 +34,72 @@ import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext; import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
import org.springframework.jdbc.datasource.embedded.EmbeddedDatabase;
import static org.hamcrest.Matchers.is;
import static org.junit.Assert.assertThat;
import static org.mockito.BDDMockito.given; import static org.mockito.BDDMockito.given;
import static org.mockito.Mockito.inOrder; import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock; import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.times; import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verify;
/** /**
* Tests for {@link DevToolsDataSourceAutoConfiguration}. * Base class for tests for {@link DevToolsDataSourceAutoConfiguration}.
* *
* @author Andy Wilkinson * @author Andy Wilkinson
*/ */
public class DevToolsDataSourceAutoConfigurationTests { public class AbstractDevToolsDataSourceAutoConfigurationTests {
@Test @Test
public void embeddedDatabaseIsNotShutDown() throws SQLException { public void singleManuallyConfiguredDataSourceIsNotClosed() throws SQLException {
ConfigurableApplicationContext context = createContextWithDriver("org.h2.Driver", ConfigurableApplicationContext context = createContext(
EmbeddedDatabaseConfiguration.class); DataSourcePropertiesConfiguration.class,
SingleDataSourceConfiguration.class);
DataSource dataSource = context.getBean(DataSource.class); DataSource dataSource = context.getBean(DataSource.class);
context.close(); Statement statement = configureDataSourceBehaviour(dataSource);
verify(dataSource, times(0)).getConnection(); verify(statement, times(0)).execute("SHUTDOWN");
} }
@Test @Test
public void externalDatabaseIsNotShutDown() throws SQLException { public void multipleDataSourcesAreIgnored() throws SQLException {
ConfigurableApplicationContext context = createContextWithDriver( ConfigurableApplicationContext context = createContext(
"org.postgresql.Driver", DataSourceConfiguration.class); DataSourcePropertiesConfiguration.class,
DataSource dataSource = context.getBean(DataSource.class); MultipleDataSourcesConfiguration.class);
context.close(); Collection<DataSource> dataSources = context.getBeansOfType(DataSource.class)
verify(dataSource, times(0)).getConnection(); .values();
} for (DataSource dataSource : dataSources) {
Statement statement = configureDataSourceBehaviour(dataSource);
@Test verify(statement, times(0)).execute("SHUTDOWN");
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).size(),
is(0));
} }
@Test protected final Statement configureDataSourceBehaviour(DataSource dataSource)
public void entityManagerFactoryIsClosedBeforeDatabaseIsShutDown()
throws SQLException { throws SQLException {
ConfigurableApplicationContext context = createContextWithUrl("jdbc:h2:mem:test",
DataSourceConfiguration.class, EntityManagerFactoryConfiguration.class);
DataSource dataSource = context.getBean(DataSource.class);
Connection connection = mock(Connection.class); Connection connection = mock(Connection.class);
given(dataSource.getConnection()).willReturn(connection);
Statement statement = mock(Statement.class); Statement statement = mock(Statement.class);
doReturn(connection).when(dataSource).getConnection();
given(connection.createStatement()).willReturn(statement); given(connection.createStatement()).willReturn(statement);
EntityManagerFactory entityManagerFactory = context return statement;
.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, protected final ConfigurableApplicationContext createContext(String driverClassName,
Class<?>... classes) {
return createContext("spring.datasource.url:" + url, classes);
}
private ConfigurableApplicationContext createContext(String property,
Class<?>... classes) { Class<?>... classes) {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(); AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
context.register(classes); context.register(classes);
context.register(DevToolsDataSourceAutoConfiguration.class); context.register(DevToolsDataSourceAutoConfiguration.class);
EnvironmentTestUtils.addEnvironment(context, property); if (driverClassName != null) {
EnvironmentTestUtils.addEnvironment(context,
"spring.datasource.driver-class-name:" + driverClassName);
}
context.refresh(); context.refresh();
return context; return context;
} }
@Configuration protected final ConfigurableApplicationContext createContext(Class<?>... classes) {
@EnableConfigurationProperties(DataSourceProperties.class) return this.createContext(null, classes);
static class EmbeddedDatabaseConfiguration {
@Bean
public EmbeddedDatabase embeddedDatabase() {
return mock(EmbeddedDatabase.class);
}
} }
@Configuration @Configuration
@EnableConfigurationProperties(DataSourceProperties.class) static class SingleDataSourceConfiguration {
static class DataSourceConfiguration {
@Bean @Bean
public DataSource dataSource() { public DataSource dataSource() {
...@@ -165,21 +109,51 @@ public class DevToolsDataSourceAutoConfigurationTests { ...@@ -165,21 +109,51 @@ public class DevToolsDataSourceAutoConfigurationTests {
} }
@Configuration @Configuration
static class NoDataSourcePropertiesConfiguration { static class MultipleDataSourcesConfiguration {
@Bean @Bean
public DataSource dataSource() { public DataSource dataSourceOne() {
return mock(DataSource.class);
}
@Bean
public DataSource dataSourceTwo() {
return mock(DataSource.class); return mock(DataSource.class);
} }
} }
@Configuration @Configuration
static class EntityManagerFactoryConfiguration { @EnableConfigurationProperties(DataSourceProperties.class)
static class DataSourcePropertiesConfiguration {
}
@Configuration
static class DataSourceSpyConfiguration {
@Bean @Bean
public EntityManagerFactory entityManagerFactory() { public DataSourceSpyBeanPostProcessor dataSourceSpyBeanPostProcessor() {
return mock(EntityManagerFactory.class); 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;
} }
} }
......
/*
* 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.devtools.test.ClassPathExclusions;
import org.springframework.boot.devtools.test.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");
}
}
/*
* 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.devtools.test.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");
}
}
/*
* 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.test;
import java.io.File;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* Annotation used in combination with {@link FilteredClassPathRunner} to exclude entries
* from the classpath.
*
* @author Andy Wilkinson
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface ClassPathExclusions {
/**
* One or more Ant-style patterns that identify entries to be excluded from the class
* path. Matching is performed against an entry's {@link File#getName() file name}.
* For example, to exclude Hibernate Validator from the classpath,
* {@code "hibernate-validator-*.jar"} can be used.
* @return the exclusion patterns
*/
String[] value();
}
/*
* 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.test;
import java.io.File;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.jar.Attributes;
import java.util.jar.JarFile;
import org.junit.runners.BlockJUnit4ClassRunner;
import org.junit.runners.model.FrameworkMethod;
import org.junit.runners.model.InitializationError;
import org.junit.runners.model.TestClass;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.util.AntPathMatcher;
import org.springframework.util.StringUtils;
/**
* A custom {@link BlockJUnit4ClassRunner} that runs tests using a filtered class path.
* Entries are excluded from the class path using {@link ClassPathExclusions} on the test
* class. A class loader is created with the customized class path and is used both to
* load the test class and as the thread context class loader while the test is being run.
*
* @author Andy Wilkinson
*/
public class FilteredClassPathRunner extends BlockJUnit4ClassRunner {
public FilteredClassPathRunner(Class<?> testClass) throws InitializationError {
super(testClass);
}
@Override
protected TestClass createTestClass(Class<?> testClass) {
try {
ClassLoader classLoader = createTestClassLoader(testClass);
return new FilteredTestClass(classLoader, testClass.getName());
}
catch (Exception ex) {
throw new IllegalStateException(ex);
}
}
private URLClassLoader createTestClassLoader(Class<?> testClass) throws Exception {
URLClassLoader classLoader = (URLClassLoader) this.getClass().getClassLoader();
return new URLClassLoader(filterUrls(extractUrls(classLoader), testClass),
classLoader.getParent());
}
private URL[] extractUrls(URLClassLoader classLoader) throws Exception {
List<URL> extractedUrls = new ArrayList<URL>();
for (URL url : classLoader.getURLs()) {
if (isSurefireBooterJar(url)) {
extractedUrls.addAll(extractUrlsFromManifestClassPath(url));
}
else {
extractedUrls.add(url);
}
}
return extractedUrls.toArray(new URL[extractedUrls.size()]);
}
private boolean isSurefireBooterJar(URL url) {
return url.getPath().contains("surefirebooter");
}
private List<URL> extractUrlsFromManifestClassPath(URL booterJar) throws Exception {
List<URL> urls = new ArrayList<URL>();
for (String entry : getClassPath(booterJar)) {
urls.add(new URL(entry));
}
return urls;
}
private String[] getClassPath(URL booterJar) throws Exception {
JarFile jarFile = new JarFile(new File(booterJar.toURI()));
try {
return StringUtils.delimitedListToStringArray(jarFile.getManifest()
.getMainAttributes().getValue(Attributes.Name.CLASS_PATH), " ");
}
finally {
jarFile.close();
}
}
private URL[] filterUrls(URL[] urls, Class<?> testClass) throws Exception {
ClassPathEntryFilter filter = new ClassPathEntryFilter(testClass);
List<URL> filteredUrls = new ArrayList<URL>();
for (URL url : urls) {
if (!filter.isExcluded(url)) {
filteredUrls.add(url);
}
}
return filteredUrls.toArray(new URL[filteredUrls.size()]);
}
/**
* Filter for class path entries.
*/
private static final class ClassPathEntryFilter {
private final List<String> exclusions;
private final AntPathMatcher matcher = new AntPathMatcher();
private ClassPathEntryFilter(Class<?> testClass) throws Exception {
ClassPathExclusions exclusions = AnnotationUtils.findAnnotation(testClass,
ClassPathExclusions.class);
this.exclusions = exclusions == null ? Collections.<String>emptyList()
: Arrays.asList(exclusions.value());
}
private boolean isExcluded(URL url) throws Exception {
if (!"file".equals(url.getProtocol())) {
return false;
}
String name = new File(url.toURI()).getName();
for (String exclusion : this.exclusions) {
if (this.matcher.match(exclusion, name)) {
return true;
}
}
return false;
}
}
/**
* Filtered version of JUnit's {@link TestClass}.
*/
private static final class FilteredTestClass extends TestClass {
private final ClassLoader classLoader;
FilteredTestClass(ClassLoader classLoader, String testClassName)
throws ClassNotFoundException {
super(classLoader.loadClass(testClassName));
this.classLoader = classLoader;
}
@Override
public List<FrameworkMethod> getAnnotatedMethods(
Class<? extends Annotation> annotationClass) {
try {
return getAnnotatedMethods(annotationClass.getName());
}
catch (ClassNotFoundException ex) {
throw new RuntimeException(ex);
}
}
@SuppressWarnings("unchecked")
private List<FrameworkMethod> getAnnotatedMethods(String annotationClassName)
throws ClassNotFoundException {
Class<? extends Annotation> annotationClass = (Class<? extends Annotation>) this.classLoader
.loadClass(annotationClassName);
List<FrameworkMethod> methods = super.getAnnotatedMethods(annotationClass);
return wrapFrameworkMethods(methods);
}
private List<FrameworkMethod> wrapFrameworkMethods(
List<FrameworkMethod> methods) {
List<FrameworkMethod> wrapped = new ArrayList<FrameworkMethod>(
methods.size());
for (FrameworkMethod frameworkMethod : methods) {
wrapped.add(new FilteredFrameworkMethod(this.classLoader,
frameworkMethod.getMethod()));
}
return wrapped;
}
}
/**
* Filtered version of JUnit's {@link FrameworkMethod}.
*/
private static final class FilteredFrameworkMethod extends FrameworkMethod {
private final ClassLoader classLoader;
private FilteredFrameworkMethod(ClassLoader classLoader, Method method) {
super(method);
this.classLoader = classLoader;
}
@Override
public Object invokeExplosively(Object target, Object... params)
throws Throwable {
ClassLoader originalClassLoader = Thread.currentThread()
.getContextClassLoader();
Thread.currentThread().setContextClassLoader(this.classLoader);
try {
return super.invokeExplosively(target, params);
}
finally {
Thread.currentThread().setContextClassLoader(originalClassLoader);
}
}
}
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment