diff --git a/spring-boot-project/spring-boot-test/src/main/java/org/springframework/boot/test/context/FilteredClassLoader.java b/spring-boot-project/spring-boot-test/src/main/java/org/springframework/boot/test/context/FilteredClassLoader.java index d222aa5cda..16cd61d9fe 100644 --- a/spring-boot-project/spring-boot-test/src/main/java/org/springframework/boot/test/context/FilteredClassLoader.java +++ b/spring-boot-project/spring-boot-test/src/main/java/org/springframework/boot/test/context/FilteredClassLoader.java @@ -18,26 +18,35 @@ package org.springframework.boot.test.context; import java.net.URL; import java.net.URLClassLoader; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; import java.util.function.Predicate; +import org.springframework.core.io.ClassPathResource; + /** - * Test {@link URLClassLoader} that can filter the classes it can load. + * Test {@link URLClassLoader} that can filter the classes and resources it can load. * * @author Andy Wilkinson * @author Stephane Nicoll * @author Phillip Webb + * @author Roy Jacobs * @since 2.0.0 */ public class FilteredClassLoader extends URLClassLoader { - private final Predicate[] filters; + private final Collection> classesFilters; + + private final Collection> resourcesFilters; /** * Create a {@link FilteredClassLoader} that hides the given classes. * @param hiddenClasses the classes to hide */ public FilteredClassLoader(Class... hiddenClasses) { - this(ClassFilter.of(hiddenClasses)); + this(Collections.singleton(ClassFilter.of(hiddenClasses)), + Collections.emptyList()); } /** @@ -45,24 +54,43 @@ public class FilteredClassLoader extends URLClassLoader { * @param hiddenPackages the packages to hide */ public FilteredClassLoader(String... hiddenPackages) { - this(PackageFilter.of(hiddenPackages)); + this(Collections.singleton(PackageFilter.of(hiddenPackages)), + Collections.emptyList()); + } + + /** + * Create a {@link FilteredClassLoader} that hides resources from the given + * {@link ClassPathResource classpath resources}. + * @param hiddenResources the resources to hide + */ + public FilteredClassLoader(ClassPathResource... hiddenResources) { + this(Collections.emptyList(), + Collections.singleton(ClassPathResourceFilter.of(hiddenResources))); } /** * Create a {@link FilteredClassLoader} that filters based on the given predicate. - * @param filters a set of filters to determine when a class name should be hidden. A - * {@link Predicate#test(Object) result} of {@code true} indicates a filtered class. + * @param filters a set of filters to determine when a class name or resource should + * be hidden. A {@link Predicate#test(Object) result} of {@code true} indicates a + * filtered class or resource. The input of the predicate can either be the binary + * name of a class or a resource name. */ @SafeVarargs public FilteredClassLoader(Predicate... filters) { + this(Arrays.asList(filters), Arrays.asList(filters)); + } + + private FilteredClassLoader(Collection> classesFilters, + Collection> resourcesFilters) { super(new URL[0], FilteredClassLoader.class.getClassLoader()); - this.filters = filters; + this.classesFilters = classesFilters; + this.resourcesFilters = resourcesFilters; } @Override protected Class loadClass(String name, boolean resolve) throws ClassNotFoundException { - for (Predicate filter : this.filters) { + for (Predicate filter : this.classesFilters) { if (filter.test(name)) { throw new ClassNotFoundException(); } @@ -70,6 +98,16 @@ public class FilteredClassLoader extends URLClassLoader { return super.loadClass(name, resolve); } + @Override + public URL getResource(String name) { + for (Predicate filter : this.resourcesFilters) { + if (filter.test(name)) { + return null; + } + } + return super.getResource(name); + } + /** * Filter to restrict the classes that can be loaded. */ @@ -124,4 +162,32 @@ public class FilteredClassLoader extends URLClassLoader { } + /** + * Filter to restrict the resources that can be loaded. + */ + public static final class ClassPathResourceFilter implements Predicate { + + private final ClassPathResource[] hiddenResources; + + private ClassPathResourceFilter(ClassPathResource[] hiddenResources) { + this.hiddenResources = hiddenResources; + } + + @Override + public boolean test(String resourceName) { + for (ClassPathResource hiddenResource : this.hiddenResources) { + if (hiddenResource.getFilename() != null + && resourceName.equals(hiddenResource.getPath())) { + return true; + } + } + return false; + } + + public static ClassPathResourceFilter of(ClassPathResource... hiddenResources) { + return new ClassPathResourceFilter(hiddenResources); + } + + } + } diff --git a/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/context/FilteredClassLoaderTests.java b/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/context/FilteredClassLoaderTests.java index 1422f1e3a4..f297c66676 100644 --- a/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/context/FilteredClassLoaderTests.java +++ b/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/context/FilteredClassLoaderTests.java @@ -16,8 +16,12 @@ package org.springframework.boot.test.context; +import java.net.URL; + import org.junit.Test; +import org.springframework.core.io.ClassPathResource; + import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatExceptionOfType; @@ -25,9 +29,13 @@ import static org.assertj.core.api.Assertions.assertThatExceptionOfType; * Tests for {@link FilteredClassLoader}. * * @author Phillip Webb + * @author Roy Jacobs */ public class FilteredClassLoaderTests { + private static ClassPathResource TEST_RESOURCE = new ClassPathResource( + "org/springframework/boot/test/context/FilteredClassLoaderTestsResource.txt"); + @Test public void loadClassWhenFilteredOnPackageShouldThrowClassNotFound() throws Exception { @@ -55,4 +63,22 @@ public class FilteredClassLoaderTests { classLoader.close(); } + @Test + public void loadResourceWhenFilteredOnResourceShouldReturnNotFound() + throws Exception { + try (FilteredClassLoader classLoader = new FilteredClassLoader(TEST_RESOURCE)) { + final URL loaded = classLoader.getResource(TEST_RESOURCE.getPath()); + assertThat(loaded).isNull(); + } + } + + @Test + public void loadResourceWhenNotFilteredShouldLoadResource() throws Exception { + try (FilteredClassLoader classLoader = new FilteredClassLoader( + (resourceName) -> false)) { + final URL loaded = classLoader.getResource(TEST_RESOURCE.getPath()); + assertThat(loaded).isNotNull(); + } + } + } diff --git a/spring-boot-project/spring-boot-test/src/test/resources/org/springframework/boot/test/context/FilteredClassLoaderTestsResource.txt b/spring-boot-project/spring-boot-test/src/test/resources/org/springframework/boot/test/context/FilteredClassLoaderTestsResource.txt new file mode 100644 index 0000000000..5c1b88e1a7 --- /dev/null +++ b/spring-boot-project/spring-boot-test/src/test/resources/org/springframework/boot/test/context/FilteredClassLoaderTestsResource.txt @@ -0,0 +1 @@ +Used by FilteredClassLoaderTests.java \ No newline at end of file