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