| /******************************************************************************* |
| * Copyright (c) 2005, 2016 IBM Corporation and others. |
| * |
| * This program and the accompanying materials |
| * are made available under the terms of the Eclipse Public License 2.0 |
| * which accompanies this distribution, and is available at |
| * https://www.eclipse.org/legal/epl-2.0/ |
| * |
| * SPDX-License-Identifier: EPL-2.0 |
| * |
| * Contributors: |
| * IBM Corporation - initial API and implementation |
| *******************************************************************************/ |
| package org.eclipse.osgi.internal.framework; |
| |
| import java.io.IOException; |
| import java.net.URL; |
| import java.security.AccessController; |
| import java.security.PrivilegedAction; |
| import java.util.ArrayList; |
| import java.util.Collections; |
| import java.util.Enumeration; |
| import java.util.HashSet; |
| import java.util.Iterator; |
| import java.util.List; |
| import java.util.Set; |
| import org.eclipse.osgi.internal.loader.BundleLoader; |
| import org.eclipse.osgi.internal.loader.ModuleClassLoader; |
| |
| public class ContextFinder extends ClassLoader implements PrivilegedAction<List<ClassLoader>> { |
| static final class Finder extends SecurityManager { |
| @Override |
| public Class<?>[] getClassContext() { |
| return super.getClassContext(); |
| } |
| } |
| |
| //This is used to detect cycle that could be caused while delegating the loading to other classloaders |
| //It keeps track on a thread basis of the set of requested classes and resources |
| private static ThreadLocal<Set<String>> cycleDetector = new ThreadLocal<>(); |
| static ClassLoader finderClassLoader; |
| static Finder contextFinder; |
| static { |
| AccessController.doPrivileged(new PrivilegedAction<Void>() { |
| @Override |
| public Void run() { |
| finderClassLoader = ContextFinder.class.getClassLoader(); |
| contextFinder = new Finder(); |
| return null; |
| } |
| }); |
| } |
| |
| private static Class<ContextFinder> THIS = ContextFinder.class; |
| |
| private final ClassLoader parentContextClassLoader; |
| |
| public ContextFinder(ClassLoader contextClassLoader, ClassLoader bootLoader) { |
| super(contextClassLoader); |
| this.parentContextClassLoader = contextClassLoader != null ? contextClassLoader : bootLoader; |
| } |
| |
| // Return a list of all classloaders on the stack that are neither the |
| // ContextFinder classloader nor the boot classloader. The last classloader |
| // in the list is either a bundle classloader or the framework's classloader |
| // We assume that the bootclassloader never uses the context classloader to find classes in itself. |
| List<ClassLoader> basicFindClassLoaders() { |
| Class<?>[] stack = contextFinder.getClassContext(); |
| List<ClassLoader> result = new ArrayList<>(1); |
| ClassLoader previousLoader = null; |
| for (int i = 1; i < stack.length; i++) { |
| ClassLoader tmp = stack[i].getClassLoader(); |
| if (stack[i] != THIS && tmp != null && tmp != this) { |
| if (checkClassLoader(tmp)) { |
| if (previousLoader != tmp) { |
| result.add(tmp); |
| previousLoader = tmp; |
| } |
| } |
| // stop at the framework classloader or the first bundle classloader |
| if (tmp == finderClassLoader || tmp instanceof ModuleClassLoader) |
| break; |
| } |
| } |
| return result; |
| } |
| |
| // ensures that a classloader does not have the ContextFinder as part of the |
| // parent hierachy. A classloader which has the ContextFinder as a parent must |
| // not be used as a delegate, otherwise we endup in endless recursion. |
| private boolean checkClassLoader(ClassLoader classloader) { |
| if (classloader == null || classloader == getParent()) |
| return false; |
| for (ClassLoader parent = classloader.getParent(); parent != null; parent = parent.getParent()) |
| if (parent == this) |
| return false; |
| return true; |
| } |
| |
| private List<ClassLoader> findClassLoaders() { |
| if (System.getSecurityManager() == null) |
| return basicFindClassLoaders(); |
| return AccessController.doPrivileged(this); |
| } |
| |
| @Override |
| public List<ClassLoader> run() { |
| return basicFindClassLoaders(); |
| } |
| |
| //Return whether the request for loading "name" should proceed. |
| //False is returned when a cycle is being detected |
| private boolean startLoading(String name) { |
| Set<String> classesAndResources = cycleDetector.get(); |
| if (classesAndResources != null && classesAndResources.contains(name)) |
| return false; |
| |
| if (classesAndResources == null) { |
| classesAndResources = new HashSet<>(3); |
| cycleDetector.set(classesAndResources); |
| } |
| classesAndResources.add(name); |
| return true; |
| } |
| |
| private void stopLoading(String name) { |
| cycleDetector.get().remove(name); |
| } |
| |
| @Override |
| protected Class<?> loadClass(String arg0, boolean arg1) throws ClassNotFoundException { |
| //Shortcut cycle |
| if (startLoading(arg0) == false) |
| throw new ClassNotFoundException(arg0); |
| |
| try { |
| List<ClassLoader> toConsult = findClassLoaders(); |
| for (Iterator<ClassLoader> loaders = toConsult.iterator(); loaders.hasNext();) |
| try { |
| return loaders.next().loadClass(arg0); |
| } catch (ClassNotFoundException e) { |
| // go to the next class loader |
| } |
| // avoid calling super.loadClass here because it checks the local cache (bug 127963) |
| return parentContextClassLoader.loadClass(arg0); |
| } finally { |
| stopLoading(arg0); |
| } |
| } |
| |
| @Override |
| public URL getResource(String arg0) { |
| //Shortcut cycle |
| if (startLoading(arg0) == false) |
| return null; |
| try { |
| List<ClassLoader> toConsult = findClassLoaders(); |
| for (Iterator<ClassLoader> loaders = toConsult.iterator(); loaders.hasNext();) { |
| URL result = loaders.next().getResource(arg0); |
| if (result != null) |
| return result; |
| // go to the next class loader |
| } |
| return super.getResource(arg0); |
| } finally { |
| stopLoading(arg0); |
| } |
| } |
| |
| @Override |
| public Enumeration<URL> getResources(String arg0) throws IOException { |
| //Shortcut cycle |
| if (startLoading(arg0) == false) { |
| return Collections.enumeration(Collections.<URL> emptyList()); |
| } |
| try { |
| List<ClassLoader> toConsult = findClassLoaders(); |
| Enumeration<URL> result = null; |
| for (Iterator<ClassLoader> loaders = toConsult.iterator(); loaders.hasNext();) { |
| result = loaders.next().getResources(arg0); |
| if (result != null && result.hasMoreElements()) { |
| // For context finder we do not compound results after this first loader that has resources |
| break; |
| } |
| // no results yet, go to the next class loader |
| } |
| return BundleLoader.compoundEnumerations(result, super.getResources(arg0)); |
| } finally { |
| stopLoading(arg0); |
| } |
| } |
| } |