Merge pull request #14774 from RoyJacobs

* pr/14774:
  Polish "Allow ClassPathResources to be filtered by FilteredClassLoader"
  Allow ClassPathResources to be filtered by FilteredClassLoader
This commit is contained in:
Stephane Nicoll
2018-10-12 15:39:31 +02:00
3 changed files with 101 additions and 8 deletions

View File

@@ -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<String>[] filters;
private final Collection<Predicate<String>> classesFilters;
private final Collection<Predicate<String>> 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<String>... filters) {
this(Arrays.asList(filters), Arrays.asList(filters));
}
private FilteredClassLoader(Collection<Predicate<String>> classesFilters,
Collection<Predicate<String>> 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<String> filter : this.filters) {
for (Predicate<String> 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<String> 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<String> {
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);
}
}
}

View File

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