/** * 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.awt.Frame; import java.awt.Image; 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.URISyntaxException; 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.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.Comparator; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.LinkedList; import java.util.List; import java.util.Locale; import java.util.Map; import java.util.Map.Entry; import java.util.Properties; import java.util.ResourceBundle; import java.util.Set; import java.util.TreeSet; import java.util.concurrent.ConcurrentHashMap; 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 javax.xml.bind.DatatypeConverter; import javax.xml.parsers.ParserConfigurationException; import org.w3c.dom.Document; import org.w3c.dom.Element; import org.w3c.dom.NodeList; import org.xml.sax.SAXException; import com.rapidminer.RapidMiner; import com.rapidminer.RapidMiner.ExecutionMode; import com.rapidminer.gui.MainFrame; import com.rapidminer.gui.RapidMinerGUI; import com.rapidminer.gui.flow.processrendering.draw.ProcessDrawUtils; import com.rapidminer.gui.properties.SettingsItem; import com.rapidminer.gui.properties.SettingsItems; import com.rapidminer.gui.properties.SettingsXmlHandler; import com.rapidminer.gui.renderer.RendererService; import com.rapidminer.gui.safemode.SafeMode; import com.rapidminer.gui.tools.SplashScreen; import com.rapidminer.gui.tools.VersionNumber; import com.rapidminer.gui.tools.dialogs.AboutBox; import com.rapidminer.io.process.XMLImporter; import com.rapidminer.io.process.XMLTools; import com.rapidminer.operator.Operator; import com.rapidminer.parameter.ParameterType; import com.rapidminer.tools.FileSystemService; import com.rapidminer.tools.I18N; import com.rapidminer.tools.I18N.SettingsType; import com.rapidminer.tools.LogService; import com.rapidminer.tools.OperatorService; import com.rapidminer.tools.ParameterService; import com.rapidminer.tools.PlatformUtilities; import com.rapidminer.tools.ResourceSource; import com.rapidminer.tools.Tools; import com.rapidminer.tools.WebServiceTools; import com.rapidminer.tools.container.Pair; import com.rapidminer.tools.usagestats.ActionStatisticsCollector; /** * <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, Nils Woehler, Adrian Wilke */ 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 folder where bundled server extensions are stored at */ private static Set<String> additionalExtensionDirs = new HashSet<>(); /** * 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 requiredRapidMinerVersion = "0.0.000"; /** The plugins and their versions which are needed for this plugin. */ private final List<Dependency> pluginDependencies = new LinkedList<>(); 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 pluginSettingsDescriptions; private String pluginSettingsStructure; private String prefix; private boolean disabled = false; private ResourceBundle settingsRessourceBundle; private Boolean useExtensionTreeRoot = null; private static final Comparator<Plugin> PLUGIN_COMPARATOR = (p1, p2) -> { if (p1 == null && p2 == null) { return 0; } if (p1 == null || p2 == null) { return p1 == null ? 1 : -1; } if (p1.getName() == null && p2.getName() == null) { return 0; } if (p1.getName() == null || p2.getName() == null) { return p1.getName() == null ? 1 : -1; } // if both plugins have the same name then check the version if (p1.getName().equals(p2.getName())) { if (p1.getVersion() == null && p2.getVersion() == null) { return 0; } if (p1.getVersion() == null || p2.getVersion() == null) { return p1.getVersion() == null ? 1 : -1; } return p1.getVersion().compareTo(p2.getVersion()); } return p1.getName().compareTo(p2.getName()); }; /** * An ordered collection of plugins sorted by the dependency order. IMPORTANT: This collection * does not respect failures during initialization, so it might contain more plugins than * {@link #ALL_PLUGINS}. */ private static final Collection<Plugin> PLUGIN_INITIALIZATION_ORDER = new ArrayList<>(); /** An ordered set of all plugins sorted lexically based on the plugin name. */ private static final Collection<Plugin> ALL_PLUGINS = new TreeSet<>(PLUGIN_COMPARATOR); /** Set of plugins that failed to load. */ private static final Set<Plugin> INCOMPATIBLE_PLUGINS = new HashSet<>(); /** * The map of blacklisted plugins that should not be loaded. The key is the extension id. The * value can be {@code null} or a pair of version numbers. The pair of version numbers specifies * the range of forbidden version numbers [from - up to]. These version numbers can be * {@code null} indicating no upper or lower bound. */ private static final Map<String, Pair<VersionNumber, VersionNumber>> PLUGIN_BLACKLIST = new HashMap<>(); static { // incompatible initialization code PLUGIN_BLACKLIST.put("rmx_parallel", null); // every version smaller or equal 7.1.1 final Pair<VersionNumber, VersionNumber> upToRm711 = new Pair<>(null, new VersionNumber(7, 1, 1)); // bundled extensions using the old license schema PLUGIN_BLACKLIST.put("rmx_advanced_file_connectors", upToRm711); PLUGIN_BLACKLIST.put("rmx_jdbc_connectors", upToRm711); PLUGIN_BLACKLIST.put("rmx_legacy", upToRm711); PLUGIN_BLACKLIST.put("rmx_productivity", upToRm711); PLUGIN_BLACKLIST.put("rmx_remote_repository", upToRm711); // packaged extensions using the old license schema PLUGIN_BLACKLIST.put("rmx_data_editor", upToRm711); PLUGIN_BLACKLIST.put("rmx_process_scheduling", upToRm711); PLUGIN_BLACKLIST.put("rmx_social_media", upToRm711); PLUGIN_BLACKLIST.put("rmx_cloud_connectivity", upToRm711); PLUGIN_BLACKLIST.put("rmx_cloud_execution", upToRm711); PLUGIN_BLACKLIST.put("rmx_operator_recommender", upToRm711); // non-packaged extensions using the old license schema PLUGIN_BLACKLIST.put("rmx_mozenda", upToRm711); PLUGIN_BLACKLIST.put("rmx_nosql", upToRm711); PLUGIN_BLACKLIST.put("rmx_pmml", upToRm711); PLUGIN_BLACKLIST.put("rmx_qlik", upToRm711); PLUGIN_BLACKLIST.put("rmx_solr", upToRm711); PLUGIN_BLACKLIST.put("rmx_splunk", upToRm711); PLUGIN_BLACKLIST.put("rmx_tableau_table_writer", upToRm711); PLUGIN_BLACKLIST.put("rmx_text", upToRm711); PLUGIN_BLACKLIST.put("rmx_web", upToRm711); PLUGIN_BLACKLIST.put("rmx_r_scripting", upToRm711); PLUGIN_BLACKLIST.put("rmx_python_scripting", upToRm711); // Radoop depends on Parallel Decision Tree in Concurrency Extension PLUGIN_BLACKLIST.put("rmx_radoop", new Pair<>(null, new VersionNumber(7, 3, 0))); // RapidLabs / 3rd party extensions causing problems since Studio 7.2 PLUGIN_BLACKLIST.put("rmx_rapidprom", new Pair<>(null, new VersionNumber(3, 0, 7))); // yes the rmx_rmx_ prefix is correct... PLUGIN_BLACKLIST.put("rmx_rmx_toolkit", new Pair<>(null, new VersionNumber(1, 0, 0))); PLUGIN_BLACKLIST.put("rmx_ida", new Pair<>(null, new VersionNumber(5, 1, 0))); } /** map of all plugin loading times */ private static final Map<String, Long> LOADING_TIMES = new ConcurrentHashMap<>(); /** * amount of time in ms a plugin has to load before its loading time will be displayed as * WARNING instead of INFO log level */ private static final int LOADING_THRESHOLD = 10_000; /** 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(); this.classLoader.setPluginKey(getExtensionId()); 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 VersionNumber getNecessaryRapidMinerVersion() { return new VersionNumber(requiredRapidMinerVersion); } /** * 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; } public String getPluginSettingsDescriptions() { return pluginSettingsDescriptions; } public String getPluginSettingsStructure() { return pluginSettingsStructure; } /** * 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<Dependency> 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(Plugin plugin, Collection<Plugin> plugins) { if (RapidMiner.getVersion().compareTo(getNecessaryRapidMinerVersion()) < 0) { LogService.getRoot().log(Level.WARNING, "com.rapidminer.tools.plugin.Plugin.registring_operators_error_rm_version", new Object[] { plugin.getName(), plugin.getNecessaryRapidMinerVersion(), RapidMiner.getVersion() }); return false; } // other extensions Iterator<Dependency> i = pluginDependencies.iterator(); while (i.hasNext()) { Dependency dependency = i.next(); if (!dependency.isFulfilled(plugins)) { LogService.getRoot().log(Level.WARNING, "com.rapidminer.tools.plugin.Plugin.registring_operators_error_ext_missing", new Object[] { plugin.getName(), dependency.getPluginExtensionId(), dependency.getPluginVersion() }); 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); pluginSettingsDescriptions = getDescriptorResource("Settings-Descriptor", false, true, atts); pluginSettingsStructure = getDescriptorResource("SettingsStructure-Descriptor", false, false, atts); requiredRapidMinerVersion = getValue(atts, "RapidMiner-Version"); 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().log(Level.WARNING, "com.rapidminer.tools.plugin.Plugin.registring_operators_error", getName()); } InputStream in = null; // trying normal plugins if (pluginResourceOperators != null) { URL operatorsURL = this.classLoader.getResource(pluginResourceOperators); if (operatorsURL == null) { LogService.getRoot().log(Level.WARNING, "com.rapidminer.tools.plugin.Plugin.operators_description_not_existing", new Object[] { pluginResourceOperators, archive.getName() }); return; } else { // register operators try { in = operatorsURL.openStream(); } catch (IOException e) { LogService.getRoot().log(Level.WARNING, I18N.getMessage(LogService.getRoot().getResourceBundle(), "com.rapidminer.tools.plugin.Plugin.operator_descriptor_reading_error", operatorsURL, archive.getName()), e); return; } } } else if (pluginInitClassName != null) { LogService.getRoot().log(Level.INFO, "com.rapidminer.tools.plugin.Plugin.operator_descriptor_not_specified", new Object[] { getName(), 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 | SecurityException | NoSuchMethodException | IllegalArgumentException | IllegalAccessException | InvocationTargetException e) { // ignore } } if (in != null) { OperatorService.registerOperators(archive.getName(), in, this.classLoader, this); } else { LogService.getRoot().log(Level.WARNING, "com.rapidminer.tools.plugin.Plugin.operator_descriptor_not_defined", getName()); } } /** * Register all things delivered with this plugin. * * @throws PluginException */ public void registerDescriptions() throws PluginException { // make sure to not accidentally find resources from dependencies this.classLoader.setIgnoreDependencyClassloaders(true); // 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 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)); } if (pluginSettingsDescriptions != null) { settingsRessourceBundle = ResourceBundle.getBundle(pluginSettingsDescriptions, Locale.getDefault(), this.classLoader); I18N.registerSettingsBundle(settingsRessourceBundle); } // Do only register renderers and process renderer colors if not in headless mode if (!RapidMiner.getExecutionMode().isHeadless()) { // 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 colors if (pluginGroupDescriptions != null) { ProcessDrawUtils.registerAdditionalObjectColors(pluginGroupDescriptions, name, classLoader, this); ProcessDrawUtils.registerAdditionalGroupColors(pluginGroupDescriptions, name, classLoader, this); } } this.classLoader.setIgnoreDependencyClassloaders(false); } /** 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) { I18N.getMessage(ResourceBundle.getBundle("com.rapidminer.resources.i18n.LogMessages"), "com.rapidminer.tools.I18N.plugin_warning1", Level.WARNING, 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); I18N.getMessage(ResourceBundle.getBundle("com.rapidminer.resources.i18n.LogMessages"), "com.rapidminer.tools.I18N.plugin_warning2", Level.WARNING, 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, boolean overwritePluginsWithHigherVersions) { List<File> files = new LinkedList<>(); if (pluginDir == null) { LogService.getRoot().log(Level.WARNING, "com.rapidminer.tools.plugin.Plugin.findandregisterplugins_called_with_null_directory"); return; } if (!(pluginDir.exists() && pluginDir.isDirectory())) { LogService.getRoot().log(Level.CONFIG, "com.rapidminer.tools.plugin.Plugin.plugin_dir_not_existing", pluginDir); } else { LogService.getRoot().log(Level.CONFIG, "com.rapidminer.tools.plugin.Plugin.scanning_for_plugins", pluginDir); files.addAll(Arrays.asList(pluginDir.listFiles(new FilenameFilter() { @Override public boolean accept(File dir, String name) { return name.endsWith(".jar"); } }))); } registerPlugins(files, showWarningForNonPluginJars, overwritePluginsWithHigherVersions); } /** * Makes {@link Plugin} s from all files and adds them to {@link #ALL_PLUGINS}. After all * Plugins are loaded, they must be assigend their final class loader. */ private static void registerPlugins(List<File> files, boolean showWarningForNonPluginJars, boolean overwritePluginsWithHigherVersions) { List<Plugin> newPlugins = new LinkedList<>(); 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(), newPlugins); if (conflict == null) { newPlugins.add(plugin); } else { resolveVersionConflict(plugin, conflict, newPlugins); } } else { if (showWarningForNonPluginJars) { LogService.getRoot().log(Level.WARNING, "com.rapidminer.tools.plugin.Plugin.jar_file_does_not_contain_entry", new Object[] { jarFile.getName(), RAPIDMINER_TYPE }); } } } catch (Throwable e) { LogService.getRoot().log(Level.WARNING, I18N.getMessage(LogService.getRoot().getResourceBundle(), "com.rapidminer.tools.plugin.Plugin.plugin_loading_error", file, e.getMessage()), e); } } for (Plugin newPlugin : newPlugins) { LogService.getRoot().log(Level.INFO, "Register plugin: " + newPlugin.getName()); Plugin oldPlugin = getPluginByExtensionId(newPlugin.getExtensionId(), ALL_PLUGINS); if (oldPlugin == null) { ALL_PLUGINS.add(newPlugin); } else { if (overwritePluginsWithHigherVersions) { ALL_PLUGINS.remove(oldPlugin); ALL_PLUGINS.add(newPlugin); } else { resolveVersionConflict(newPlugin, oldPlugin, ALL_PLUGINS); } } } } /** * Resolves an extension version conflict by comparing both extension versions. If the * conflicting extension has a lower version than the new extension version the conflicting * extension is removed from the provided list and the new extension is added to it. * * @param newExtension * the newly loaded extension * @param conflictingExtension * the already registered extension with the same extension ID * @param plugins * the collection from which the conflicting extension should be removed if its * version is lower than the version of the new extension */ private static void resolveVersionConflict(Plugin newExtension, Plugin conflictingExtension, Collection<Plugin> plugins) { // keep extension with higher version number VersionNumber newVersion = new VersionNumber(newExtension.getVersion()); VersionNumber conflictVersion = new VersionNumber(conflictingExtension.getVersion()); VersionNumber higherNumber = conflictVersion; if (newVersion.compareTo(conflictVersion) > 0) { plugins.remove(conflictingExtension); plugins.add(newExtension); higherNumber = newVersion; } if (LogService.getRoot().isLoggable(Level.WARNING)) { LogService.getRoot().log(Level.WARNING, "com.rapidminer.tools.plugin.Plugin.duplicate_plugin_definition_higher_version", new Object[] { newExtension.getExtensionId(), newExtension.file, conflictingExtension.file, higherNumber.toString() }); } } @Override public String toString() { return name + " " + version + " (" + archive.getName() + ") depending on " + pluginDependencies; } /** * Checks if the version of the extension with id extensionId is blacklisted. * * @param extensionId * the id of the extension to check * @param version * the version to check * @return {@code true} if the extension version is blacklisted */ public static boolean isExtensionVersionBlacklisted(String extensionId, VersionNumber version) { if (PLUGIN_BLACKLIST.containsKey(extensionId)) { Pair<VersionNumber, VersionNumber> versionRange = PLUGIN_BLACKLIST.get(extensionId); if (versionRange != null && (versionRange.getSecond() != null && version.isAbove(versionRange.getSecond()) || versionRange.getFirst() != null && !version.isAtLeast(versionRange.getFirst()))) { return false; } return true; } return false; } /** * Adds the amount of milliseconds elapsed since the given start time to the already logged * amount of time the specified extension took to load. Times can be accessed from the * {@link #LOADING_TIMES} map. * * @param id * the id of the extension * @param start * the starting time of this recording in milliseconds since 1970 */ private static void recordLoadingTime(String id, long start) { long end = System.currentTimeMillis(); Long time = LOADING_TIMES.get(id); if (time == null) { time = 0L; } time += end - start; LOADING_TIMES.put(id, time); } /** * Finds all plugins in lib/plugins directory and initializes them. */ private static void registerAllPluginDescriptions() { Iterator<Plugin> i = ALL_PLUGINS.iterator(); while (i.hasNext()) { Plugin plugin = i.next(); if (!plugin.checkDependencies(plugin, ALL_PLUGINS)) { plugin.disabled = true; i.remove(); INCOMPATIBLE_PLUGINS.add(plugin); } } if (ALL_PLUGINS.size() > 0) { i = ALL_PLUGINS.iterator(); while (i.hasNext()) { Plugin plugin = i.next(); try { long start = System.currentTimeMillis(); plugin.registerDescriptions(); recordLoadingTime(plugin.getExtensionId(), start); } catch (Exception e) { LogService.getRoot().log(Level.WARNING, I18N.getMessage(LogService.getRoot().getResourceBundle(), "com.rapidminer.tools.plugin.Plugin.plugin_initializing_error", e), e); i.remove(); plugin.disabled = true; INCOMPATIBLE_PLUGINS.add(plugin); } } } } /** * Removes the blacklisted plugins from the list of all plugins and adds them to the * incompatible plugins. */ private static void filterBlacklistedPlugins() { Iterator<Plugin> i = ALL_PLUGINS.iterator(); while (i.hasNext()) { Plugin plugin = i.next(); if (plugin.isIncompatible()) { plugin.disabled = true; i.remove(); INCOMPATIBLE_PLUGINS.add(plugin); } } } /** * Checks if the plugin is marked as incompatible by the {@link #PLUGIN_BLACKLIST}. * * @return whether the plugin is incompatible */ private final boolean isIncompatible() { if (PLUGIN_BLACKLIST.containsKey(getExtensionId())) { Pair<VersionNumber, VersionNumber> forbiddenRange = PLUGIN_BLACKLIST.get(getExtensionId()); if (forbiddenRange == null) { LogService.getRoot().log(Level.WARNING, "com.rapidminer.tools.plugin.Plugin.incompatible_extension", new Object[] { getName() }); return true; } VersionNumber startVersion = forbiddenRange.getFirst(); VersionNumber endVersion = forbiddenRange.getSecond(); VersionNumber currentVersion = new VersionNumber(getVersion()); if ((startVersion != null && currentVersion.isAtLeast(startVersion) || startVersion == null) && (endVersion != null && currentVersion.isAtMost(endVersion) || endVersion == null)) { if (startVersion != null && endVersion != null) { LogService.getRoot().log(Level.WARNING, "com.rapidminer.tools.plugin.Plugin.incompatible_extension_version", new Object[] { getName(), startVersion, endVersion, currentVersion }); } else if (startVersion != null) { LogService.getRoot().log(Level.WARNING, "com.rapidminer.tools.plugin.Plugin.incompatible_extension_version_above", new Object[] { getName(), startVersion, currentVersion }); } else if (endVersion != null) { LogService.getRoot().log(Level.WARNING, "com.rapidminer.tools.plugin.Plugin.incompatible_extension_version_below", new Object[] { getName(), endVersion, currentVersion }); } else { LogService.getRoot().log(Level.WARNING, "com.rapidminer.tools.plugin.Plugin.incompatible_extension", new Object[] { getName() }); } return true; } } return false; } /** * 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<>(ALL_PLUGINS); HashSet<Plugin> initialized = new HashSet<>(); // 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; long start = System.currentTimeMillis(); 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 ALL_PLUGINS.remove(plugin); INCOMPATIBLE_PLUGINS.add(plugin); iterator.remove(); LogService.getRoot().log(Level.SEVERE, "com.rapidminer.tools.plugin.Plugin.loading_extension_error", new Object[] { plugin.extensionId, dependency.getPluginExtensionId() }); 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; // remember the initialization order globally PLUGIN_INITIALIZATION_ORDER.add(plugin); } recordLoadingTime(plugin.getExtensionId(), start); } } } /** * Registers all operators from the plugins previously found by a call of * registerAllPluginDescriptions */ public static void registerAllPluginOperators() { for (Plugin plugin : ALL_PLUGINS) { long start = System.currentTimeMillis(); plugin.registerOperators(); recordLoadingTime(plugin.getExtensionId(), start); } } /** Returns a class loader which is able to load all classes (core _and_ all plugins). */ public static ClassLoader getMajorClassLoader() { return MAJOR_CLASS_LOADER; } /** Returns a sorted collection of all plugins. */ public static Collection<Plugin> getAllPlugins() { return ALL_PLUGINS; } /** * Returns unmodifiable list of plugins that failed to load. * * @return the list of plugins */ public static Collection<Plugin> getIncompatiblePlugins() { return Collections.unmodifiableCollection(INCOMPATIBLE_PLUGINS); } /** Returns the plugin with the given extension id. */ public static Plugin getPluginByExtensionId(String name) { return getPluginByExtensionId(name, ALL_PLUGINS); } /** Returns the plugin with the given extension id. */ private static Plugin getPluginByExtensionId(String name, Collection<Plugin> plugins) { Iterator<Plugin> i = plugins.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); } public static void initPluginTests() { callPluginInitMethods("initPluginTests", new Class[] {}, new Object[] {}, false); } private static void callPluginInitMethods(String methodName, Class<?>[] arguments, Object[] argumentValues, boolean useOriginalJarClassLoader) { for (Plugin plugin : PLUGIN_INITIALIZATION_ORDER) { if (!ALL_PLUGINS.contains(plugin)) { // plugin may be removed in the meantime, // so skip the initialization continue; } if (!plugin.checkDependencies(plugin, ALL_PLUGINS)) { getAllPlugins().remove(plugin); INCOMPATIBLE_PLUGINS.add(plugin); continue; } long start = System.currentTimeMillis(); if (!plugin.callInitMethod(methodName, arguments, argumentValues, useOriginalJarClassLoader)) { getAllPlugins().remove(plugin); INCOMPATIBLE_PLUGINS.add(plugin); } recordLoadingTime(plugin.getExtensionId(), start); } } /** * @return true if everything went well, false if a fatal error occurred. The plugin should be * unregistered in this case. */ private boolean callInitMethod(String methodName, Class<?>[] arguments, Object[] argumentValues, boolean useOriginalJarClassLoader) { if (pluginInitClassName == null) { return true; } 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 true; } initMethod.invoke(null, argumentValues); return true; } catch (Throwable e) { LogService.getRoot().log(Level.WARNING, I18N.getMessage(LogService.getRoot().getResourceBundle(), "com.rapidminer.tools.plugin.Plugin.plugin_initializer_error", pluginInitClassName, methodName, getName(), e.getMessage()), e); return false; } } 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 | NoSuchMethodException | SecurityException | IllegalAccessException | IllegalArgumentException | InvocationTargetException e) { } return true; } /** * Defines whether the extension is using the "extensions.EXTENSION_NAME" folder as tree root. * * @return {@code true} by default */ public synchronized boolean useExtensionTreeRoot() { if (pluginInitClassName == null) { return true; } // lookup only once if (useExtensionTreeRoot == null) { // store old value and ensure that the dependency classloaders are not ignored boolean oldValue = this.classLoader.isIgnoreDependencyClassloaders(); this.classLoader.setIgnoreDependencyClassloaders(false); try { Class<?> pluginInitator = Class.forName(pluginInitClassName, false, getClassLoader()); Method initGuiMethod = pluginInitator.getMethod("useExtensionTreeRoot", new Class[] {}); useExtensionTreeRoot = (Boolean) initGuiMethod.invoke(null, new Object[] {}); } catch (Throwable e) { useExtensionTreeRoot = Boolean.TRUE; } // restore setting for ignoring dependency classloaders this.classLoader.setIgnoreDependencyClassloaders(oldValue); } // return cached value return useExtensionTreeRoot.booleanValue(); } /** * 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); SafeMode safeMode = RapidMinerGUI.getSafeMode(); boolean isSafeMode = false; if (safeMode != null) { isSafeMode = safeMode.isSafeMode(); } if (loadPlugins && !isSafeMode) { // Check for Web start extension directory and load extensions from Web start extension // directory if it exists. File webstartPluginDir; if (RapidMiner.getExecutionMode() == ExecutionMode.WEBSTART) { webstartPluginDir = updateWebstartPluginsCache(); } else { webstartPluginDir = null; } if (webstartPluginDir != null) { findAndRegisterPlugins(webstartPluginDir, true, false); } // Check if an extension directory is specified in the preferences and load extensions // from there (if it exists). File pluginDir = null; String pluginDirString = ParameterService .getParameterValue(RapidMiner.PROPERTY_RAPIDMINER_INIT_PLUGINS_LOCATION); if (pluginDirString != null && !pluginDirString.trim().isEmpty()) { pluginDir = new File(pluginDirString); } else if (loadFromUserConfigFolder()) { // update preferences if preferences property is empty // and point it to ~/.RapidMiner/extensions pluginDir = FileSystemService.getUserConfigFile("extensions"); if (!pluginDir.isDirectory()) { if (!pluginDir.mkdirs()) { LogService.getRoot().log(Level.WARNING, "com.rapidminer.tools.plugin.Plugin.could_not_create_user_home_extension_directory"); } } ParameterService.setParameterValue(RapidMiner.PROPERTY_RAPIDMINER_INIT_PLUGINS_LOCATION, pluginDir.getAbsolutePath()); } if (pluginDir != null) { findAndRegisterPlugins(pluginDir, true, false); } // Check for additional extension directories and load extensions from there (if the // directory exists). for (String additionalExtensionDir : additionalExtensionDirs) { File extensionDir = new File(additionalExtensionDir); if (extensionDir.isDirectory()) { findAndRegisterPlugins(extensionDir, true, false); } } // Check for managed extensions and register them registerPlugins(ManagedExtension.getActivePluginJars(), true, false); // Check global folder for extensions and register them // CAUTION: This extensions overwrite extensions from other folders, even if they have a // lower version number. // Otherwise plugins that are too new for the running studio version could not work. // After this registerPlugins or findAndRegisterPlugins must not be called anymore. if (loadFromGlobalFolder()) { try { // Load globally installed extensions if RAPIDMINER_HOME is specified File globalPluginDir = getPluginLocation(); if (globalPluginDir != null) { findAndRegisterPlugins(globalPluginDir, true, true); } } catch (IOException e) { LogService.getRoot().log(Level.WARNING, "com.rapidminer.tools.plugin.Plugin.no_properties_set", new Object[] { PlatformUtilities.PROPERTY_RAPIDMINER_HOME }); } } filterBlacklistedPlugins(); finalizePluginLoading(); registerAllPluginDescriptions(); loadAllSettingsStructures(); initPlugins(); updateAllSettingsDescriptions(); } else { LogService.getRoot().log(Level.INFO, "com.rapidminer.tools.plugin.Plugin.plugins_skipped"); } // log extension loading times List<Entry<String, Long>> sortedLoadingTimes = new LinkedList<>(LOADING_TIMES.entrySet()); // sort from fastest to slowest Collections.sort(sortedLoadingTimes, new Comparator<Entry<String, Long>>() { @Override public int compare(Entry<String, Long> o1, Entry<String, Long> o2) { return o1.getValue().compareTo(o2.getValue()); } }); for (Entry<String, Long> entry : sortedLoadingTimes) { Plugin plugin = getPluginByExtensionId(entry.getKey()); String value = String.valueOf(entry.getValue()) + "ms"; Level logLevel = Level.INFO; if (entry.getValue() > LOADING_THRESHOLD) { value = Tools.formatDuration(entry.getValue()); logLevel = Level.WARNING; } String identifier; if (plugin != null) { LogService.getRoot().log(logLevel, "com.rapidminer.tools.plugin.Plugin.loading_time", new Object[] { plugin.getName(), value }); identifier = plugin.getExtensionId(); } else { LogService.getRoot().log(logLevel, "com.rapidminer.tools.plugin.Plugin.loading_time_failure", new Object[] { entry.getKey(), value }); identifier = entry.getKey(); } ActionStatisticsCollector.INSTANCE.log(ActionStatisticsCollector.TYPE_CONSTANT, ActionStatisticsCollector.VALUE_EXTENSION_INITIALIZATION, identifier); } } /** * @return {@code false} if the {@link ExecutionMode} of RapidMiner is embedded, web * (server/applet) or test. {@code true} for UI, COMMAND_LINE or UNKNOWN. */ private static boolean loadFromUserConfigFolder() { switch (RapidMiner.getExecutionMode()) { case APPLET: case APPSERVER: case EMBEDDED_WITHOUT_UI: case EMBEDDED_AS_APPLET: case EMBEDDED_WITH_UI: case TEST: return false; case WEBSTART: case COMMAND_LINE: case UI: case UNKNOWN: default: return true; } } /** * @return {@code false} if the {@link ExecutionMode} of RapidMiner is embedded or web * (server/applet). {@code true} for UI, COMMAND_LINE or UNKNOWN. */ private static boolean loadFromGlobalFolder() { switch (RapidMiner.getExecutionMode()) { case APPLET: case APPSERVER: case EMBEDDED_WITHOUT_UI: case EMBEDDED_AS_APPLET: case EMBEDDED_WITH_UI: return false; case TEST: case WEBSTART: case COMMAND_LINE: case UI: case UNKNOWN: default: return true; } } /** Calls {@link #loadSettingsStructure()} for all extensions, which are not disabled. */ private static void loadAllSettingsStructures() { for (Iterator<Plugin> iterator = getAllPlugins().iterator(); iterator.hasNext();) { Plugin plugin = iterator.next(); if (plugin != null && !plugin.disabled) { long start = System.currentTimeMillis(); plugin.loadSettingsStructure(); recordLoadingTime(plugin.getExtensionId(), start); } } } /** * Checks, if a XML file with information about the settings of a plugin is provided. If such a * XML file is found, it is parsed and the settings items are added to the {@link SettingsItems} * container. The settings can be displayed and changed via the 'RapidMiner Studio Preferences' * dialog. */ private void loadSettingsStructure() { // XML file has to be provided by extension if (getPluginSettingsStructure() != null) { // Locate XML resource URL settingsXML = getClassLoader().getResource(getPluginSettingsStructure()); // XML file has to be found if (settingsXML != null) { try { // Parse XML Map<String, SettingsItem> map = new SettingsXmlHandler().parse(settingsXML.toURI()); // Add to settings items SettingsItems settingsItems = SettingsItems.INSTANCE; Iterator<Entry<String, SettingsItem>> iterator = map.entrySet().iterator(); while (iterator.hasNext()) { Entry<String, SettingsItem> entry = iterator.next(); if (!settingsItems.containsKey(entry.getKey())) { settingsItems.put(entry.getKey(), entry.getValue()); } } } catch (ParserConfigurationException | SAXException | IOException | URISyntaxException e) { LogService.getRoot().log(Level.WARNING, "Could not parse XML settings file: " + SettingsXmlHandler.SETTINGS_XML_FILE + " of extension " + getName() + " " + getVersion()); // Must not throw an exception, as the settings work without the structure // inside XML. } } } } /** Calls {@link #updateSettingsDescriptions()} for all extensions, which are not disabled. */ private static void updateAllSettingsDescriptions() { for (Iterator<Plugin> iterator = getAllPlugins().iterator(); iterator.hasNext();) { Plugin plugin = iterator.next(); if (plugin != null && !plugin.disabled) { long start = System.currentTimeMillis(); plugin.updateSettingsDescriptions(); recordLoadingTime(plugin.getExtensionId(), start); } } } /** * Updates descriptions of {@link ParameterType} objects, which are defined in the i18n * settings. */ private void updateSettingsDescriptions() { if (settingsRessourceBundle != null) { for (String settingsKey : settingsRessourceBundle.keySet()) { if (settingsKey.endsWith(SettingsType.DESCRIPTION.toString())) { // Extract key of ParameterType by removing suffix of description String parameterTypeKey = settingsKey.substring(0, settingsKey.length() - SettingsType.DESCRIPTION.toString().length()); ParameterType parameterType = ParameterService.getParameterType(parameterTypeKey); if (parameterType != null) { String description = I18N.getSettingsMessage(parameterTypeKey, SettingsType.DESCRIPTION); // Only update description, if it is set in i18n if (!description.startsWith(settingsKey)) { parameterType.setDescription(description); } } } } } } /** 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 = DatatypeConverter.printBase64Binary(md5hash); } catch (NoSuchAlgorithmException e) { LogService.getRoot().log(Level.WARNING, I18N.getMessage(LogService.getRoot().getResourceBundle(), "com.rapidminer.tools.plugin.Plugin.hashing_remote_url_error", 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 RapidMiner Server 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, I18N.getMessage(LogService.getRoot().getResourceBundle(), "com.rapidminer.tools.plugin.Plugin.creating_file_error", 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, I18N.getMessage(LogService.getRoot().getResourceBundle(), "com.rapidminer.tools.plugin.Plugin.loading_extensions_list_error", e), e); return null; } Set<File> cachedFiles = new HashSet<>(); 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, "com.rapidminer.tools.plugin.Plugin.extension_found_cache_exists", pluginName); } else { LogService.getRoot().log(Level.CONFIG, "com.rapidminer.tools.plugin.Plugin.extension_found_downloading", pluginName); try { URL pluginUrl = new URL(homeUrl + "/RAWS/dependencies/plugins/" + pluginName); Tools.copyStreamSynchronously(WebServiceTools.openStreamFromURL(pluginUrl), new FileOutputStream(pluginFile), true); } catch (Exception e) { LogService.getRoot().log(Level.WARNING, I18N.getMessage(LogService.getRoot().getResourceBundle(), "com.rapidminer.tools.plugin.Plugin.downloading_extension_error", 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, "com.rapidminer.tools.plugin.Plugin.deleting_obsolete_file", file); 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 the main directory to scan for extensions. */ public static void setPluginLocation(String directory) { ParameterService.setParameterValue(RapidMiner.PROPERTY_RAPIDMINER_INIT_PLUGINS_LOCATION, directory); } /** * Adds a directory to scan for RapidMiner extensions when initializing the RapidMiner * extensions. * * @param directory * the absolute path to the directory which contains the RapidMiner extensions */ public static void addAdditionalExtensionDir(String directory) { additionalExtensionDirs.add(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; } /** * @return the directory where globally installed extension files are expected. */ public static File getPluginLocation() throws IOException { return FileSystemService.getLibraryFile("plugins"); } /** * 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; } /** * <strong>Experimental method.</strong> Registers this plugin at runtime. */ public void reregister() { getAllPlugins().add(this); try { registerDescriptions(); } catch (Exception e) { LogService.getRoot().log(Level.WARNING, "com.rapidminer.tools.plugin.Plugin.register_desc_runtime_failed", new Object[] { this.getName(), e.getMessage() }); } try { registerOperators(); } catch (Exception e) { LogService.getRoot().log(Level.WARNING, "com.rapidminer.tools.plugin.Plugin.register_operators_runtime_failed", new Object[] { this.getName(), e.getMessage() }); } } /** * <strong>Experimental method.</strong> Finishes the initializing of this plugin. */ public void finishReregister() { buildFinalClassLoader(); callInitMethod("initPlugin", new Class[] {}, new Object[] {}, false); callInitMethod("initGui", new Class[] { MainFrame.class }, new Object[] { RapidMinerGUI.getMainFrame() }, false); callInitMethod("initFinalChecks", new Class[] {}, new Object[] {}, false); callInitMethod("initPluginManager", new Class[] {}, new Object[] {}, false); } /** * <strong>Experimental method.</strong> Unregisters this plugin, all of its {@link Operator}s, * and calls tearDown() and optionally tearDownGUI(MainFrame) on the * {@link #pluginInitClassName}. Finally, removes the plugin from {@link #ALL_PLUGINS}. */ public void tearDown() { OperatorService.unregisterAll(this); if (!RapidMiner.getExecutionMode().isHeadless()) { callInitMethod("tearDownGUI", new Class[] { MainFrame.class }, new Object[] { RapidMinerGUI.getMainFrame() }, false); } callInitMethod("tearDown", new Class[0], new Object[0], false); try { classLoader.close(); } catch (IOException e) { // files could not be closed } ALL_PLUGINS.remove(this); } }