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 addToActivatedPlugins(plugin); 133 // 134 logger.info("----- Activation of " + plugin + " complete -----"); 135 } catch (Exception e) { 136 throw new RuntimeException("Activation of " + plugin + " failed", e); 137 } 138 } 139 140 /** 141 * Checks if all plugins are activated. 142 */ 143 private boolean checkAllPluginsActivated() { 144 Bundle[] bundles = dms.bundleContext.getBundles(); 145 int plugins = 0; 146 int activated = 0; 147 for (Bundle bundle : bundles) { 148 if (isDeepaMehtaPlugin(bundle)) { 149 plugins++; 150 if (_isPluginActivated(bundle.getSymbolicName())) { 151 activated++; 152 } 153 } 154 } 155 logger.info("### Bundles total: " + bundles.length + 156 ", DeepaMehta plugins: " + plugins + ", Activated: " + activated); 157 return plugins == activated; 158 } 159 160 /** 161 * Plugin detection: checks if an arbitrary bundle is a DeepaMehta plugin. 162 */ 163 private boolean isDeepaMehtaPlugin(Bundle bundle) { 164 try { 165 String activatorClassName = (String) bundle.getHeaders().get("Bundle-Activator"); 166 if (activatorClassName != null) { 167 Class activatorClass = bundle.loadClass(activatorClassName); // throws ClassNotFoundException 168 return PluginActivator.class.isAssignableFrom(activatorClass); 169 } else { 170 // Note: 3rd party bundles may have no activator 171 return false; 172 } 173 } catch (Exception e) { 174 throw new RuntimeException("Plugin detection failed for bundle " + bundle, e); 175 } 176 } 177 178 // --- 179 180 private void addToActivatedPlugins(PluginImpl plugin) { 181 activatedPlugins.put(plugin.getUri(), plugin); 182 } 183 184 private void removeFromActivatedPlugins(String pluginUri) { 185 activatedPlugins.remove(pluginUri); 186 } 187 188 private boolean _isPluginActivated(String pluginUri) { 189 return activatedPlugins.get(pluginUri) != null; 190 } 191 }