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    }