001package systems.dmx.core.impl; 002 003import systems.dmx.core.osgi.PluginActivator; 004import systems.dmx.core.service.PluginInfo; 005 006import org.osgi.framework.Bundle; 007 008import java.util.ArrayList; 009import java.util.HashMap; 010import java.util.List; 011import java.util.Map; 012import 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 CoreServiceImpl} and is accessed concurrently 021 * by all bundle activation threads (as created e.g. by the File Install bundle). 022 */ 023class PluginManager { 024 025 // ---------------------------------------------------------------------------------------------- Instance Variables 026 027 /** 028 * The pool of activated plugins. 029 * 030 * Hashed by plugin bundle's symbolic name, e.g. "systems.dmx.topicmaps". 031 */ 032 private Map<String, PluginImpl> activatedPlugins = new HashMap(); 033 034 private CoreServiceImpl dmx; 035 036 private Logger logger = Logger.getLogger(getClass().getName()); 037 038 // ---------------------------------------------------------------------------------------------------- Constructors 039 040 PluginManager(CoreServiceImpl dmx) { 041 this.dmx = dmx; 042 } 043 044 // ----------------------------------------------------------------------------------------- Package Private Methods 045 046 /** 047 * Activates a plugin. 048 * Called once the plugin's requirements are met (see PluginImpl.checkRequirementsForActivation()). 049 * <p> 050 * Once the plugin is activated checks if <i>all</i> installed plugins are activated now, and if so, fires the 051 * {@link CoreEvent.ALL_PLUGINS_ACTIVE} core event. 052 * <p> 053 * If the plugin is already activated, nothing is performed. This happens e.g. when a dependent plugin is 054 * redeployed. 055 * <p> 056 * Note: this method is synchronized. While a plugin is activated no other plugin must be activated. Otherwise 057 * the "type introduction" mechanism might miss some types. Consider this unsynchronized scenario: plugin B 058 * starts running its migrations just in the moment between plugin A's type introduction and event listener 059 * registration. Plugin A might miss some of the types created by plugin B. 060 */ 061 synchronized void activatePlugin(PluginImpl plugin) { 062 // Note: we must not activate a plugin twice. 063 if (!_isPluginActivated(plugin.getUri())) { 064 plugin.activate(); 065 addToActivatedPlugins(plugin); 066 // 067 if (checkAllPluginsActivated()) { 068 logger.info("########## All Plugins Active ##########"); 069 dmx.fireEvent(CoreEvent.ALL_PLUGINS_ACTIVE); 070 } 071 } else { 072 logger.info("Activating " + plugin + " SKIPPED -- already activated"); 073 } 074 } 075 076 synchronized void deactivatePlugin(PluginImpl plugin) { 077 // Note: if plugin activation failed its listeners are not registered and it is not in the pool of activated 078 // plugins. Unregistering the listeners and removing from pool would fail. 079 String pluginUri = plugin.getUri(); 080 if (_isPluginActivated(pluginUri)) { 081 plugin.deactivate(); 082 removeFromActivatedPlugins(pluginUri); 083 } else { 084 logger.info("Deactivation of " + plugin + " SKIPPED -- it was not successfully activated"); 085 } 086 } 087 088 // --- 089 090 synchronized boolean isPluginActivated(String pluginUri) { 091 return _isPluginActivated(pluginUri); 092 } 093 094 // --- 095 096 synchronized PluginImpl getPlugin(String pluginUri) { 097 PluginImpl plugin = activatedPlugins.get(pluginUri); 098 if (plugin == null) { 099 throw new RuntimeException("Plugin \"" + pluginUri + "\" is not installed/activated"); 100 } 101 return plugin; 102 } 103 104 synchronized List<PluginInfo> getPluginInfo() { 105 List info = new ArrayList(); 106 for (PluginImpl plugin : activatedPlugins.values()) { 107 info.add(plugin.getInfo()); 108 } 109 return info; 110 } 111 112 113 114 // ------------------------------------------------------------------------------------------------- Private Methods 115 116 /** 117 * Checks if all installed plugins are activated. 118 */ 119 private boolean checkAllPluginsActivated() { 120 Bundle[] bundles = dmx.bundleContext.getBundles(); 121 int plugins = 0; 122 int activated = 0; 123 for (Bundle bundle : bundles) { 124 if (isDMXPlugin(bundle)) { 125 plugins++; 126 if (_isPluginActivated(bundle.getSymbolicName())) { 127 activated++; 128 } 129 } 130 } 131 logger.info("### Bundles total: " + bundles.length + 132 ", DMX plugins: " + plugins + ", Activated: " + activated); 133 return plugins == activated; 134 } 135 136 /** 137 * Plugin detection: checks if an arbitrary bundle is a DMX plugin. 138 */ 139 private boolean isDMXPlugin(Bundle bundle) { 140 try { 141 String activatorClassName = (String) bundle.getHeaders().get("Bundle-Activator"); 142 if (activatorClassName != null) { 143 Class activatorClass = bundle.loadClass(activatorClassName); // throws ClassNotFoundException 144 return PluginActivator.class.isAssignableFrom(activatorClass); 145 } else { 146 // Note: 3rd party bundles may have no activator 147 return false; 148 } 149 } catch (Exception e) { 150 throw new RuntimeException("Plugin detection failed for bundle " + bundle, e); 151 } 152 } 153 154 // --- 155 156 private void addToActivatedPlugins(PluginImpl plugin) { 157 activatedPlugins.put(plugin.getUri(), plugin); 158 } 159 160 private void removeFromActivatedPlugins(String pluginUri) { 161 if (activatedPlugins.remove(pluginUri) == null) { 162 throw new RuntimeException("Removing plugin \"" + pluginUri + "\" from pool of activated plugins failed: " + 163 "not found in " + activatedPlugins); 164 } 165 } 166 167 private boolean _isPluginActivated(String pluginUri) { 168 return activatedPlugins.get(pluginUri) != null; 169 } 170}