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 }