View Javadoc

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