001/*
002 * Copyright 2012-2018 the original author or authors.
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 *      http://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 */
016
017package org.springframework.boot.test.context;
018
019import java.net.URL;
020import java.net.URLClassLoader;
021import java.util.Arrays;
022import java.util.Collection;
023import java.util.Collections;
024import java.util.function.Predicate;
025
026import org.springframework.core.io.ClassPathResource;
027
028/**
029 * Test {@link URLClassLoader} that can filter the classes and resources it can load.
030 *
031 * @author Andy Wilkinson
032 * @author Stephane Nicoll
033 * @author Phillip Webb
034 * @author Roy Jacobs
035 * @since 2.0.0
036 */
037public class FilteredClassLoader extends URLClassLoader {
038
039        private final Collection<Predicate<String>> classesFilters;
040
041        private final Collection<Predicate<String>> resourcesFilters;
042
043        /**
044         * Create a {@link FilteredClassLoader} that hides the given classes.
045         * @param hiddenClasses the classes to hide
046         */
047        public FilteredClassLoader(Class<?>... hiddenClasses) {
048                this(Collections.singleton(ClassFilter.of(hiddenClasses)),
049                                Collections.emptyList());
050        }
051
052        /**
053         * Create a {@link FilteredClassLoader} that hides classes from the given packages.
054         * @param hiddenPackages the packages to hide
055         */
056        public FilteredClassLoader(String... hiddenPackages) {
057                this(Collections.singleton(PackageFilter.of(hiddenPackages)),
058                                Collections.emptyList());
059        }
060
061        /**
062         * Create a {@link FilteredClassLoader} that hides resources from the given
063         * {@link ClassPathResource classpath resources}.
064         * @param hiddenResources the resources to hide
065         * @since 2.1.0
066         */
067        public FilteredClassLoader(ClassPathResource... hiddenResources) {
068                this(Collections.emptyList(),
069                                Collections.singleton(ClassPathResourceFilter.of(hiddenResources)));
070        }
071
072        /**
073         * Create a {@link FilteredClassLoader} that filters based on the given predicate.
074         * @param filters a set of filters to determine when a class name or resource should
075         * be hidden. A {@link Predicate#test(Object) result} of {@code true} indicates a
076         * filtered class or resource. The input of the predicate can either be the binary
077         * name of a class or a resource name.
078         */
079        @SafeVarargs
080        public FilteredClassLoader(Predicate<String>... filters) {
081                this(Arrays.asList(filters), Arrays.asList(filters));
082        }
083
084        private FilteredClassLoader(Collection<Predicate<String>> classesFilters,
085                        Collection<Predicate<String>> resourcesFilters) {
086                super(new URL[0], FilteredClassLoader.class.getClassLoader());
087                this.classesFilters = classesFilters;
088                this.resourcesFilters = resourcesFilters;
089        }
090
091        @Override
092        protected Class<?> loadClass(String name, boolean resolve)
093                        throws ClassNotFoundException {
094                for (Predicate<String> filter : this.classesFilters) {
095                        if (filter.test(name)) {
096                                throw new ClassNotFoundException();
097                        }
098                }
099                return super.loadClass(name, resolve);
100        }
101
102        @Override
103        public URL getResource(String name) {
104                for (Predicate<String> filter : this.resourcesFilters) {
105                        if (filter.test(name)) {
106                                return null;
107                        }
108                }
109                return super.getResource(name);
110        }
111
112        /**
113         * Filter to restrict the classes that can be loaded.
114         */
115        public static final class ClassFilter implements Predicate<String> {
116
117                private Class<?>[] hiddenClasses;
118
119                private ClassFilter(Class<?>[] hiddenClasses) {
120                        this.hiddenClasses = hiddenClasses;
121                }
122
123                @Override
124                public boolean test(String className) {
125                        for (Class<?> hiddenClass : this.hiddenClasses) {
126                                if (className.equals(hiddenClass.getName())) {
127                                        return true;
128                                }
129                        }
130                        return false;
131                }
132
133                public static ClassFilter of(Class<?>... hiddenClasses) {
134                        return new ClassFilter(hiddenClasses);
135                }
136
137        }
138
139        /**
140         * Filter to restrict the packages that can be loaded.
141         */
142        public static final class PackageFilter implements Predicate<String> {
143
144                private final String[] hiddenPackages;
145
146                private PackageFilter(String[] hiddenPackages) {
147                        this.hiddenPackages = hiddenPackages;
148                }
149
150                @Override
151                public boolean test(String className) {
152                        for (String hiddenPackage : this.hiddenPackages) {
153                                if (className.startsWith(hiddenPackage)) {
154                                        return true;
155                                }
156                        }
157                        return false;
158                }
159
160                public static PackageFilter of(String... hiddenPackages) {
161                        return new PackageFilter(hiddenPackages);
162                }
163
164        }
165
166        /**
167         * Filter to restrict the resources that can be loaded.
168         *
169         * @since 2.1.0
170         */
171        public static final class ClassPathResourceFilter implements Predicate<String> {
172
173                private final ClassPathResource[] hiddenResources;
174
175                private ClassPathResourceFilter(ClassPathResource[] hiddenResources) {
176                        this.hiddenResources = hiddenResources;
177                }
178
179                @Override
180                public boolean test(String resourceName) {
181                        for (ClassPathResource hiddenResource : this.hiddenResources) {
182                                if (hiddenResource.getFilename() != null
183                                                && resourceName.equals(hiddenResource.getPath())) {
184                                        return true;
185                                }
186                        }
187                        return false;
188                }
189
190                public static ClassPathResourceFilter of(ClassPathResource... hiddenResources) {
191                        return new ClassPathResourceFilter(hiddenResources);
192                }
193
194        }
195
196}