/*
* RapidMiner
*
* Copyright (C) 2001-2011 by Rapid-I and the contributors
*
* Complete list of developers available at our web site:
*
* http://rapid-i.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.awt.Frame;
import java.awt.Image;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileOutputStream;
import java.io.FilenameFilter;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLClassLoader;
import java.security.AccessController;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.PrivilegedActionException;
import java.security.PrivilegedExceptionAction;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Locale;
import java.util.Properties;
import java.util.ResourceBundle;
import java.util.Set;
import java.util.jar.Attributes;
import java.util.jar.JarFile;
import java.util.jar.Manifest;
import java.util.logging.Level;
import javax.imageio.ImageIO;
import javax.swing.ImageIcon;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NodeList;
import com.rapid_i.Launcher;
import com.rapid_i.deployment.update.client.ManagedExtension;
import com.rapidminer.RapidMiner;
import com.rapidminer.RapidMiner.ExecutionMode;
import com.rapidminer.gui.MainFrame;
import com.rapidminer.gui.flow.ProcessRenderer;
import com.rapidminer.gui.renderer.RendererService;
import com.rapidminer.gui.templates.BuildingBlock;
import com.rapidminer.gui.tools.SplashScreen;
import com.rapidminer.gui.tools.dialogs.AboutBox;
import com.rapidminer.io.Base64;
import com.rapidminer.io.process.XMLImporter;
import com.rapidminer.io.process.XMLTools;
import com.rapidminer.tools.FileSystemService;
import com.rapidminer.tools.I18N;
import com.rapidminer.tools.LogService;
import com.rapidminer.tools.OperatorService;
import com.rapidminer.tools.ParameterService;
import com.rapidminer.tools.ResourceSource;
import com.rapidminer.tools.Tools;
/**
* <p>
* The class for RapidMiner plugins. This class is used to encapsulate the .jar file which must be in the
* <code>lib/plugins</code> subdirectory of RapidMiner. Provides methods for plugin checks, operator registering, and
* getting information about the plugin.
* </p>
* <p>
* Plugin dependencies must be defined in the form <br />
* plugin_name1 (plugin_version1) # ... # plugin_nameM (plugin_versionM) < /br> of the manifest parameter
* <code>Plugin-Dependencies</code>. You must define both the name and the version of the desired plugins and separate
* them with "#".
* </p>
*
* @author Simon Fischer, Ingo Mierswa
*/
public class Plugin {
/**
* The name for the manifest entry RapidMiner-Type which can be used to indicate that a jar file is a RapidMiner
* plugin.
*/
public static final String RAPIDMINER_TYPE = "RapidMiner-Type";
/** The value for the manifest entry RapidMiner-Type which indicates that a jar file is a RapidMiner plugin. */
public static final String RAPIDMINER_TYPE_PLUGIN = "RapidMiner_Extension";
private static final ClassLoader MAJOR_CLASS_LOADER;
static {
try {
MAJOR_CLASS_LOADER = AccessController.doPrivileged(new PrivilegedExceptionAction<ClassLoader>() {
@Override
public ClassLoader run() throws Exception {
return new AllPluginsClassLoader();
}
});
} catch (PrivilegedActionException e) {
throw new RuntimeException("Cannot create major class loader: " + e.getMessage(), e);
}
}
/**
* The jar archive of the plugin which must be placed in the <code>lib/plugins</code> subdirectory of RapidMiner.
*/
private final JarFile archive;
/** The file for this plugin. */
private final File file;
/** The class loader based on the plugin file. */
private PluginClassLoader classLoader;
/** The name of the plugin. */
private String name;
/** The version of the plugin. */
private String version;
/** The vendor of the plugin. */
private String vendor;
/** The url for this plugin (in WWW). */
private String url;
/** The RapidMiner version which is needed for this plugin. */
private String necessaryRapidMinerVersion = "0";
/** The plugins and their versions which are needed for this plugin. */
private final List<Dependency> pluginDependencies = new LinkedList<Dependency>();
private String extensionId;
private String pluginInitClassName;
private String pluginResourceObjects;
private String pluginResourceOperators;
private String pluginParseRules;
private String pluginGroupDescriptions;
private String pluginErrorDescriptions;
private String pluginUserErrorDescriptions;
private String pluginGUIDescriptions;
private String prefix;
private boolean disabled = false;
/** The collection of all plugins. */
private static final List<Plugin> allPlugins = new LinkedList<Plugin>();
/** Creates a new plugin based on the plugin .jar file. */
public Plugin(File file) throws IOException {
this.file = file;
this.archive = new JarFile(this.file);
this.classLoader = makeInitialClassloader();
Tools.addResourceSource(new ResourceSource(this.classLoader));
fetchMetaData();
if (!RapidMiner.getExecutionMode().isHeadless()) {
RapidMiner.getSplashScreen().addExtension(this);
}
}
/**
* This method will create an initial class loader that is only used to
* access the manifest.
* After the manifest is read, a new class loader will be constructed from
* all dependencies.
*/
private PluginClassLoader makeInitialClassloader() {
URL url;
try {
url = this.file.toURI().toURL();
} catch (MalformedURLException e) {
throw new RuntimeException("Cannot make classloader for plugin: " + e, e);
}
final PluginClassLoader cl = new PluginClassLoader(new URL[] { url });
return cl;
}
/**
* This method will build the final class loader for this plugin
* that contains all class loaders of all plugins this plugin depends on.
*
* This must be called after all plugins have been initially loaded.
*/
public void buildFinalClassLoader() {
// add URLs of plugins this plugin depends on
for (Dependency dependency: this.pluginDependencies) {
final Plugin other = getPluginByExtensionId(dependency.getPluginExtensionId());
classLoader.addDependency(other);
}
}
/** Returns the name of the plugin. */
public String getName() {
return name;
}
/** Returns the version of this plugin. */
public String getVersion() {
return version;
}
/** Returns the necessary RapidMiner version. */
public String getNecessaryRapidMinerVersion() {
return necessaryRapidMinerVersion;
}
/**
* Returns the class name of the plugin init class
*/
public String getPluginInitClassName() {
return pluginInitClassName;
}
public String getPluginParseRules() {
return pluginParseRules;
}
public String getPluginGroupDescriptions() {
return pluginGroupDescriptions;
}
public String getPluginErrorDescriptions() {
return pluginErrorDescriptions;
}
public String getPluginUserErrorDescriptions() {
return pluginUserErrorDescriptions;
}
public String getPluginGUIDescriptions() {
return pluginGUIDescriptions;
}
/**
* Returns the resource identifier of the xml file specifying the operators
*/
public String getPluginResourceOperators() {
return pluginResourceOperators;
}
/**
* Returns the resource identifier of the IO Object descriptions.
*/
public String getPluginResourceObjects() {
return pluginResourceObjects;
}
/** Returns the plugin dependencies of this plugin. */
public List getPluginDependencies() {
return pluginDependencies;
}
/**
* Returns the class loader of this plugin. This class loader should be used in cases where Class.forName(...)
* should be used, e.g. for implementation finding in all classes (including the core and the plugins).
*/
public PluginClassLoader getClassLoader() {
return this.classLoader;
}
/**
* Returns the class loader of this plugin. This class loader should be used in cases where Class.forName(...)
* should find a class explicitly defined in this plugin jar.
*/
public ClassLoader getOriginalClassLoader() {
try {
// this.archive = new JarFile(this.file);
final URL url = new URL("file", null, this.file.getAbsolutePath());
return AccessController.doPrivileged(new PrivilegedExceptionAction<ClassLoader>() {
@Override
public ClassLoader run() throws Exception {
return new URLClassLoader(new URL[] { url }, Plugin.class.getClassLoader());
}
});
} catch (IOException e) {
return null;
} catch (PrivilegedActionException e) {
return null;
}
}
/** Checks the RapidMiner version and plugin dependencies. */
private boolean checkDependencies(List plugins) {
if (RapidMiner.getLongVersion().compareTo(necessaryRapidMinerVersion) < 0)
return false;
if (pluginDependencies.size() > 1) {
throw new UnsupportedOperationException("Only one dependent plugin allowed!");
}
// other plugins
Iterator i = pluginDependencies.iterator();
while (i.hasNext()) {
Dependency dependency = (Dependency) i.next();
if (!dependency.isFulfilled(plugins))
return false;
}
// all ok
return true;
}
/** Collects all meta data of the plugin from the manifest file. */
private void fetchMetaData() {
try {
java.util.jar.Attributes atts = archive.getManifest().getMainAttributes();
name = getValue(atts, "Implementation-Title");
if (name == null) {
name = archive.getName();
}
version = getValue(atts, "Implementation-Version");
if (version == null)
version = "";
url = getValue(atts, "Implementation-URL");
vendor = getValue(atts, "Implementation-Vendor");
prefix = getValue(atts, "Namespace");
extensionId = getValue(atts, "Extension-ID");
pluginInitClassName = getValue(atts, "Initialization-Class");
pluginResourceObjects = getDescriptorResource("IOObject-Descriptor", false, false, atts);
pluginResourceOperators = getDescriptorResource("Operator-Descriptor", false, true, atts);
pluginParseRules = getDescriptorResource("ParseRule-Descriptor", false, false, atts);
pluginGroupDescriptions = getDescriptorResource("Group-Descriptor", false, false, atts);
pluginErrorDescriptions = getDescriptorResource("Error-Descriptor", false, true, atts);
pluginUserErrorDescriptions = getDescriptorResource("UserError-Descriptor", false, true, atts);
pluginGUIDescriptions = getDescriptorResource("GUI-Descriptor", false, true, atts);
necessaryRapidMinerVersion = getValue(atts, "RapidMiner-Version");
if (necessaryRapidMinerVersion == null) {
necessaryRapidMinerVersion = "0";
}
String dependencies = getValue(atts, "Plugin-Dependencies");
if (dependencies == null)
dependencies = "";
addDependencies(dependencies);
RapidMiner.splashMessage("loading_plugin", name);
} catch (Exception e) {
e.printStackTrace();
}
}
private String getValue(java.util.jar.Attributes atts, String key) {
String result = atts.getValue(key);
if (result == null) {
return null;
} else {
result = result.trim();
if (result.isEmpty()) {
return null;
} else {
return result;
}
}
}
private String getDescriptorResource(String typeName, boolean mandatory, boolean isBundle, java.util.jar.Attributes atts) throws IOException {
String value = getValue(atts, typeName);
if (value == null) {
if (mandatory) {
throw new IOException("Manifest attribute '" + typeName + "' is not defined.");
} else {
return null;
}
} else {
if (isBundle) {
return toResourceBundleIdentifier(value);
} else {
return toResourceIdentifier(value);
}
}
}
private String toResourceBundleIdentifier(String value) {
if (value.startsWith("/"))
value = value.substring(1);
if (value.endsWith(".properties")) {
value = value.substring(0, value.length() - 11);
}
return value;
}
/**
* Removes leading slash if present.
*/
private String toResourceIdentifier(String value) {
if (value.startsWith("/"))
value = value.substring(1);
return value;
}
/** Register plugin dependencies. */
private void addDependencies(String dependencies) {
pluginDependencies.addAll(Dependency.parse(dependencies));
}
public void registerOperators() {
if (disabled) {
LogService.getRoot().warning("Plugin " + getName() + " disabled due to previous errors. Not registering operators.");
}
InputStream in = null;
// trying normal plugins
if (pluginResourceOperators != null) {
URL operatorsURL = this.classLoader.getResource(pluginResourceOperators);
if (operatorsURL == null) {
LogService.getRoot().log(Level.WARNING, "Operator descriptor '" + pluginResourceOperators + "' does not exist in '" + archive.getName() + "'!");
return;
} else {
// register operators
try {
in = operatorsURL.openStream();
} catch (IOException e) {
LogService.getRoot().log(Level.WARNING, "Cannot read operator descriptor '" + operatorsURL + "' from '" + archive.getName() + "'!", e);
return;
}
}
} else if (pluginInitClassName != null) {
LogService.getRoot().info("No operator descriptor specified for plugin " + getName() + ". Trying plugin initializtation class " + pluginInitClassName + ".");
// if no operators.xml found: Try via PluginInit method getOperatorStream()
try {
// important: here the combined class loader has to be used
Class<?> pluginInitator = Class.forName(pluginInitClassName, false, getClassLoader());
Method registerOperatorMethod = pluginInitator.getMethod("getOperatorStream", new Class[] { ClassLoader.class });
in = (InputStream) registerOperatorMethod.invoke(null, new Object[] { getClassLoader() });
} catch (ClassNotFoundException e) {
} catch (SecurityException e) {
} catch (NoSuchMethodException e) {
} catch (IllegalArgumentException e) {
} catch (IllegalAccessException e) {
} catch (InvocationTargetException e) {
}
}
if (in != null) {
OperatorService.registerOperators(archive.getName(), in, this.classLoader, this);
} else {
LogService.getRoot().warning("No operator descriptor defined for: " + getName());
}
}
/**
* Register all things delivered with this plugin.
*
* @throws PluginException
*/
public void registerDescriptions() throws PluginException {
// registering settings for internationalization
if (pluginErrorDescriptions != null)
I18N.registerErrorBundle(ResourceBundle.getBundle(pluginErrorDescriptions, Locale.getDefault(), this.classLoader));
if (pluginGUIDescriptions != null)
I18N.registerGUIBundle(ResourceBundle.getBundle(pluginGUIDescriptions, Locale.getDefault(), this.classLoader));
if (pluginUserErrorDescriptions != null)
I18N.registerUserErrorMessagesBundle(ResourceBundle.getBundle(pluginUserErrorDescriptions, Locale.getDefault(), this.classLoader));
// registering renderers
if (pluginResourceObjects != null) {
URL resource = this.classLoader.getResource(pluginResourceObjects);
if (resource != null) {
RendererService.init(name, resource, this.classLoader);
} else {
throw new PluginException("Cannot find io object descriptor '" + pluginResourceObjects + "' for plugin " + getName() + ".");
}
}
// registering parse rules
if (pluginParseRules != null) {
URL resource = this.classLoader.getResource(pluginParseRules);
if (resource != null) {
XMLImporter.importParseRules(resource, this);
} else {
throw new PluginException("Cannot find parse rules '" + pluginParseRules + "' for plugin " + getName() + ".");
}
}
// registering colors
if (pluginGroupDescriptions != null) {
ProcessRenderer.registerAdditionalObjectColors(pluginGroupDescriptions, name, classLoader);
ProcessRenderer.registerAdditionalGroupColors(pluginGroupDescriptions, name, classLoader);
}
}
/**
* Returns a list of building blocks. If this plugin does not define any building blocks, an empty list will be
* returned.
*/
public List<BuildingBlock> getBuildingBlocks() {
List<BuildingBlock> result = new LinkedList<BuildingBlock>();
URL url = null;
try {
url = new URL("file", null, this.file.getAbsolutePath());
} catch (MalformedURLException e1) {
LogService.getRoot().log(Level.WARNING, "Cannot load plugin building blocks. Skipping...", e1);
}
if (url != null) {
// TODO: Check why we have to build an independentLoader? If yes: Use doPriviledged.
// ClassLoader independentLoader = new PluginClassLoader(new URL[] { url });
// URL bbDefinition = independentLoader.getResource(Tools.RESOURCE_PREFIX + "buildingblocks.txt");
URL bbDefinition = classLoader.getResource(Tools.RESOURCE_PREFIX + "buildingblocks.txt");
if (bbDefinition != null) {
BufferedReader in = null;
try {
in = new BufferedReader(new InputStreamReader(bbDefinition.openStream()));
String line = null;
while ((line = in.readLine()) != null) {
URL bbURL = this.classLoader.getResource(Tools.RESOURCE_PREFIX + line);
BufferedReader bbIn = null;
try {
bbIn = new BufferedReader(new InputStreamReader(bbURL.openStream()));
result.add(new BuildingBlock(bbIn, BuildingBlock.PLUGIN_DEFINED));
} catch (IOException e) {
LogService.getRoot().log(Level.WARNING, "Cannot load plugin building blocks. Skipping...", e);
} finally {
if (bbIn != null) {
bbIn.close();
}
}
}
} catch (IOException e) {
LogService.getRoot().log(Level.WARNING, "Cannot load plugin building blocks.", e);
} finally {
if (in != null) {
try {
in.close();
} catch (IOException e) {
LogService.getRoot().log(Level.WARNING, "Cannot close stream to plugin building blocks.", e);
}
}
}
}
}
return result;
}
/** Creates the about box for this plugin. */
public AboutBox createAboutBox(Frame owner) {
ClassLoader simpleClassLoader = makeInitialClassloader();
String about = "";
try {
URL url = simpleClassLoader.getResource("META-INF/ABOUT.NFO");
if (url != null)
about = Tools.readTextFile(new InputStreamReader(url.openStream()));
} catch (Exception e) {
LogService.getRoot().log(Level.WARNING, "Error reading ABOUT.NFO for plugin " + getName(), e);
}
Image productLogo = null;
try {
InputStream imageIn = simpleClassLoader.getResourceAsStream("META-INF/icon.png");
productLogo = ImageIO.read(imageIn);
} catch (Exception e) {
LogService.getRoot().log(Level.WARNING, "Error reading icon.png for plugin " + getName(), e);
}
return new AboutBox(owner, name, version, "Vendor: " + (vendor != null ? vendor : "unknown"), url, about, true, productLogo);
}
/** Scans the directory for jar files and calls {@link #registerPlugins(List, boolean)} on the list of files. */
private static void findAndRegisterPlugins(File pluginDir, boolean showWarningForNonPluginJars) {
List<File> files = new LinkedList<File>();
if (pluginDir == null) {
LogService.getRoot().warning("findAndRegisterPlugins called with null directory.");
return;
}
if (!(pluginDir.exists() && pluginDir.isDirectory())) {
LogService.getRoot().config("Plugin directory " + pluginDir + " does not exist.");
} else {
LogService.getRoot().config("Scanning plugins in " + pluginDir + ".");
files.addAll(Arrays.asList(pluginDir.listFiles(new FilenameFilter() {
@Override
public boolean accept(File dir, String name) {
return name.endsWith(".jar");
}
})));
}
registerPlugins(files, showWarningForNonPluginJars);
}
/** Makes {@link Plugin} s from all files and adds them to {@link #allPlugins}.
* After all Plugins are loaded, they must be assigend their final class loader.
* */
private static void registerPlugins(List<File> files, boolean showWarningForNonPluginJars) {
for (File file : files) {
try {
JarFile jarFile = new JarFile(file);
Manifest manifest = jarFile.getManifest();
Attributes attributes = manifest.getMainAttributes();
if (RAPIDMINER_TYPE_PLUGIN.equals(attributes.getValue(RAPIDMINER_TYPE))) {
final Plugin plugin = new Plugin(file);
final Plugin conflict = getPluginByExtensionId(plugin.getExtensionId());
if (conflict == null) {
allPlugins.add(plugin);
} else {
LogService.getRoot().warning("Duplicate plugin definition for plugin " + plugin.getExtensionId() + " in " + conflict.file + " and " + file + ". Keeping the first.");
}
} else {
if (showWarningForNonPluginJars)
LogService.getRoot().warning("The jar file '" + jarFile.getName() + "' does not contain an entry '" + RAPIDMINER_TYPE + "' in its manifest and will therefore not be loaded (if this file actually is a plugin updating the plugin file might help).");
}
} catch (Throwable e) {
LogService.getRoot().log(Level.WARNING, "Cannot load plugin '" + file + "': " + e.getMessage(), e);
}
}
}
@Override
public String toString() {
return name + " " + version + " (" + archive.getName() + ") depending on " + pluginDependencies;
}
/**
* Finds all plugins in lib/plugins directory and initializes them.
*/
private static void registerAllPluginDescriptions() {
Iterator<Plugin> i = allPlugins.iterator();
while (i.hasNext()) {
Plugin plugin = i.next();
if (!plugin.checkDependencies(allPlugins)) {
LogService.getRoot().warning("Cannot register operators from '" + plugin.getName() + "': Dependencies not fulfilled! This plugin needs a RapidMiner version " + plugin.getNecessaryRapidMinerVersion() + " and the following plugins:" + Tools.getLineSeparator() + plugin.getPluginDependencies());
plugin.disabled = true;
i.remove();
}
}
if (allPlugins.size() > 0) {
i = allPlugins.iterator();
while (i.hasNext()) {
Plugin plugin = i.next();
try {
plugin.registerDescriptions();
} catch (Exception e) {
LogService.getRoot().log(Level.WARNING, "Error initializing plugin: " + e, e);
i.remove();
plugin.disabled = true;
}
}
}
}
/**
* This method will check all needed dependencies of all currently registered
* plugin files and will build the final class loaders for the extensions containing all
* dependencies.
*/
public static void finalizePluginLoading() {
// building final class loader with all dependent extensions
LinkedList<Plugin> queue = new LinkedList<Plugin>(allPlugins);
HashSet<Plugin> initialized = new HashSet<Plugin>();
// now initialized every extension that's dependencies are fulfilled as long as we find another per round
boolean found = false;
while(found || !queue.isEmpty() && initialized.isEmpty()) {
found = false;
Iterator<Plugin> iterator = queue.iterator();
while (iterator.hasNext()) {
Plugin plugin = iterator.next();
boolean dependenciesMet = true;
for (Dependency dependency: plugin.pluginDependencies) {
Plugin dependencyPlugin = getPluginByExtensionId(dependency.getPluginExtensionId());
if (dependencyPlugin == null) {
// if we cannot find dependency plugin: Don't load this one, instead remove it and post error
allPlugins.remove(plugin);
iterator.remove();
LogService.getRoot().log(Level.SEVERE, "Cannot load extension '" + plugin.extensionId + "': Depends on '" + dependency.getPluginExtensionId() + "' which cannot be found!");
found = true;
dependenciesMet = false;
break; //break this loop: Nothing to check
} else {
dependenciesMet &= initialized.contains(dependencyPlugin);
}
}
// if we have all dependencies met: Load final class loader
if (dependenciesMet) {
plugin.buildFinalClassLoader();
initialized.add(plugin);
iterator.remove();
// then we have one more extension that is initialized, next round might find more
found = true;
}
}
}
}
/**
* Registers all operators from the plugins previously found by a call of registerAllPluginDescriptions
*/
public static void registerAllPluginOperators() {
for (Plugin plugin : allPlugins) {
plugin.registerOperators();
}
}
/** Returns a class loader which is able to load all classes (core _and_ all plugins). */
public static ClassLoader getMajorClassLoader() {
return MAJOR_CLASS_LOADER;
}
/** Returns the collection of all plugins. */
public static List<Plugin> getAllPlugins() {
return allPlugins;
}
/** Returns the plugin with the given extension id. */
public static Plugin getPluginByExtensionId(String name) {
Iterator<Plugin> i = allPlugins.iterator();
while (i.hasNext()) {
Plugin plugin = i.next();
if (name.equals(plugin.getExtensionId()))
return plugin;
}
return null;
}
/**
* This method will try to invoke the method void initGui(MainFrame) of PluginInit class of every plugin.
*/
public static void initPluginGuis(MainFrame mainframe) {
callPluginInitMethods("initGui", new Class[] { MainFrame.class }, new Object[] { mainframe }, false);
}
/**
* This method will try to invoke the public static method initPlugin() of the class com.rapidminer.PluginInit for
* arbitrary initializations of the plugins. It is called directly after registering the plugins.
*/
public static void initPlugins() {
callPluginInitMethods("initPlugin", new Class[] {}, new Object[] {}, false);
}
public static void initPluginUpdateManager() {
callPluginInitMethods("initPluginManager", new Class[] {}, new Object[] {}, false);
}
public static void initFinalChecks() {
callPluginInitMethods("initFinalChecks", new Class[] {}, new Object[] {}, false);
}
private static void callPluginInitMethods(String methodName, Class[] arguments, Object[] argumentValues, boolean useOriginalJarClassLoader) {
List<Plugin> plugins = getAllPlugins();
for (Plugin plugin : plugins) {
plugin.callInitMethod(methodName, arguments, argumentValues, useOriginalJarClassLoader);
}
}
private void callInitMethod(String methodName, Class[] arguments, Object[] argumentValues, boolean useOriginalJarClassLoader) {
if (pluginInitClassName == null) {
return;
}
try {
ClassLoader classLoader;
if (useOriginalJarClassLoader) {
classLoader = getOriginalClassLoader();
} else {
classLoader = getClassLoader();
}
Class<?> pluginInitator = Class.forName(pluginInitClassName, false, classLoader);
Method initMethod;
try {
initMethod = pluginInitator.getMethod(methodName, arguments);
} catch (NoSuchMethodException e) {
return;
}
initMethod.invoke(null, argumentValues);
} catch (Exception e) {
LogService.getRoot().log(Level.WARNING, "Plugin initializer " + pluginInitClassName + "." + methodName + " of Plugin " + getName() + " causes an error: " + e.getMessage(), e);
}
}
public static void initPluginSplashTexts(SplashScreen splashScreen) {
if (!RapidMiner.getExecutionMode().isHeadless()) {
callPluginInitMethods("initSplashTexts", new Class[] { SplashScreen.class }, new Object[] { splashScreen }, false);
}
}
public static void initAboutTexts(Properties properties) {
callPluginInitMethods("initAboutTexts", new Class[] { Properties.class }, new Object[] { properties }, false);
}
public boolean showAboutBox() {
if (pluginInitClassName == null) {
return true;
}
try {
Class<?> pluginInitator = Class.forName(pluginInitClassName, false, getClassLoader());
Method initGuiMethod = pluginInitator.getMethod("showAboutBox", new Class[] {});
Boolean showAboutBox = (Boolean) initGuiMethod.invoke(null, new Object[] {});
return showAboutBox.booleanValue();
} catch (ClassNotFoundException e) {
} catch (SecurityException e) {
} catch (NoSuchMethodException e) {
} catch (IllegalArgumentException e) {
} catch (IllegalAccessException e) {
} catch (InvocationTargetException e) {
}
return true;
}
/**
* Initializes all plugins if {@link RapidMiner#PROPERTY_RAPIDMINER_INIT_PLUGINS} is set. Plugins are searched for
* in the directory specified by {@link RapidMiner#PROPERTY_RAPIDMINER_INIT_PLUGINS_LOCATION} or, if this is not
* set, in the RapidMiner/lib/plugins directory.
*/
public static void initAll() {
// only load managed extensions if execution modes indicates
if (RapidMiner.getExecutionMode().isLoadingManagedExtensions())
ManagedExtension.init();
String loadPluginsString = ParameterService.getParameterValue(RapidMiner.PROPERTY_RAPIDMINER_INIT_PLUGINS);
boolean loadPlugins = Tools.booleanValue(loadPluginsString, true);
if (loadPlugins) {
File webstartPluginDir;
if (RapidMiner.getExecutionMode() == ExecutionMode.WEBSTART) {
webstartPluginDir = updateWebstartPluginsCache();
} else {
webstartPluginDir = null;
}
File pluginDir = null;
String pluginDirString = ParameterService.getParameterValue(RapidMiner.PROPERTY_RAPIDMINER_INIT_PLUGINS_LOCATION);
if (pluginDirString != null && !pluginDirString.isEmpty()) {
pluginDir = new File(pluginDirString);
}
if (pluginDir == null) {
try {
pluginDir = getPluginLocation();
} catch (IOException e) {
LogService.getRoot().warning("None of the properties " + RapidMiner.PROPERTY_RAPIDMINER_INIT_PLUGINS + " and " + Launcher.PROPERTY_RAPIDMINER_HOME + " is set. No globally installed plugins will be loaded.");
}
}
if (webstartPluginDir != null) {
findAndRegisterPlugins(webstartPluginDir, true);
}
if (pluginDir != null) {
findAndRegisterPlugins(pluginDir, true);
}
registerPlugins(ManagedExtension.getActivePluginJars(), true);
registerAllPluginDescriptions();
finalizePluginLoading();
initPlugins();
} else {
LogService.getRoot().config("Plugins skipped.");
}
}
/** Updates plugins from the server and returns a cache directory containing the jar files. */
private static File updateWebstartPluginsCache() {
// We hash the home URL to a directory name, so we don't have special characters.
final String homeUrl = System.getProperty(RapidMiner.PROPERTY_HOME_REPOSITORY_URL);
String dirName;
try {
final byte[] md5hash = MessageDigest.getInstance("MD5").digest(homeUrl.getBytes());
dirName = Base64.encodeBytes(md5hash);
} catch (NoSuchAlgorithmException e) {
LogService.getRoot().log(Level.WARNING, "Failed to hash remote url: " + e, e);
return null;
}
File cacheDir = new File(ManagedExtension.getUserExtensionsDir(), dirName);
cacheDir.mkdirs();
File readmeFile = new File(cacheDir, "README.txt");
try {
Tools.writeTextFile(readmeFile,
"This directory contains plugins downloaded from RapidAnalytics instance \n" +
" " + homeUrl + ".\n" +
"These plugins are only used if RapidMiner is started via WebStart from this \n" +
"server. You can delete the directory if you no longer need the cached plugins.");
} catch (IOException e1) {
LogService.getRoot().log(Level.WARNING, "Failed to create file " + readmeFile + ": " + e1, e1);
}
Document pluginsDoc;
try {
URL pluginsListUrl = new URL(homeUrl + "/RAWS/dependencies/resources.xml");
pluginsDoc = XMLTools.parse(pluginsListUrl.openStream());
} catch (Exception e) {
LogService.getRoot().log(Level.WARNING, "Failed to load extensions list from server: " + e, e);
return null;
}
Set<File> cachedFiles = new HashSet<File>();
NodeList pluginElements = pluginsDoc.getElementsByTagName("extension");
boolean errorOccurred = false;
for (int i = 0; i < pluginElements.getLength(); i++) {
Element pluginElem = (Element) pluginElements.item(i);
String pluginName = pluginElem.getTextContent();
String pluginVersion = pluginElem.getAttribute("version");
File pluginFile = new File(cacheDir, pluginName + "-" + pluginVersion + ".jar");
cachedFiles.add(pluginFile);
if (pluginFile.exists()) {
LogService.getRoot().log(Level.CONFIG, "Found extension on server: " + pluginName + ". Local cache exists.");
} else {
LogService.getRoot().log(Level.CONFIG, "Found extension on server: " + pluginName + ". Downloading to local cache.");
try {
URL pluginUrl = new URL(homeUrl + "/RAWS/dependencies/plugins/" + pluginName);
Tools.copyStreamSynchronously(pluginUrl.openStream(), new FileOutputStream(pluginFile), true);
} catch (Exception e) {
LogService.getRoot().log(Level.WARNING, "Failed to download extension from server: " + e, e);
errorOccurred = true; // Don't clear unknown files in this case.
}
}
}
// clear out of date cache files unless error occurred
if (!errorOccurred) {
for (File file : cacheDir.listFiles()) {
if (file.getName().equals("README.txt")) {
continue;
}
if (!cachedFiles.contains(file)) {
LogService.getRoot().log(Level.CONFIG, "Deleting obsolete file " + file + " from extension cache.");
file.delete();
}
}
}
return cacheDir;
}
/** Specifies whether plugins should be initialized on startup. */
public static void setInitPlugins(boolean init) {
ParameterService.setParameterValue(RapidMiner.PROPERTY_RAPIDMINER_INIT_PLUGINS, Boolean.toString(init));
}
/** Specifies a directory to scan for plugins. */
public static void setPluginLocation(String directory) {
ParameterService.setParameterValue(RapidMiner.PROPERTY_RAPIDMINER_INIT_PLUGINS_LOCATION, directory);
}
/** Returns the prefix to be used in the operator keys (namespace). This is also used for the Wiki URL. */
public String getPrefix() {
return this.prefix;
}
public JarFile getArchive() {
return archive;
}
public File getFile() {
return file;
}
public String getExtensionId() {
return extensionId;
}
/** Returns the directory where plugin files are expected. */
public static File getPluginLocation() throws IOException {
String locationProperty = ParameterService.getParameterValue(RapidMiner.PROPERTY_RAPIDMINER_INIT_PLUGINS_LOCATION);
if (locationProperty == null || locationProperty.isEmpty()) {
return FileSystemService.getLibraryFile("plugins");
} else {
return new File(locationProperty);
}
}
/**
* This returns the Icon of the extension or null if not present.
*/
public ImageIcon getExtensionIcon() {
URL iconURL = classLoader.findResource("META-INF/icon.png");
if (iconURL != null)
return new ImageIcon(iconURL);
return null;
}
}