View Javadoc

1   package com.atlassian.plugin.manager;
2   
3   import com.atlassian.plugin.ModuleCompleteKey;
4   import com.atlassian.plugin.ModuleDescriptor;
5   import com.atlassian.plugin.ModuleDescriptorFactory;
6   import com.atlassian.plugin.Plugin;
7   import com.atlassian.plugin.PluginAccessor;
8   import com.atlassian.plugin.PluginArtifact;
9   import com.atlassian.plugin.PluginController;
10  import com.atlassian.plugin.PluginException;
11  import com.atlassian.plugin.PluginInstaller;
12  import com.atlassian.plugin.PluginParseException;
13  import com.atlassian.plugin.PluginRestartState;
14  import com.atlassian.plugin.PluginState;
15  import com.atlassian.plugin.PluginSystemLifecycle;
16  import com.atlassian.plugin.StateAware;
17  import com.atlassian.plugin.classloader.PluginsClassLoader;
18  import com.atlassian.plugin.descriptors.UnloadableModuleDescriptor;
19  import com.atlassian.plugin.descriptors.UnloadableModuleDescriptorFactory;
20  import com.atlassian.plugin.event.PluginEventListener;
21  import com.atlassian.plugin.event.PluginEventManager;
22  import com.atlassian.plugin.event.events.PluginDisabledEvent;
23  import com.atlassian.plugin.event.events.PluginEnabledEvent;
24  import com.atlassian.plugin.event.events.PluginFrameworkShutdownEvent;
25  import com.atlassian.plugin.event.events.PluginFrameworkStartedEvent;
26  import com.atlassian.plugin.event.events.PluginFrameworkStartingEvent;
27  import com.atlassian.plugin.event.events.PluginModuleDisabledEvent;
28  import com.atlassian.plugin.event.events.PluginModuleEnabledEvent;
29  import com.atlassian.plugin.event.events.PluginRefreshedEvent;
30  import com.atlassian.plugin.event.events.PluginUpgradedEvent;
31  import com.atlassian.plugin.impl.UnloadablePlugin;
32  import com.atlassian.plugin.impl.UnloadablePluginFactory;
33  import com.atlassian.plugin.loaders.DynamicPluginLoader;
34  import com.atlassian.plugin.loaders.PluginLoader;
35  import com.atlassian.plugin.parsers.DescriptorParserFactory;
36  import com.atlassian.plugin.predicate.EnabledModulePredicate;
37  import com.atlassian.plugin.predicate.EnabledPluginPredicate;
38  import com.atlassian.plugin.predicate.ModuleDescriptorOfClassPredicate;
39  import com.atlassian.plugin.predicate.ModuleDescriptorOfTypePredicate;
40  import com.atlassian.plugin.predicate.ModuleDescriptorPredicate;
41  import com.atlassian.plugin.predicate.ModuleOfClassPredicate;
42  import com.atlassian.plugin.predicate.PluginPredicate;
43  import com.atlassian.plugin.util.PluginUtils;
44  import com.atlassian.plugin.util.WaitUntil;
45  import com.atlassian.plugin.util.collect.CollectionUtil;
46  import static com.atlassian.plugin.util.collect.CollectionUtil.filter;
47  import static com.atlassian.plugin.util.collect.CollectionUtil.toList;
48  import static com.atlassian.plugin.util.collect.CollectionUtil.transform;
49  import com.atlassian.plugin.util.collect.Function;
50  import com.atlassian.plugin.util.collect.Predicate;
51  import org.apache.commons.collections.CollectionUtils;
52  import org.apache.commons.logging.Log;
53  import org.apache.commons.logging.LogFactory;
54  import org.apache.commons.lang.time.StopWatch;
55  
56  import java.io.InputStream;
57  import java.util.ArrayList;
58  import java.util.Collection;
59  import java.util.Collections;
60  import java.util.HashMap;
61  import java.util.HashSet;
62  import java.util.LinkedList;
63  import java.util.List;
64  import java.util.Map;
65  import java.util.Set;
66  import java.util.TreeSet;
67  import java.util.concurrent.ConcurrentHashMap;
68  
69  /**
70   * This implementation delegates the initiation and classloading of plugins to a
71   * list of {@link com.atlassian.plugin.loaders.PluginLoader}s and records the state of plugins in a {@link com.atlassian.plugin.manager.PluginPersistentStateStore}.
72   * <p/>
73   * This class is responsible for enabling and disabling plugins and plugin modules and reflecting these
74   * state changes in the PluginPersistentStateStore.
75   * <p/>
76   * An interesting quirk in the design is that {@link #installPlugin(com.atlassian.plugin.PluginArtifact)} explicitly stores
77   * the plugin via a {@link com.atlassian.plugin.PluginInstaller}, whereas {@link #uninstall(com.atlassian.plugin.Plugin)} relies on the
78   * underlying {@link com.atlassian.plugin.loaders.PluginLoader} to remove the plugin if necessary.
79   */
80  public class DefaultPluginManager implements PluginController, PluginAccessor, PluginSystemLifecycle
81  {
82      private static final Log log = LogFactory.getLog(DefaultPluginManager.class);
83  
84      private final List<PluginLoader> pluginLoaders;
85      private final PluginPersistentStateStore store;
86      private final ModuleDescriptorFactory moduleDescriptorFactory;
87      private final PluginsClassLoader classLoader;
88      private final Map<String, Plugin> plugins = new ConcurrentHashMap<String, Plugin>();
89      private final PluginEventManager pluginEventManager;
90      private final PluginEnabler pluginEnabler = new PluginEnabler(this, this);
91  
92      private final StateTracker tracker = new StateTracker();
93  
94      /**
95       * Installer used for storing plugins. Used by {@link #installPlugin(PluginArtifact)}.
96       */
97      private PluginInstaller pluginInstaller;
98  
99      /**
100      * Stores {@link Plugin}s as a key and {@link PluginLoader} as a value.
101      */
102     private final Map<Plugin, PluginLoader> pluginToPluginLoader = new HashMap<Plugin, PluginLoader>();
103 
104     public DefaultPluginManager(final PluginPersistentStateStore store, final List<PluginLoader> pluginLoaders, final ModuleDescriptorFactory moduleDescriptorFactory, final PluginEventManager pluginEventManager)
105     {
106         if (store == null)
107         {
108             throw new IllegalArgumentException("PluginPersistentStateStore must not be null.");
109         }
110         if (pluginLoaders == null)
111         {
112             throw new IllegalArgumentException("Plugin Loaders list must not be null.");
113         }
114         if (moduleDescriptorFactory == null)
115         {
116             throw new IllegalArgumentException("ModuleDescriptorFactory must not be null.");
117         }
118         if (pluginEventManager == null)
119         {
120             throw new IllegalArgumentException("PluginEventManager must not be null.");
121         }
122         this.pluginLoaders = pluginLoaders;
123         this.store = store;
124         this.moduleDescriptorFactory = moduleDescriptorFactory;
125         this.pluginEventManager = pluginEventManager;
126         this.pluginEventManager.register(this);
127         classLoader = new PluginsClassLoader(this);
128     }
129 
130     /**
131      * Initialize all plugins in all loaders
132      *
133      * @throws PluginParseException
134      * @throws IllegalStateException if already initialized or already in the process of initialization.
135      */
136     public void init() throws PluginParseException
137     {
138         tracker.setState(StateTracker.State.STARTING);
139         StopWatch stopWatch = new StopWatch();
140         stopWatch.start();
141         log.info("Initialising the plugin system");
142         pluginEventManager.broadcast(new PluginFrameworkStartingEvent(this, this));
143         for (final PluginLoader loader : pluginLoaders)
144         {
145             if (loader == null)
146             {
147                 continue;
148             }
149 
150             final Collection<Plugin> possiblePluginsToLoad = loader.loadAllPlugins(moduleDescriptorFactory);
151             final Collection<Plugin> pluginsToLoad = new ArrayList<Plugin>();
152             for (final Plugin plugin : possiblePluginsToLoad)
153             {
154                 if (getState().getPluginRestartState(plugin.getKey()) == PluginRestartState.REMOVE)
155                 {
156                     loader.removePlugin(plugin);
157 
158                     // PLUG-13: Plugins should not save state across uninstalls.
159                     removeStateFromStore(getStore(), plugin);
160                 }
161                 else
162                 {
163                     pluginsToLoad.add(plugin);
164                 }
165             }
166             addPlugins(loader, pluginsToLoad);
167         }
168 
169         final DefaultPluginPersistentState currentState = new DefaultPluginPersistentState(getStore().load());
170         currentState.clearPluginRestartState();
171         getStore().save(currentState);
172 
173         pluginEventManager.broadcast(new PluginFrameworkStartedEvent(this, this));
174         stopWatch.stop();
175         log.info("Plugin system started in " + stopWatch);
176         tracker.setState(StateTracker.State.STARTED);
177     }
178 
179     /**
180      * Fires the shutdown event
181      * @since 2.0.0
182      * @throws IllegalStateException if already shutdown or already in the process of shutting down.
183      */
184     public void shutdown()
185     {
186         tracker.setState(StateTracker.State.SHUTTING_DOWN);
187         log.info("Shutting down the plugin system");
188         pluginEventManager.broadcast(new PluginFrameworkShutdownEvent(this, this));
189         tracker.setState(StateTracker.State.SHUTDOWN);
190         plugins.clear();
191         pluginEventManager.unregister(this);
192     }
193 
194     @PluginEventListener
195     public void onPluginRefresh(final PluginRefreshedEvent event)
196     {
197         final Plugin plugin = event.getPlugin();
198 
199         // disable the plugin, shamefully copied from notifyPluginDisabled()
200         final List<ModuleDescriptor<?>> moduleDescriptors = new ArrayList<ModuleDescriptor<?>>(plugin.getModuleDescriptors());
201         Collections.reverse(moduleDescriptors); // disable in reverse order
202 
203         for (final ModuleDescriptor<?> module : moduleDescriptors)
204         {
205             // don't actually disable the module, just fire the events because its plugin is being disabled
206             // if the module was actually disabled, you'd have to reenable each one when enabling the plugin
207 
208             if (isPluginModuleEnabled(module.getCompleteKey()))
209             {
210                 publishModuleDisabledEvents(module);
211             }
212         }
213 
214         // enable the plugin, shamefully copied from notifyPluginEnabled()
215         classLoader.notifyPluginOrModuleEnabled();
216         enablePluginModules(plugin);
217         pluginEventManager.broadcast(new PluginEnabledEvent(plugin));
218     }
219 
220     /**
221      * Set the plugin installation strategy for this manager
222      *
223      * @param pluginInstaller the plugin installation strategy to use
224      * @see PluginInstaller
225      */
226     public void setPluginInstaller(final PluginInstaller pluginInstaller)
227     {
228         this.pluginInstaller = pluginInstaller;
229     }
230 
231     protected final PluginPersistentStateStore getStore()
232     {
233         return store;
234     }
235 
236     public String installPlugin(final PluginArtifact pluginArtifact) throws PluginParseException
237     {
238         final String key = validatePlugin(pluginArtifact);
239         pluginInstaller.installPlugin(key, pluginArtifact);
240         scanForNewPlugins();
241         return key;
242     }
243 
244     /**
245      * Validate a plugin jar.  Looks through all plugin loaders for ones that can load the plugin and
246      * extract the plugin key as proof.
247      *
248      * @param pluginArtifact the jar file representing the plugin
249      * @return The plugin key
250      * @throws PluginParseException if the plugin cannot be parsed
251      * @throws NullPointerException if <code>pluginJar</code> is null.
252      */
253     String validatePlugin(final PluginArtifact pluginArtifact) throws PluginParseException
254     {
255         boolean foundADynamicPluginLoader = false;
256         for (final PluginLoader loader : pluginLoaders)
257         {
258             if (loader instanceof DynamicPluginLoader)
259             {
260                 foundADynamicPluginLoader = true;
261                 final String key = ((DynamicPluginLoader) loader).canLoad(pluginArtifact);
262                 if (key != null)
263                 {
264                     return key;
265                 }
266             }
267         }
268 
269         if (!foundADynamicPluginLoader)
270         {
271             throw new IllegalStateException("Should be at least one DynamicPluginLoader in the plugin loader list");
272         }
273         throw new PluginParseException("Jar " + pluginArtifact.getName() + " is not a valid plugin");
274     }
275 
276     public int scanForNewPlugins() throws PluginParseException
277     {
278         int numberFound = 0;
279 
280         for (final PluginLoader loader : pluginLoaders)
281         {
282             if (loader != null)
283             {
284                 if (loader.supportsAddition())
285                 {
286                     final List<Plugin> pluginsToAdd = new ArrayList<Plugin>();
287                     for (Plugin plugin : loader.addFoundPlugins(moduleDescriptorFactory))
288                     {
289                         final Plugin oldPlugin = plugins.get(plugin.getKey());
290                         // Only actually install the plugin if its module descriptors support it.  Otherwise, mark it as
291                         // unloadable.
292                         if (!(plugin instanceof UnloadablePlugin))
293                         {
294                             if (PluginUtils.doesPluginRequireRestart(plugin))
295                             {
296                                 if (oldPlugin == null)
297                                 {
298                                     final DefaultPluginPersistentState currentState = new DefaultPluginPersistentState(getStore().load());
299                                     currentState.setPluginRestartState(plugin.getKey(), PluginRestartState.INSTALL);
300                                     getStore().save(currentState);
301 
302                                     final UnloadablePlugin unloadablePlugin = new UnloadablePlugin("Plugin requires a restart of the application");
303                                     unloadablePlugin.setKey(plugin.getKey());
304                                     plugin = unloadablePlugin;
305                                 }
306                                 else
307                                 {
308                                     final DefaultPluginPersistentState currentState = new DefaultPluginPersistentState(getStore().load());
309                                     currentState.setPluginRestartState(plugin.getKey(), PluginRestartState.UPGRADE);
310                                     getStore().save(currentState);
311                                     continue;
312                                 }
313                             }
314 
315                             // Check to ensure that the old plugin didn't require restart, even if the new one doesn't
316                             else if ((oldPlugin != null) && PluginUtils.doesPluginRequireRestart(oldPlugin))
317                             {
318                                 final DefaultPluginPersistentState currentState = new DefaultPluginPersistentState(getStore().load());
319                                 currentState.setPluginRestartState(plugin.getKey(), PluginRestartState.UPGRADE);
320                                 getStore().save(currentState);
321                                 continue;
322                             }
323                             pluginsToAdd.add(plugin);
324                         }
325 
326                     }
327                     addPlugins(loader, pluginsToAdd);
328                     numberFound = pluginsToAdd.size();
329                 }
330             }
331         }
332 
333         return numberFound;
334     }
335 
336     /**
337      *
338      * @param plugin
339      * @throws PluginException If the plugin or loader doesn't support uninstallation
340      */
341     public void uninstall(final Plugin plugin) throws PluginException
342     {
343         if (PluginUtils.doesPluginRequireRestart(plugin))
344         {
345             ensurePluginAndLoaderSupportsUninstall(plugin);
346             final DefaultPluginPersistentState currentState = new DefaultPluginPersistentState(getStore().load());
347             currentState.setPluginRestartState(plugin.getKey(), PluginRestartState.REMOVE);
348             getStore().save(currentState);
349         }
350         else
351         {
352             unloadPlugin(plugin);
353 
354             // PLUG-13: Plugins should not save state across uninstalls.
355             removeStateFromStore(getStore(), plugin);
356         }
357     }
358 
359     protected void removeStateFromStore(final PluginPersistentStateStore stateStore, final Plugin plugin)
360     {
361         final DefaultPluginPersistentState currentState = new DefaultPluginPersistentState(stateStore.load());
362         currentState.removeState(plugin.getKey());
363         for (final ModuleDescriptor<?> moduleDescriptor : plugin.getModuleDescriptors())
364         {
365             currentState.removeState(moduleDescriptor.getCompleteKey());
366         }
367         stateStore.save(currentState);
368     }
369 
370     /**
371      * Unload a plugin. Called when plugins are added locally,
372      * or remotely in a clustered application.
373      *
374      * @param plugin the plugin to remove
375      * @throws PluginException if th eplugin cannot be uninstalled
376      */
377     protected void unloadPlugin(final Plugin plugin) throws PluginException
378     {
379         final PluginLoader loader = ensurePluginAndLoaderSupportsUninstall(plugin);
380 
381         if (isPluginEnabled(plugin.getKey()))
382         {
383             notifyPluginDisabled(plugin);
384         }
385 
386         notifyUninstallPlugin(plugin);
387         if (loader != null)
388         {
389             removePluginFromLoader(plugin);
390         }
391 
392         plugins.remove(plugin.getKey());
393     }
394 
395     private PluginLoader ensurePluginAndLoaderSupportsUninstall(final Plugin plugin)
396     {
397         if (!plugin.isUninstallable())
398         {
399             throw new PluginException("Plugin is not uninstallable: " + plugin.getKey());
400         }
401 
402         final PluginLoader loader = pluginToPluginLoader.get(plugin);
403 
404         if ((loader != null) && !loader.supportsRemoval())
405         {
406             throw new PluginException("Not uninstalling plugin - loader doesn't allow removal. Plugin: " + plugin.getKey());
407         }
408         return loader;
409     }
410 
411     private void removePluginFromLoader(final Plugin plugin) throws PluginException
412     {
413         if (plugin.isDeleteable())
414         {
415             final PluginLoader pluginLoader = pluginToPluginLoader.get(plugin);
416             pluginLoader.removePlugin(plugin);
417         }
418 
419         pluginToPluginLoader.remove(plugin);
420     }
421 
422     protected void notifyUninstallPlugin(final Plugin plugin)
423     {
424         classLoader.notifyUninstallPlugin(plugin);
425 
426         for (final ModuleDescriptor<?> descriptor : plugin.getModuleDescriptors())
427         {
428             descriptor.destroy(plugin);
429         }
430     }
431 
432     protected PluginPersistentState getState()
433     {
434         return getStore().load();
435     }
436 
437     /**
438      * @deprecated Since 2.0.2, use {@link #addPlugins(PluginLoader,Collection<Plugin>...)} instead
439      */
440     @Deprecated
441     protected void addPlugin(final PluginLoader loader, final Plugin plugin) throws PluginParseException
442     {
443         addPlugins(loader, Collections.singletonList(plugin));
444     }
445 
446     /**
447      * Update the local plugin state and enable state aware modules.
448      * <p>
449      * If there is an existing plugin with the same key, the version strings of the existing plugin and the plugin
450      * provided to this method will be parsed and compared.  If the installed version is newer than the provided
451      * version, it will not be changed.  If the specified plugin's version is the same or newer, the existing plugin
452      * state will be saved and the plugin will be unloaded before the provided plugin is installed.  If the existing
453      * plugin cannot be unloaded a {@link PluginException} will be thrown.
454      *
455      * @param loader the loader used to load this plugin
456      * @param pluginsToInstall the plugins to add
457      * @throws PluginParseException if the plugin cannot be parsed
458      * @since 2.0.2
459      */
460     protected void addPlugins(final PluginLoader loader, final Collection<Plugin> pluginsToInstall) throws PluginParseException
461     {
462         final Set<Plugin> pluginsToEnable = new HashSet<Plugin>();
463 
464         // Install plugins, looking for upgrades and duplicates
465         for (final Plugin plugin : new TreeSet<Plugin>(pluginsToInstall))
466         {
467             boolean pluginUpgraded = false;
468             // testing to make sure plugin keys are unique
469             if (plugins.containsKey(plugin.getKey()))
470             {
471                 final Plugin existingPlugin = plugins.get(plugin.getKey());
472                 if (plugin.compareTo(existingPlugin) >= 0)
473                 {
474                     try
475                     {
476                         updatePlugin(existingPlugin, plugin);
477                         pluginUpgraded = true;
478                     }
479                     catch (final PluginException e)
480                     {
481                         throw new PluginParseException(
482                             "Duplicate plugin found (installed version is the same or older) and could not be unloaded: '" + plugin.getKey() + "'", e);
483                     }
484                 }
485                 else
486                 {
487                     // If we find an older plugin, don't error, just ignore it. PLUG-12.
488                     if (log.isDebugEnabled())
489                     {
490                         log.debug("Duplicate plugin found (installed version is newer): '" + plugin.getKey() + "'");
491                     }
492                     // and don't install the older plugin
493                     continue;
494                 }
495             }
496 
497             plugin.install();
498             if (getState().isEnabled(plugin))
499             {
500                 pluginsToEnable.add(plugin);
501             }
502 
503             if (pluginUpgraded)
504             {
505                 pluginEventManager.broadcast(new PluginUpgradedEvent(plugin));
506             }
507             plugins.put(plugin.getKey(), plugin);
508             pluginToPluginLoader.put(plugin, loader);
509         }
510 
511         // enable all plugins, waiting a time period for them to enable
512         pluginEnabler.enable(pluginsToEnable);
513 
514         // handle the plugins that were able to be successfully enabled
515         for (final Plugin plugin : pluginsToInstall)
516         {
517             if (plugin.getPluginState() == PluginState.ENABLED)
518             {
519                 // This method enables the plugin modules
520                 enablePluginModules(plugin);
521                 pluginEventManager.broadcast(new PluginEnabledEvent(plugin));
522             }
523         }
524     }
525 
526     /**
527      * Replace an already loaded plugin with another version. Relevant stored configuration for the plugin will be
528      * preserved.
529      *
530      * @param oldPlugin Plugin to replace
531      * @param newPlugin New plugin to install
532      * @throws PluginException if the plugin cannot be updated
533      */
534     protected void updatePlugin(final Plugin oldPlugin, final Plugin newPlugin) throws PluginException
535     {
536         if (!oldPlugin.getKey().equals(newPlugin.getKey()))
537         {
538             throw new IllegalArgumentException("New plugin must have the same key as the old plugin");
539         }
540 
541         if (log.isInfoEnabled())
542         {
543             log.info("Updating plugin '" + oldPlugin + "' to '" + newPlugin + "'");
544         }
545 
546         // Preserve the old plugin configuration - uninstall changes it (as disable is called on all modules) and then
547         // removes it
548         final Map<String, Boolean> oldPluginState = new HashMap<String, Boolean>(getState().getPluginStateMap(oldPlugin));
549 
550         if (log.isDebugEnabled())
551         {
552             log.debug("Uninstalling old plugin: " + oldPlugin);
553         }
554         uninstall(oldPlugin);
555         if (log.isDebugEnabled())
556         {
557             log.debug("Plugin uninstalled '" + oldPlugin + "', preserving old state");
558         }
559 
560         // Build a set of module keys from the new plugin version
561         final Set<String> newModuleKeys = new HashSet<String>();
562         newModuleKeys.add(newPlugin.getKey());
563 
564         for (final ModuleDescriptor<?> moduleDescriptor : newPlugin.getModuleDescriptors())
565         {
566             newModuleKeys.add(moduleDescriptor.getCompleteKey());
567         }
568 
569         // Remove any keys from the old plugin state that do not exist in the new version
570         CollectionUtils.filter(oldPluginState.keySet(), new org.apache.commons.collections.Predicate()
571         {
572             public boolean evaluate(final Object o)
573             {
574                 return newModuleKeys.contains(o);
575             }
576         });
577 
578         // Restore the configuration
579         final DefaultPluginPersistentState currentState = new DefaultPluginPersistentState(getStore().load());
580         currentState.addState(oldPluginState);
581         getStore().save(currentState);
582     }
583 
584     public Collection<Plugin> getPlugins()
585     {
586         return plugins.values();
587     }
588 
589     /**
590      * @see PluginAccessor#getPlugins(com.atlassian.plugin.predicate.PluginPredicate)
591      * @since 0.17
592      */
593     public Collection<Plugin> getPlugins(final PluginPredicate pluginPredicate)
594     {
595         return toList(filter(getPlugins(), new Predicate<Plugin>()
596         {
597             public boolean evaluate(final Plugin plugin)
598             {
599                 return pluginPredicate.matches(plugin);
600             }
601         }));
602     }
603 
604     /**
605      * @see PluginAccessor#getEnabledPlugins()
606      */
607     public Collection<Plugin> getEnabledPlugins()
608     {
609         return getPlugins(new EnabledPluginPredicate(this));
610     }
611 
612     /**
613      * @see PluginAccessor#getModules(com.atlassian.plugin.predicate.ModuleDescriptorPredicate)
614      * @since 0.17
615      */
616     public <M> Collection<M> getModules(final ModuleDescriptorPredicate<M> moduleDescriptorPredicate)
617     {
618         final Collection<ModuleDescriptor<M>> moduleDescriptors = getModuleDescriptors(moduleDescriptorPredicate);
619         return getModules(moduleDescriptors);
620     }
621 
622     /**
623      * @see PluginAccessor#getModuleDescriptors(com.atlassian.plugin.predicate.ModuleDescriptorPredicate)
624      * @since 0.17
625      */
626     public <M> Collection<ModuleDescriptor<M>> getModuleDescriptors(final ModuleDescriptorPredicate<M> moduleDescriptorPredicate)
627     {
628         final List<ModuleDescriptor<M>> moduleDescriptors = getModuleDescriptorsList(getPlugins());
629         return toList(filter(moduleDescriptors, new Predicate<ModuleDescriptor<M>>()
630         {
631             public boolean evaluate(final ModuleDescriptor<M> input)
632             {
633                 return moduleDescriptorPredicate.matches(input);
634             }
635         }));
636     }
637 
638     /**
639      * Get the all the module descriptors from the given collection of plugins.
640      * <p>
641      * Be careful, this does not actually return a list of ModuleDescriptors that are M, it returns all
642      * ModuleDescriptors of all types, you must further filter the list as required.
643      *
644      * @param plugins a collection of {@link Plugin}s
645      * @return a collection of {@link ModuleDescriptor}s
646      */
647     private <M> List<ModuleDescriptor<M>> getModuleDescriptorsList(final Collection<Plugin> plugins)
648     {
649         // hack way to get typed descriptors from plugin and keep generics happy
650         final List<ModuleDescriptor<M>> moduleDescriptors = new LinkedList<ModuleDescriptor<M>>();
651         for (final Plugin plugin : plugins)
652         {
653             final Collection<ModuleDescriptor<?>> descriptors = plugin.getModuleDescriptors();
654             for (final ModuleDescriptor<?> moduleDescriptor : descriptors)
655             {
656                 @SuppressWarnings("unchecked")
657                 final ModuleDescriptor<M> typedDescriptor = (ModuleDescriptor<M>) moduleDescriptor;
658                 moduleDescriptors.add(typedDescriptor);
659             }
660         }
661         return moduleDescriptors;
662     }
663 
664     /**
665      * Get the modules of all the given descriptor.  If any of the getModule() calls fails, the error is recorded in
666      * the logs and the plugin is disabled.
667      *
668      * @param moduleDescriptors the collection of module descriptors to get the modules from.
669      * @return a {@link Collection} modules that can be any type of object.
670      *         This collection will not contain any null value.
671      */
672     private <M> List<M> getModules(final Iterable<ModuleDescriptor<M>> moduleDescriptors)
673     {
674         final Set<String> pluginsToDisable = new HashSet<String>();
675         final List<M> modules = transform(moduleDescriptors, new Function<ModuleDescriptor<M>, M>()
676         {
677             public M get(final ModuleDescriptor<M> input)
678             {
679                 M result = null;
680                 try
681                 {
682                     result = input.getModule();
683                 }
684                 catch (final RuntimeException ex)
685                 {
686                     log.error("Exception when retrieving plugin module " + input.getKey() + ", will disable plugin " + input.getPlugin().getKey(), ex);
687                     pluginsToDisable.add(input.getPlugin().getKey());
688                 }
689                 return result;
690             }
691         });
692 
693         for (final String badPluginKey : pluginsToDisable)
694         {
695             disablePlugin(badPluginKey);
696         }
697         return modules;
698     }
699 
700     public Plugin getPlugin(final String key)
701     {
702         return plugins.get(key);
703     }
704 
705     public Plugin getEnabledPlugin(final String pluginKey)
706     {
707         if (!isPluginEnabled(pluginKey))
708         {
709             return null;
710         }
711         return getPlugin(pluginKey);
712     }
713 
714     public ModuleDescriptor<?> getPluginModule(final String completeKey)
715     {
716         final ModuleCompleteKey key = new ModuleCompleteKey(completeKey);
717         final Plugin plugin = getPlugin(key.getPluginKey());
718 
719         if (plugin == null)
720         {
721             return null;
722         }
723         return plugin.getModuleDescriptor(key.getModuleKey());
724     }
725 
726     public ModuleDescriptor<?> getEnabledPluginModule(final String completeKey)
727     {
728         final ModuleCompleteKey key = new ModuleCompleteKey(completeKey);
729 
730         // If it's disabled, return null
731         if (!isPluginModuleEnabled(completeKey))
732         {
733             return null;
734         }
735 
736         return getEnabledPlugin(key.getPluginKey()).getModuleDescriptor(key.getModuleKey());
737     }
738 
739     /**
740      * @see PluginAccessor#getEnabledModulesByClass(Class)
741      */
742     public <M> List<M> getEnabledModulesByClass(final Class<M> moduleClass)
743     {
744         return getModules(getEnabledModuleDescriptorsByModuleClass(moduleClass));
745     }
746 
747     /**
748      * @see PluginAccessor#getEnabledModulesByClassAndDescriptor(Class[], Class)
749      * @deprecated since 0.17, use {@link #getModules(com.atlassian.plugin.predicate.ModuleDescriptorPredicate)} with an appropriate predicate instead.
750      */
751     @Deprecated
752     public <M> List<M> getEnabledModulesByClassAndDescriptor(final Class<ModuleDescriptor<M>>[] descriptorClasses, final Class<M> moduleClass)
753     {
754         final Iterable<ModuleDescriptor<M>> moduleDescriptors = filterModuleDescriptors(getEnabledModuleDescriptorsByModuleClass(moduleClass),
755             new ModuleDescriptorOfClassPredicate<M>(descriptorClasses));
756 
757         return getModules(moduleDescriptors);
758     }
759 
760     /**
761      * @see PluginAccessor#getEnabledModulesByClassAndDescriptor(Class, Class)
762      * @deprecated since 0.17, use {@link #getModules(com.atlassian.plugin.predicate.ModuleDescriptorPredicate)} with an appropriate predicate instead.
763      */
764     @Deprecated
765     public <M> List<M> getEnabledModulesByClassAndDescriptor(final Class<ModuleDescriptor<M>> descriptorClass, final Class<M> moduleClass)
766     {
767         final Iterable<ModuleDescriptor<M>> moduleDescriptors = getEnabledModuleDescriptorsByModuleClass(moduleClass);
768         return getModules(filterModuleDescriptors(moduleDescriptors, new ModuleDescriptorOfClassPredicate<M>(descriptorClass)));
769     }
770 
771     /**
772      * Get all module descriptor that are enabled and for which the module is an instance of the given class.
773      *
774      * @param moduleClass the class of the module within the module descriptor.
775      * @return a collection of {@link ModuleDescriptor}s
776      */
777     private <M> Collection<ModuleDescriptor<M>> getEnabledModuleDescriptorsByModuleClass(final Class<M> moduleClass)
778     {
779         Iterable<ModuleDescriptor<M>> moduleDescriptors = getModuleDescriptorsList(getEnabledPlugins());
780         moduleDescriptors = filterModuleDescriptors(moduleDescriptors, new ModuleOfClassPredicate<M>(moduleClass));
781         moduleDescriptors = filterModuleDescriptors(moduleDescriptors, new EnabledModulePredicate<M>(this));
782 
783         return toList(moduleDescriptors);
784     }
785 
786     public <D extends ModuleDescriptor<?>> List<D> getEnabledModuleDescriptorsByClass(final Class<D> descriptorClazz)
787     {
788         return getEnabledModuleDescriptorsByClass(descriptorClazz, false);
789     }
790 
791     /**
792      * This method has been reverted to pre PLUG-40 to fix performance issues that were encountered during
793      * load testing. This should be reverted to the state it was in at 54639 when the fundamental issue leading
794      * to this slowdown has been corrected (that is, slowness of PluginClassLoader).
795      *
796      * @see PluginAccessor#getEnabledModuleDescriptorsByClass(Class)
797      */
798     public <D extends ModuleDescriptor<?>> List<D> getEnabledModuleDescriptorsByClass(final Class<D> descriptorClazz, final boolean verbose)
799     {
800         final List<D> result = new LinkedList<D>();
801         for (final Plugin plugin : plugins.values())
802         {
803             // Skip disabled plugins
804             if (!isPluginEnabled(plugin.getKey()))
805             {
806                 if (verbose && log.isInfoEnabled())
807                 {
808                     log.info("Plugin [" + plugin.getKey() + "] is disabled.");
809                 }
810                 continue;
811             }
812 
813             for (final ModuleDescriptor<?> module : plugin.getModuleDescriptors())
814             {
815                 if (descriptorClazz.isInstance(module) && isPluginModuleEnabled(module.getCompleteKey()))
816                 {
817                     @SuppressWarnings("unchecked")
818                     final D moduleDescriptor = (D) module;
819                     result.add(moduleDescriptor);
820                 }
821                 else
822                 {
823                     if (verbose && log.isInfoEnabled())
824                     {
825                         log.info("Module [" + module.getCompleteKey() + "] is disabled.");
826                     }
827                 }
828             }
829         }
830 
831         return result;
832     }
833 
834     /**
835      * @see PluginAccessor#getEnabledModuleDescriptorsByType(String)
836      * @deprecated since 0.17, use {@link #getModuleDescriptors(com.atlassian.plugin.predicate.ModuleDescriptorPredicate)} with an appropriate predicate instead.
837      */
838     @Deprecated
839     public <M> List<ModuleDescriptor<M>> getEnabledModuleDescriptorsByType(final String type) throws PluginParseException, IllegalArgumentException
840     {
841         Iterable<ModuleDescriptor<M>> moduleDescriptors = getModuleDescriptorsList(getEnabledPlugins());
842         moduleDescriptors = filterModuleDescriptors(moduleDescriptors, new ModuleDescriptorOfTypePredicate<M>(moduleDescriptorFactory, type));
843         moduleDescriptors = filterModuleDescriptors(moduleDescriptors, new EnabledModulePredicate<M>(this));
844         return toList(moduleDescriptors);
845     }
846 
847     /**
848      * Filters out a collection of {@link ModuleDescriptor}s given a predicate.
849      *
850      * @param moduleDescriptors         the collection of {@link ModuleDescriptor}s to filter.
851      * @param moduleDescriptorPredicate the predicate to use for filtering.
852      */
853     private static <M> Iterable<ModuleDescriptor<M>> filterModuleDescriptors(final Iterable<ModuleDescriptor<M>> moduleDescriptors, final ModuleDescriptorPredicate<M> moduleDescriptorPredicate)
854     {
855         return CollectionUtil.filter(moduleDescriptors, new Predicate<ModuleDescriptor<M>>()
856         {
857             public boolean evaluate(final ModuleDescriptor<M> input)
858             {
859                 return moduleDescriptorPredicate.matches(input);
860             }
861         });
862     }
863 
864     public void enablePlugin(final String key)
865     {
866         if (key == null)
867         {
868             throw new IllegalArgumentException("You must specify a plugin key to disable.");
869         }
870 
871         if (!plugins.containsKey(key))
872         {
873             if (log.isInfoEnabled())
874             {
875                 log.info("No plugin was found for key '" + key + "'. Not enabling.");
876             }
877 
878             return;
879         }
880 
881         final Plugin plugin = plugins.get(key);
882 
883         if (!plugin.getPluginInformation().satisfiesMinJavaVersion())
884         {
885             log.error("Minimum Java version of '" + plugin.getPluginInformation().getMinJavaVersion() + "' was not satisfied for module '" + key + "'. Not enabling.");
886             return;
887         }
888 
889         pluginEnabler.enableRecursively(plugin);
890 
891         if (plugin.getPluginState().equals(PluginState.ENABLED))
892         {
893             enablePluginState(plugin, getStore());
894             notifyPluginEnabled(plugin);
895         }
896     }
897 
898     protected void enablePluginState(final Plugin plugin, final PluginPersistentStateStore stateStore)
899     {
900         final DefaultPluginPersistentState currentState = new DefaultPluginPersistentState(stateStore.load());
901         currentState.setEnabled(plugin, true);
902         stateStore.save(currentState);
903     }
904 
905     /**
906      * Called on all clustered application nodes, rather than {@link #enablePlugin(String)}
907      * to just update the local state, state aware modules and loaders, but not affect the
908      * global plugin state.
909      *
910      * @param plugin the plugin being enabled
911      */
912     protected void notifyPluginEnabled(final Plugin plugin)
913     {
914         plugin.enable();
915         classLoader.notifyPluginOrModuleEnabled();
916         enablePluginModules(plugin);
917         pluginEventManager.broadcast(new PluginEnabledEvent(plugin));
918     }
919 
920     /**
921      * For each module in the plugin, call the module descriptor's enabled() method if the module is StateAware and enabled.
922      *
923      * @param plugin the plugin to enable
924      */
925     private void enablePluginModules(final Plugin plugin)
926     {
927         for (final ModuleDescriptor<?> descriptor : plugin.getModuleDescriptors())
928         {
929             if (!isPluginModuleEnabled(descriptor.getCompleteKey()))
930             {
931                 if (log.isDebugEnabled())
932                 {
933                     log.debug("Plugin module is disabled, so not enabling ModuleDescriptor '" + descriptor.getName() + "'.");
934                 }
935                 continue;
936             }
937 
938             try
939             {
940                 notifyModuleEnabled(descriptor);
941             }
942             catch (final Throwable exception) // catch any errors and insert an UnloadablePlugin (PLUG-7)
943             {
944                 log.error("There was an error loading the descriptor '" + descriptor.getName() + "' of plugin '" + plugin.getKey() + "'. Disabling.",
945                     exception);
946                 replacePluginWithUnloadablePlugin(plugin, descriptor, exception);
947             }
948         }
949         classLoader.notifyPluginOrModuleEnabled();
950     }
951 
952     public void disablePlugin(final String key)
953     {
954         if (key == null)
955         {
956             throw new IllegalArgumentException("You must specify a plugin key to disable.");
957         }
958 
959         if (!plugins.containsKey(key))
960         {
961             if (log.isInfoEnabled())
962             {
963                 log.info("No plugin was found for key '" + key + "'. Not disabling.");
964             }
965 
966             return;
967         }
968 
969         final Plugin plugin = plugins.get(key);
970 
971         notifyPluginDisabled(plugin);
972         disablePluginState(plugin, getStore());
973     }
974 
975     protected void disablePluginState(final Plugin plugin, final PluginPersistentStateStore stateStore)
976     {
977         final DefaultPluginPersistentState currentState = new DefaultPluginPersistentState(stateStore.load());
978         currentState.setEnabled(plugin, false);
979         stateStore.save(currentState);
980     }
981 
982     protected void notifyPluginDisabled(final Plugin plugin)
983     {
984         final List<ModuleDescriptor<?>> moduleDescriptors = new ArrayList<ModuleDescriptor<?>>(plugin.getModuleDescriptors());
985         Collections.reverse(moduleDescriptors); // disable in reverse order
986 
987         for (final ModuleDescriptor<?> module : moduleDescriptors)
988         {
989             // don't actually disable the module, just fire the events because its plugin is being disabled
990             // if the module was actually disabled, you'd have to reenable each one when enabling the plugin
991 
992             if (isPluginModuleEnabled(module.getCompleteKey()))
993             {
994                 publishModuleDisabledEvents(module);
995             }
996         }
997 
998         // This needs to happen after modules are disabled to prevent errors
999         plugin.disable();
1000         pluginEventManager.broadcast(new PluginDisabledEvent(plugin));
1001     }
1002 
1003     public void disablePluginModule(final String completeKey)
1004     {
1005         if (completeKey == null)
1006         {
1007             throw new IllegalArgumentException("You must specify a plugin module key to disable.");
1008         }
1009 
1010         final ModuleDescriptor<?> module = getPluginModule(completeKey);
1011 
1012         if (module == null)
1013         {
1014             if (log.isInfoEnabled())
1015             {
1016                 log.info("Returned module for key '" + completeKey + "' was null. Not disabling.");
1017             }
1018 
1019             return;
1020         }
1021         disablePluginModuleState(module, getStore());
1022         notifyModuleDisabled(module);
1023     }
1024 
1025     protected void disablePluginModuleState(final ModuleDescriptor<?> module, final PluginPersistentStateStore stateStore)
1026     {
1027         final DefaultPluginPersistentState currentState = new DefaultPluginPersistentState(stateStore.load());
1028         currentState.setEnabled(module, false);
1029         stateStore.save(currentState);
1030     }
1031 
1032     protected void notifyModuleDisabled(final ModuleDescriptor<?> module)
1033     {
1034         publishModuleDisabledEvents(module);
1035     }
1036 
1037     private void publishModuleDisabledEvents(final ModuleDescriptor<?> module)
1038     {
1039         if (log.isDebugEnabled())
1040         {
1041             log.debug("Disabling " + module.getKey());
1042         }
1043 
1044         if (module instanceof StateAware)
1045         {
1046             ((StateAware) module).disabled();
1047         }
1048 
1049         pluginEventManager.broadcast(new PluginModuleDisabledEvent(module));
1050     }
1051 
1052     public void enablePluginModule(final String completeKey)
1053     {
1054         if (completeKey == null)
1055         {
1056             throw new IllegalArgumentException("You must specify a plugin module key to disable.");
1057         }
1058 
1059         final ModuleDescriptor<?> module = getPluginModule(completeKey);
1060 
1061         if (module == null)
1062         {
1063             if (log.isInfoEnabled())
1064             {
1065                 log.info("Returned module for key '" + completeKey + "' was null. Not enabling.");
1066             }
1067 
1068             return;
1069         }
1070 
1071         if (!module.satisfiesMinJavaVersion())
1072         {
1073             log.error("Minimum Java version of '" + module.getMinJavaVersion() + "' was not satisfied for module '" + completeKey + "'. Not enabling.");
1074             return;
1075         }
1076         enablePluginModuleState(module, getStore());
1077         notifyModuleEnabled(module);
1078     }
1079 
1080     protected void enablePluginModuleState(final ModuleDescriptor<?> module, final PluginPersistentStateStore stateStore)
1081     {
1082         final DefaultPluginPersistentState currentState = new DefaultPluginPersistentState(stateStore.load());
1083         currentState.setEnabled(module, true);
1084         stateStore.save(currentState);
1085     }
1086 
1087     protected void notifyModuleEnabled(final ModuleDescriptor<?> module)
1088     {
1089         if (log.isDebugEnabled())
1090         {
1091             log.debug("Enabling " + module.getKey());
1092         }
1093         classLoader.notifyPluginOrModuleEnabled();
1094         if (module instanceof StateAware)
1095         {
1096             ((StateAware) module).enabled();
1097         }
1098         pluginEventManager.broadcast(new PluginModuleEnabledEvent(module));
1099     }
1100 
1101     public boolean isPluginModuleEnabled(final String completeKey)
1102     {
1103         // completeKey may be null
1104         if (completeKey == null)
1105         {
1106             return false;
1107         }
1108         final ModuleCompleteKey key = new ModuleCompleteKey(completeKey);
1109 
1110         final ModuleDescriptor<?> pluginModule = getPluginModule(completeKey);
1111         return isPluginEnabled(key.getPluginKey()) && (pluginModule != null) && getState().isEnabled(pluginModule);
1112     }
1113 
1114     /**
1115      * This method checks to see if the plugin is enabled based on the state manager and the plugin.
1116      *
1117      * @param key The plugin key
1118      * @return True if the plugin is enabled
1119      */
1120     public boolean isPluginEnabled(final String key)
1121     {
1122         final Plugin plugin = plugins.get(key);
1123 
1124         return (plugin != null) && getState().isEnabled(plugin) && (plugin.getPluginState() == PluginState.ENABLED);
1125     }
1126 
1127     public InputStream getDynamicResourceAsStream(final String name)
1128     {
1129         return getClassLoader().getResourceAsStream(name);
1130     }
1131 
1132     public Class<?> getDynamicPluginClass(final String className) throws ClassNotFoundException
1133     {
1134         return getClassLoader().loadClass(className);
1135     }
1136 
1137     public PluginsClassLoader getClassLoader()
1138     {
1139         return classLoader;
1140     }
1141 
1142     public InputStream getPluginResourceAsStream(final String pluginKey, final String resourcePath)
1143     {
1144         final Plugin plugin = getEnabledPlugin(pluginKey);
1145         if (plugin == null)
1146         {
1147             log.error("Attempted to retreive resource " + resourcePath + " for non-existent or inactive plugin " + pluginKey);
1148             return null;
1149         }
1150 
1151         return plugin.getResourceAsStream(resourcePath);
1152     }
1153 
1154     /**
1155      * Disables and replaces a plugin currently loaded with an UnloadablePlugin.
1156      *
1157      * @param plugin     the plugin to be replaced
1158      * @param descriptor the descriptor which caused the problem
1159      * @param throwable  the problem caught when enabling the descriptor
1160      * @return the UnloadablePlugin which replaced the broken plugin
1161      */
1162     private UnloadablePlugin replacePluginWithUnloadablePlugin(final Plugin plugin, final ModuleDescriptor<?> descriptor, final Throwable throwable)
1163     {
1164         final UnloadableModuleDescriptor unloadableDescriptor = UnloadableModuleDescriptorFactory.createUnloadableModuleDescriptor(plugin,
1165             descriptor, throwable);
1166         final UnloadablePlugin unloadablePlugin = UnloadablePluginFactory.createUnloadablePlugin(plugin, unloadableDescriptor);
1167 
1168         unloadablePlugin.setUninstallable(plugin.isUninstallable());
1169         unloadablePlugin.setDeletable(plugin.isDeleteable());
1170         plugins.put(plugin.getKey(), unloadablePlugin);
1171 
1172         // Disable it
1173         disablePluginState(plugin, getStore());
1174         return unloadablePlugin;
1175     }
1176 
1177     public boolean isSystemPlugin(final String key)
1178     {
1179         final Plugin plugin = getPlugin(key);
1180         return (plugin != null) && plugin.isSystemPlugin();
1181     }
1182 
1183     public PluginRestartState getPluginRestartState(final String key)
1184     {
1185         return getState().getPluginRestartState(key);
1186     }
1187 
1188     /**
1189      * @deprecated Since 2.0.0.beta2
1190      */
1191     @Deprecated
1192     public void setDescriptorParserFactory(final DescriptorParserFactory descriptorParserFactory)
1193     {}
1194 
1195     private static class PluginFinishedEnablingCondition implements WaitUntil.WaitCondition
1196     {
1197         private final Plugin plugin;
1198 
1199         public PluginFinishedEnablingCondition(final Plugin plugin)
1200         {
1201             this.plugin = plugin;
1202         }
1203 
1204         public boolean isFinished()
1205         {
1206             return plugin.getPluginState() != PluginState.ENABLING;
1207         }
1208 
1209         public String getWaitMessage()
1210         {
1211             return "Waiting until plugin " + plugin + " is enabled";
1212         }
1213     }
1214 }