/**
* Copyright (C) 2001-2017 by RapidMiner and the contributors
*
* Complete list of developers available at our web site:
*
* http://rapidminer.com
*
* This program is free software: you can redistribute it and/or modify it under the terms of the
* GNU Affero General Public License as published by the Free Software Foundation, either version 3
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without
* even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License along with this program.
* If not, see http://www.gnu.org/licenses/.
*/
package com.rapidminer.tools.plugin;
import java.net.URL;
import java.net.URLClassLoader;
import java.security.AccessController;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Set;
import com.rapidminer.RapidMiner;
/**
* The class loader for a plugin (extending URLClassLoader). Since a plugin might depend on other
* plugins the URLs of these plugins are also added to the current class loader.
*
* @author Ingo Mierswa, Michael Knopf
*/
public class PluginClassLoader extends URLClassLoader {
private ArrayList<Plugin> dependencies = new ArrayList<>();
private String pluginKey = null;
private volatile boolean ignoreDependencyClassloaders;
/**
* @return {@code true} if the dependency classloaders are ignored by
* {@link #getResource(String)} and {@link #loadClass(String)}
*/
public boolean isIgnoreDependencyClassloaders() {
return ignoreDependencyClassloaders;
}
/**
* Specifies if the dependency classloaders are ignored by {@link #getResource(String)} and
* {@link #loadClass(String)}.
*
* @param ignoreDependencyClassloaders
* flag to ignore dependency classloaders when looking for resources or loading
* classes
*/
public void setIgnoreDependencyClassloaders(boolean ignoreDependencyClassloaders) {
this.ignoreDependencyClassloaders = ignoreDependencyClassloaders;
}
/**
* This constructor is for plugins that only depend on the core.
*
* @param urls
* These URLs will be used for class building.
*/
public PluginClassLoader(URL[] urls) {
super(urls, RapidMiner.class.getClassLoader());
}
@Deprecated
public PluginClassLoader(URL[] urls, ClassLoader parent) {
super(urls, parent);
}
/**
* This method can be used if for a plugin already is known which parent plugins are needed.
* Otherwise you can use the standard constructor and add the Dependencies later using
* {@link #addDependency(Plugin)}.
*
* @param urls
* @param parentPlugins
*/
public PluginClassLoader(URL[] urls, Plugin... parentPlugins) {
super(urls, RapidMiner.class.getClassLoader());
for (Plugin plugin : parentPlugins) {
this.dependencies.add(plugin);
}
}
/**
* Adds a plugin to the list of dependencies.
*
* @param dependency
* The new dependency.
*/
public void addDependency(Plugin dependency) {
dependencies.add(dependency);
}
@Override
protected synchronized Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
Class<?> clazz = null;
try {
clazz = super.loadClass(name, resolve);
} catch (ClassNotFoundException e) {
// ClassNotFoundException thrown if class not found
// from the urls registered nor the core class loader
}
// look into dependency classloaders if not found and we are allowed to do so
if (clazz == null && !ignoreDependencyClassloaders) {
for (Plugin plugin : dependencies) {
try {
return plugin.getClassLoader().loadClass(name, resolve);
} catch (ClassNotFoundException e) {
// ClassNotFoundException thrown if class not found
// from the parent extension
}
}
}
if (clazz == null) {
// If still not found, then invoke findClass in order
// to find the class.
clazz = findClass(name);
}
// if no class found during findClass an Exception is thrown anyway
if (resolve) {
resolveClass(clazz);
}
return clazz;
}
@Override
public URL getResource(String name) {
URL url = super.getResource(name);
// look into dependency classloaders if not found and we are allowed to do so
if (!ignoreDependencyClassloaders) {
for (Plugin dependency : dependencies) {
url = dependency.getClassLoader().getResource(name);
if (url != null) {
break;
}
}
}
if (url == null) {
url = findResource(name);
}
return url;
}
@Override
public String toString() {
return "PluginClassLoader (" + Arrays.asList(getURLs()) + ")";
}
/**
* Returns the key of the plugin for this classloader. Can be {@code null} if it has not been
* specified.
*
* @return the plugin key or {@code null}
*/
public String getPluginKey() {
return pluginKey;
}
/**
* Set the key of the plugin for this classloader.
*
* @param pluginKey
* the key
* @throws SecurityException
* if caller does not have {@link RuntimePermission} for {@code createClassLoader}
*/
public void setPluginKey(String pluginKey) {
if (System.getSecurityManager() != null) {
AccessController.checkPermission(new RuntimePermission("createClassLoader"));
}
this.pluginKey = pluginKey;
}
/**
* Returns a {@link Set} of {@link PluginClassLoader}s this {@link PluginClassLoader} depends on
* (i.e., the corresponding {@link Plugin} depends on):
*
* @return Set of dependency {@link PluginClassLoader}s.
*/
public Set<PluginClassLoader> getDependencyClassLoaders() {
Set<PluginClassLoader> classLoaders = new HashSet<>();
// add class loaders of dependencies
for (Plugin dependency : dependencies) {
PluginClassLoader dependencyClassLoader = dependency.getClassLoader();
// add the dependency itself
classLoaders.add(dependencyClassLoader);
// add the dependency's dependencies
classLoaders.addAll(dependencyClassLoader.getDependencyClassLoaders());
}
return classLoaders;
}
}