View Javadoc

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