/* * Copyright 2012 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.gradle.internal.classloader; import org.gradle.internal.reflect.JavaMethod; import org.gradle.internal.reflect.JavaReflectionUtil; import java.io.IOException; import java.net.URL; import java.util.ArrayList; import java.util.Collection; import java.util.Enumeration; import java.util.HashSet; import java.util.List; import java.util.Set; /** * A ClassLoader which hides all non-system classes, packages and resources. Allows certain non-system packages and classes to be declared as visible. By default, only the Java system classes, * packages and resources are visible. */ public class FilteringClassLoader extends ClassLoader implements ClassLoaderHierarchy { private static final ClassLoader EXT_CLASS_LOADER; private static final Set<String> SYSTEM_PACKAGES = new HashSet<String>(); public static final String DEFAULT_PACKAGE = "DEFAULT"; private final Set<String> packageNames = new HashSet<String>(); private final Set<String> packagePrefixes = new HashSet<String>(); private final Set<String> resourcePrefixes = new HashSet<String>(); private final Set<String> resourceNames = new HashSet<String>(); private final Set<String> classNames = new HashSet<String>(); private final Set<String> disallowedClassNames = new HashSet<String>(); private final Set<String> disallowedPackagePrefixes = new HashSet<String>(); static { EXT_CLASS_LOADER = ClassLoaderUtils.getPlatformClassLoader(); JavaMethod<ClassLoader, Package[]> method = JavaReflectionUtil.method(ClassLoader.class, Package[].class, "getPackages"); Package[] systemPackages = method.invoke(EXT_CLASS_LOADER); for (Package p : systemPackages) { SYSTEM_PACKAGES.add(p.getName()); } } public FilteringClassLoader(ClassLoader parent, Spec spec) { super(parent); packageNames.addAll(spec.packageNames); packagePrefixes.addAll(spec.packagePrefixes); resourceNames.addAll(spec.resourceNames); resourcePrefixes.addAll(spec.resourcePrefixes); classNames.addAll(spec.classNames); disallowedClassNames.addAll(spec.disallowedClassNames); disallowedPackagePrefixes.addAll(spec.disallowedPackagePrefixes); } public void visit(ClassLoaderVisitor visitor) { visitor.visitSpec(new Spec(classNames, packageNames, packagePrefixes, resourcePrefixes, resourceNames, disallowedClassNames, disallowedPackagePrefixes)); visitor.visitParent(getParent()); } @Override protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException { try { return EXT_CLASS_LOADER.loadClass(name); } catch (ClassNotFoundException ignore) { // ignore } if (!classAllowed(name)) { throw new ClassNotFoundException(name + " not found."); } Class<?> cl = super.loadClass(name, false); if (resolve) { resolveClass(cl); } return cl; } @Override protected Package getPackage(String name) { Package p = super.getPackage(name); if (p == null || !allowed(p)) { return null; } return p; } @Override protected Package[] getPackages() { List<Package> packages = new ArrayList<Package>(); for (Package p : super.getPackages()) { if (allowed(p)) { packages.add(p); } } return packages.toArray(new Package[0]); } @Override public URL getResource(String name) { if (allowed(name)) { return super.getResource(name); } return EXT_CLASS_LOADER.getResource(name); } @Override public Enumeration<URL> getResources(String name) throws IOException { if (allowed(name)) { return super.getResources(name); } return EXT_CLASS_LOADER.getResources(name); } private boolean allowed(String resourceName) { if (resourceNames.contains(resourceName)) { return true; } for (String resourcePrefix : resourcePrefixes) { if (resourceName.startsWith(resourcePrefix)) { return true; } } return false; } private boolean allowed(Package pkg) { for (String packagePrefix : disallowedPackagePrefixes) { if (pkg.getName().startsWith(packagePrefix)) { return false; } } if (SYSTEM_PACKAGES.contains(pkg.getName())) { return true; } if (packageNames.contains(pkg.getName())) { return true; } for (String packagePrefix : packagePrefixes) { if (pkg.getName().startsWith(packagePrefix)) { return true; } } return false; } private boolean classAllowed(String className) { if (disallowedClassNames.contains(className)) { return false; } for (String packagePrefix : disallowedPackagePrefixes) { if (className.startsWith(packagePrefix)) { return false; } } if (classNames.contains(className)) { return true; } for (String packagePrefix : packagePrefixes) { if (className.startsWith(packagePrefix)) { return true; } if (packagePrefix.startsWith(DEFAULT_PACKAGE) && isInDefaultPackage(className)) { return true; } } return false; } private boolean isInDefaultPackage(String className) { return !className.contains("."); } public static class Spec extends ClassLoaderSpec { final Set<String> packageNames = new HashSet<String>(); final Set<String> packagePrefixes = new HashSet<String>(); final Set<String> resourcePrefixes = new HashSet<String>(); final Set<String> resourceNames = new HashSet<String>(); final Set<String> classNames = new HashSet<String>(); final Set<String> disallowedClassNames = new HashSet<String>(); final Set<String> disallowedPackagePrefixes = new HashSet<String>(); public Spec() { } public Spec(Spec spec) { this( spec.classNames, spec.packageNames, spec.packagePrefixes, spec.resourcePrefixes, spec.resourceNames, spec.disallowedClassNames, spec.disallowedPackagePrefixes ); } public Spec(Collection<String> classNames, Collection<String> packageNames, Collection<String> packagePrefixes, Collection<String> resourcePrefixes, Collection<String> resourceNames, Collection<String> disallowedClassNames, Collection<String> disallowedPackagePrefixes) { this.classNames.addAll(classNames); this.packageNames.addAll(packageNames); this.packagePrefixes.addAll(packagePrefixes); this.resourcePrefixes.addAll(resourcePrefixes); this.resourceNames.addAll(resourceNames); this.disallowedClassNames.addAll(disallowedClassNames); this.disallowedPackagePrefixes.addAll(disallowedPackagePrefixes); } /** * Marks a package and all its sub-packages as visible. Also makes resources in those packages visible. * * @param packageName the package name */ public void allowPackage(String packageName) { packageNames.add(packageName); packagePrefixes.add(packageName + "."); resourcePrefixes.add(packageName.replace('.', '/') + '/'); } /** * Marks a single class as visible. * * @param clazz the class */ public void allowClass(Class<?> clazz) { classNames.add(clazz.getName()); } /** * Marks a single class as not visible. * * @param className the class name */ public void disallowClass(String className) { disallowedClassNames.add(className); } /** * Marks a package and all its sub-packages as not visible. Does not affect resources in those packages. * * @param packagePrefix the package prefix */ public void disallowPackage(String packagePrefix) { disallowedPackagePrefixes.add(packagePrefix + "."); } /** * Marks all resources with the given prefix as visible. * * @param resourcePrefix the resource prefix */ public void allowResources(String resourcePrefix) { resourcePrefixes.add(resourcePrefix + "/"); } /** * Marks a single resource as visible. * * @param resourceName the resource name */ public void allowResource(String resourceName) { resourceNames.add(resourceName); } @Override public boolean equals(Object obj) { if (obj == this) { return true; } if (obj == null || obj.getClass() != getClass()) { return false; } Spec other = (Spec) obj; return other.packageNames.equals(packageNames) && other.packagePrefixes.equals(packagePrefixes) && other.resourceNames.equals(resourceNames) && other.resourcePrefixes.equals(resourcePrefixes) && other.classNames.equals(classNames) && other.disallowedClassNames.equals(disallowedClassNames) && other.disallowedPackagePrefixes.equals(disallowedPackagePrefixes); } @Override public int hashCode() { return packageNames.hashCode() ^ packagePrefixes.hashCode() ^ resourceNames.hashCode() ^ resourcePrefixes.hashCode() ^ classNames.hashCode() ^ disallowedClassNames.hashCode() ^ disallowedPackagePrefixes.hashCode(); } Set<String> getPackageNames() { return packageNames; } Set<String> getPackagePrefixes() { return packagePrefixes; } Set<String> getResourcePrefixes() { return resourcePrefixes; } Set<String> getResourceNames() { return resourceNames; } Set<String> getClassNames() { return classNames; } Set<String> getDisallowedClassNames() { return disallowedClassNames; } Set<String> getDisallowedPackagePrefixes() { return disallowedPackagePrefixes; } } }