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 }