001 package de.deepamehta.core.impl; 002 003 import de.deepamehta.core.osgi.PluginActivator; 004 import de.deepamehta.core.service.PluginInfo; 005 006 import org.osgi.framework.Bundle; 007 008 import java.util.ArrayList; 009 import java.util.HashMap; 010 import java.util.List; 011 import java.util.Map; 012 import java.util.logging.Logger; 013 014 015 016 /** 017 * Activates and deactivates plugins and keeps a pool of activated plugins. 018 * The pool of activated plugins is a shared resource. All access to it is synchronized. 019 * <p> 020 * A PluginManager singleton is hold by the {@link EmbeddedService} and is accessed concurrently 021 * by all bundle activation threads (as created e.g. by the File Install bundle). 022 */ 023 class PluginManager { 024 025 // ---------------------------------------------------------------------------------------------- Instance Variables 026 027 /** 028 * The pool of activated plugins. 029 * 030 * Hashed by plugin bundle's symbolic name, e.g. "de.deepamehta.topicmaps". 031 */ 032 private Map<String, PluginImpl> activatedPlugins = new HashMap(); 033 034 private EmbeddedService dms; 035 036 private Logger logger = Logger.getLogger(getClass().getName()); 037 038 // ---------------------------------------------------------------------------------------------------- Constructors 039 040 PluginManager(EmbeddedService dms) { 041 this.dms = dms; 042 } 043 044 // ----------------------------------------------------------------------------------------- Package Private Methods 045 046 /** 047 * Activates a plugin and fires activation events. 048 * Called once the plugin's requirements are met (see PluginImpl.checkRequirementsForActivation()). 049 * <p> 050 * After activation posts the PLUGIN_ACTIVATED OSGi event. Then checks if all installed plugins are active, and if 051 * so, fires the {@link CoreEvent.ALL_PLUGINS_ACTIVE} core event. 052 * <p> 053 * If the plugin is already activated, performs nothing. This happens e.g. when a dependent plugin is redeployed. 054 * <p> 055 * Note: this method is synchronized. While a plugin is activated no other plugin must be activated. Otherwise 056 * the "type introduction" mechanism might miss some types. Consider this unsynchronized scenario: plugin B 057 * starts running its migrations just in the moment between plugin A's type introduction and event listener 058 * registration. Plugin A might miss some of the types created by plugin B. 059 */ 060 synchronized void activatePlugin(PluginImpl plugin) { 061 // Note: we must not activate a plugin twice. 062 if (!_isPluginActivated(plugin.getUri())) { 063 // 064 _activatePlugin(plugin); 065 // 066 plugin.postPluginActivatedEvent(); 067 // 068 if (checkAllPluginsActivated()) { 069 logger.info("########## All Plugins Active ##########"); 070 dms.fireEvent(CoreEvent.ALL_PLUGINS_ACTIVE); 071 } 072 } else { 073 logger.info("Activation of " + plugin + " ABORTED -- already activated"); 074 return; 075 } 076 } 077 078 synchronized void deactivatePlugin(PluginImpl plugin) { 079 plugin.unregisterListeners(); 080 removeFromActivatedPlugins(plugin.getUri()); 081 } 082 083 // --- 084 085 synchronized boolean isPluginActivated(String pluginUri) { 086 return _isPluginActivated(pluginUri); 087 } 088 089 // --- 090 091 synchronized PluginImpl getPlugin(String pluginUri) { 092 PluginImpl plugin = activatedPlugins.get(pluginUri); 093 if (plugin == null) { 094 throw new RuntimeException("Plugin \"" + pluginUri + "\" not found"); 095 } 096 return plugin; 097 } 098 099 synchronized List<PluginInfo> getPluginInfo() { 100 List info = new ArrayList(); 101 for (PluginImpl plugin : activatedPlugins.values()) { 102 info.add(plugin.getInfo()); 103 } 104 return info; 105 } 106 107 108 109 // ------------------------------------------------------------------------------------------------- Private Methods 110 111 /** 112 * Activates a plugin. 113 * 114 * Activation comprises: 115 * - install the plugin in the database (includes migrations, post-install event, type introduction) 116 * - initialize the plugin 117 * - register the plugin's event listeners 118 * - register the plugin's OSGi service 119 * - add the plugin to the pool of activated plugins 120 */ 121 private void _activatePlugin(PluginImpl plugin) { 122 try { 123 logger.info("----- Activating " + plugin + " -----"); 124 // 125 plugin.installPluginInDB(); 126 plugin.initializePlugin(); 127 plugin.registerListeners(); 128 plugin.registerPluginService(); 129 // Note: the event listeners must be registered *after* the plugin is installed in the database and its 130 // postInstall() hook is triggered (see PluginImpl.installPluginInDB()). 131 // Consider the Access Control plugin: it can't set a topic's creator before the "admin" user is created. 132 // 133 // ### TODO: if initialization fails the plugin's listeners are not registered. 134 // Then, when stopping the plugin unregistering its listeners fails. 135 addToActivatedPlugins(plugin); 136 // 137 logger.info("----- Activation of " + plugin + " complete -----"); 138 } catch (Throwable e) { 139 // Note: we want catch a NoClassDefFoundError here (so we state Throwable instead of Exception). 140 // This might happen in initializePlugin() when the plugin's init() hook instantiates a class 141 // from a 3rd-party library which can't be loaded. 142 // If not catched File Install would retry to deploy the bundle every 2 seconds (endlessly). 143 throw new RuntimeException("Activation of " + plugin + " failed", e); 144 } 145 } 146 147 /** 148 * Checks if all plugins are activated. 149 */ 150 private boolean checkAllPluginsActivated() { 151 Bundle[] bundles = dms.bundleContext.getBundles(); 152 int plugins = 0; 153 int activated = 0; 154 for (Bundle bundle : bundles) { 155 if (isDeepaMehtaPlugin(bundle)) { 156 plugins++; 157 if (_isPluginActivated(bundle.getSymbolicName())) { 158 activated++; 159 } 160 } 161 } 162 logger.info("### Bundles total: " + bundles.length + 163 ", DeepaMehta plugins: " + plugins + ", Activated: " + activated); 164 return plugins == activated; 165 } 166 167 /** 168 * Plugin detection: checks if an arbitrary bundle is a DeepaMehta plugin. 169 */ 170 private boolean isDeepaMehtaPlugin(Bundle bundle) { 171 try { 172 String activatorClassName = (String) bundle.getHeaders().get("Bundle-Activator"); 173 if (activatorClassName != null) { 174 Class activatorClass = bundle.loadClass(activatorClassName); // throws ClassNotFoundException 175 return PluginActivator.class.isAssignableFrom(activatorClass); 176 } else { 177 // Note: 3rd party bundles may have no activator 178 return false; 179 } 180 } catch (Exception e) { 181 throw new RuntimeException("Plugin detection failed for bundle " + bundle, e); 182 } 183 } 184 185 // --- 186 187 private void addToActivatedPlugins(PluginImpl plugin) { 188 activatedPlugins.put(plugin.getUri(), plugin); 189 } 190 191 private void removeFromActivatedPlugins(String pluginUri) { 192 activatedPlugins.remove(pluginUri); 193 } 194 195 private boolean _isPluginActivated(String pluginUri) { 196 return activatedPlugins.get(pluginUri) != null; 197 } 198 }