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
pull/5808/merge
Andy Wilkinson 9 years ago
parent 432969e61e
commit a19eeaf91d

@ -116,6 +116,11 @@
<artifactId>postgresql</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.apache.tomcat</groupId>
<artifactId>tomcat-jdbc</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>

@ -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,37 @@ public class DevToolsDataSourceAutoConfiguration {
}
static class DevToolsDataSourceCondition extends AllNestedConditions {
DevToolsDataSourceCondition() {
super(ConfigurationPhase.REGISTER_BEAN);
}
@ConditionalOnBean(DataSource.class)
static final class DataSourceBean {
static class DevToolsDataSourceCondition extends SpringBootCondition
implements ConfigurationCondition {
@Override
public ConfigurationPhase getConfigurationPhase() {
return ConfigurationPhase.REGISTER_BEAN;
}
@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.getName())) {
return ConditionOutcome.match("Found auto-configured DataSource");
}
return ConditionOutcome.noMatch("DataSource was not auto-configured");
}
}

@ -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.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<DataSource> 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;
}
}
}

@ -1,187 +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.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.hamcrest.Matchers.is;
import static org.junit.Assert.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).size(),
is(0));
}
@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);
}
}
}

@ -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.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");
}
}

@ -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.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");
}
}

@ -0,0 +1,44 @@
/*
* 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();
}

@ -0,0 +1,222 @@
/*
* 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);
}
}
}
}
Loading…
Cancel
Save