|
|
@@ -26,8 +26,9 @@ import java.util.List; |
|
|
|
|
|
|
|
/** |
|
|
|
* One instance of this class should be created by plugin manager for every available plug-in. |
|
|
|
* This class loader is a Parent Last ClassLoader - it loads the classes from the plugin's jars |
|
|
|
* By default, this class loader is a Parent Last ClassLoader - it loads the classes from the plugin's jars |
|
|
|
* before delegating to the parent class loader. |
|
|
|
* Use {@link #parentFirst} to change the loading strategy. |
|
|
|
* |
|
|
|
* @author Decebal Suiu |
|
|
|
*/ |
|
|
@@ -35,19 +36,30 @@ public class PluginClassLoader extends URLClassLoader { |
|
|
|
|
|
|
|
private static final Logger log = LoggerFactory.getLogger(PluginClassLoader.class); |
|
|
|
|
|
|
|
private static final String JAVA_PACKAGE_PREFIX = "java."; |
|
|
|
private static final String PLUGIN_PACKAGE_PREFIX = "org.pf4j."; |
|
|
|
|
|
|
|
private PluginManager pluginManager; |
|
|
|
private PluginDescriptor pluginDescriptor; |
|
|
|
private boolean parentFirst; |
|
|
|
|
|
|
|
public PluginClassLoader(PluginManager pluginManager, PluginDescriptor pluginDescriptor, ClassLoader parent) { |
|
|
|
public PluginClassLoader(PluginManager pluginManager, PluginDescriptor pluginDescriptor, ClassLoader parent) { |
|
|
|
this(pluginManager, pluginDescriptor, parent, false); |
|
|
|
} |
|
|
|
|
|
|
|
/** |
|
|
|
* If {@code parentFirst} is {@code true}, indicates that the parent {@link ClassLoader} should be consulted |
|
|
|
* before trying to load the a class through this loader. |
|
|
|
*/ |
|
|
|
public PluginClassLoader(PluginManager pluginManager, PluginDescriptor pluginDescriptor, ClassLoader parent, boolean parentFirst) { |
|
|
|
super(new URL[0], parent); |
|
|
|
|
|
|
|
this.pluginManager = pluginManager; |
|
|
|
this.pluginDescriptor = pluginDescriptor; |
|
|
|
this.parentFirst = parentFirst; |
|
|
|
} |
|
|
|
|
|
|
|
@Override |
|
|
|
@Override |
|
|
|
public void addURL(URL url) { |
|
|
|
log.debug("Add '{}'", url); |
|
|
|
super.addURL(url); |
|
|
@@ -63,83 +75,133 @@ public class PluginClassLoader extends URLClassLoader { |
|
|
|
} |
|
|
|
|
|
|
|
/** |
|
|
|
* Uses a child first delegation model rather than the standard parent first. |
|
|
|
* By default, it uses a child first delegation model rather than the standard parent first. |
|
|
|
* If the requested class cannot be found in this class loader, the parent class loader will be consulted |
|
|
|
* via the standard {@link ClassLoader#loadClass(String)} mechanism. |
|
|
|
* Use {@link #parentFirst} to change the loading strategy. |
|
|
|
*/ |
|
|
|
@Override |
|
|
|
public Class<?> loadClass(String className) throws ClassNotFoundException { |
|
|
|
synchronized (getClassLoadingLock(className)) { |
|
|
|
log.trace("Received request to load class '{}'", className); |
|
|
|
// first check whether it's a system class, delegate to the system loader |
|
|
|
if (className.startsWith(JAVA_PACKAGE_PREFIX)) { |
|
|
|
return findSystemClass(className); |
|
|
|
} |
|
|
|
|
|
|
|
// if the class it's a part of the plugin engine use parent class loader |
|
|
|
if (className.startsWith(PLUGIN_PACKAGE_PREFIX)) { |
|
|
|
log.trace("Delegate the loading of class '{}' to parent", className); |
|
|
|
try { |
|
|
|
return getClass().getClassLoader().loadClass(className); |
|
|
|
} catch (ClassNotFoundException e) { |
|
|
|
// try next step |
|
|
|
} |
|
|
|
if (className.startsWith(PLUGIN_PACKAGE_PREFIX) && !className.startsWith("org.pf4j.demo")) { |
|
|
|
// log.trace("Delegate the loading of PF4J class '{}' to parent", className); |
|
|
|
return getParent().loadClass(className); |
|
|
|
} |
|
|
|
|
|
|
|
log.trace("Received request to load class '{}'", className); |
|
|
|
|
|
|
|
// second check whether it's already been loaded |
|
|
|
Class<?> clazz = findLoadedClass(className); |
|
|
|
if (clazz != null) { |
|
|
|
Class<?> loadedClass = findLoadedClass(className); |
|
|
|
if (loadedClass != null) { |
|
|
|
log.trace("Found loaded class '{}'", className); |
|
|
|
return clazz; |
|
|
|
return loadedClass; |
|
|
|
} |
|
|
|
|
|
|
|
// nope, try to load locally |
|
|
|
try { |
|
|
|
clazz = findClass(className); |
|
|
|
log.trace("Found class '{}' in plugin classpath", className); |
|
|
|
return clazz; |
|
|
|
} catch (ClassNotFoundException e) { |
|
|
|
// try next step |
|
|
|
} |
|
|
|
if (!parentFirst) { |
|
|
|
// nope, try to load locally |
|
|
|
try { |
|
|
|
loadedClass = findClass(className); |
|
|
|
log.trace("Found class '{}' in plugin classpath", className); |
|
|
|
return loadedClass; |
|
|
|
} catch (ClassNotFoundException e) { |
|
|
|
// try next step |
|
|
|
} |
|
|
|
|
|
|
|
// look in dependencies |
|
|
|
loadedClass = loadClassFromDependencies(className); |
|
|
|
if (loadedClass != null) { |
|
|
|
log.trace("Found class '{}' in dependencies", className); |
|
|
|
return loadedClass; |
|
|
|
} |
|
|
|
|
|
|
|
log.trace("Couldn't find class '{}' in plugin classpath. Delegating to parent", className); |
|
|
|
|
|
|
|
// use the standard ClassLoader (which follows normal parent delegation) |
|
|
|
return super.loadClass(className); |
|
|
|
} else { |
|
|
|
// try to load from parent |
|
|
|
try { |
|
|
|
return super.loadClass(className); |
|
|
|
} catch (ClassCastException e) { |
|
|
|
// try next step |
|
|
|
} |
|
|
|
|
|
|
|
// look in dependencies |
|
|
|
log.trace("Search in dependencies for class '{}'", className); |
|
|
|
List<PluginDependency> dependencies = pluginDescriptor.getDependencies(); |
|
|
|
for (PluginDependency dependency : dependencies) { |
|
|
|
ClassLoader classLoader = pluginManager.getPluginClassLoader(dependency.getPluginId()); |
|
|
|
log.trace("Couldn't find class '{}' in parent. Delegating to plugin classpath", className); |
|
|
|
|
|
|
|
// nope, try to load locally |
|
|
|
try { |
|
|
|
return classLoader.loadClass(className); |
|
|
|
loadedClass = findClass(className); |
|
|
|
log.trace("Found class '{}' in plugin classpath", className); |
|
|
|
return loadedClass; |
|
|
|
} catch (ClassNotFoundException e) { |
|
|
|
// try next dependency |
|
|
|
// try next step |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
log.trace("Couldn't find class '{}' in plugin classpath. Delegating to parent", className); |
|
|
|
// look in dependencies |
|
|
|
loadedClass = loadClassFromDependencies(className); |
|
|
|
if (loadedClass != null) { |
|
|
|
log.trace("Found class '{}' in dependencies", className); |
|
|
|
return loadedClass; |
|
|
|
} |
|
|
|
|
|
|
|
// use the standard ClassLoader (which follows normal parent delegation) |
|
|
|
return super.loadClass(className); |
|
|
|
throw new ClassNotFoundException(className); |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
/** |
|
|
|
* Load the named resource from this plugin. |
|
|
|
* This implementation checks the plugin's classpath first then delegates to the parent. |
|
|
|
* By default, this implementation checks the plugin's classpath first then delegates to the parent. |
|
|
|
* Use {@link #parentFirst} to change the loading strategy. |
|
|
|
* |
|
|
|
* @param name the name of the resource. |
|
|
|
* @return the URL to the resource, <code>null</code> if the resource was not found. |
|
|
|
* @return the URL to the resource, {@code null} if the resource was not found. |
|
|
|
*/ |
|
|
|
@Override |
|
|
|
public URL getResource(String name) { |
|
|
|
log.trace("Trying to find resource '{}' in plugin classpath", name); |
|
|
|
URL url = findResource(name); |
|
|
|
if (url != null) { |
|
|
|
log.trace("Found resource '{}' in plugin classpath", name); |
|
|
|
return url; |
|
|
|
} |
|
|
|
log.trace("Received request to load resource '{}'", name); |
|
|
|
if (!parentFirst) { |
|
|
|
URL url = findResource(name); |
|
|
|
if (url != null) { |
|
|
|
log.trace("Found resource '{}' in plugin classpath", name); |
|
|
|
return url; |
|
|
|
} |
|
|
|
|
|
|
|
log.trace("Couldn't find resource '{}' in plugin classpath. Delegating to parent", name); |
|
|
|
|
|
|
|
return super.getResource(name); |
|
|
|
} else { |
|
|
|
URL url = super.getResource(name); |
|
|
|
if (url != null) { |
|
|
|
log.trace("Found resource '{}' in parent", name); |
|
|
|
return url; |
|
|
|
} |
|
|
|
|
|
|
|
log.trace("Couldn't find resource '{}' in plugin classpath. Delegating to parent"); |
|
|
|
log.trace("Couldn't find resource '{}' in parent", name); |
|
|
|
|
|
|
|
return super.getResource(name); |
|
|
|
return findResource(name); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
@Override |
|
|
|
public URL findResource(String name) { |
|
|
|
return super.findResource(name); |
|
|
|
private Class<?> loadClassFromDependencies(String className) { |
|
|
|
log.trace("Search in dependencies for class '{}'", className); |
|
|
|
List<PluginDependency> dependencies = pluginDescriptor.getDependencies(); |
|
|
|
for (PluginDependency dependency : dependencies) { |
|
|
|
ClassLoader classLoader = pluginManager.getPluginClassLoader(dependency.getPluginId()); |
|
|
|
try { |
|
|
|
return classLoader.loadClass(className); |
|
|
|
} catch (ClassNotFoundException e) { |
|
|
|
// try next dependency |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
return null; |
|
|
|
} |
|
|
|
|
|
|
|
} |