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.
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                    dms.fireEvent(CoreEvent.ALL_PLUGINS_ACTIVE);
070                }
071            } else {
072                logger.info("Activation of " + plugin + " ABORTED -- 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 + " ABORTED -- 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 + "\" not found");
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 = dms.bundleContext.getBundles();
121            int plugins = 0;
122            int activated = 0;
123            for (Bundle bundle : bundles) {
124                if (isDeepaMehtaPlugin(bundle)) {
125                    plugins++;
126                    if (_isPluginActivated(bundle.getSymbolicName())) {
127                        activated++;
128                    }
129                }
130            }
131            logger.info("### Bundles total: " + bundles.length +
132                ", DeepaMehta plugins: " + plugins + ", Activated: " + activated);
133            return plugins == activated;
134        }
135    
136        /**
137         * Plugin detection: checks if an arbitrary bundle is a DeepaMehta plugin.
138         */
139        private boolean isDeepaMehtaPlugin(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    }