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}