View Javadoc

1   package com.atlassian.plugin.manager;
2   
3   import static com.atlassian.plugin.util.Assertions.notNull;
4   import static com.atlassian.plugin.util.collect.CollectionUtil.toList;
5   import static com.google.common.collect.Iterables.concat;
6   import static com.google.common.collect.Iterables.filter;
7   import static com.google.common.collect.Iterables.transform;
8   import static com.google.common.collect.Maps.filterKeys;
9   
10  import com.atlassian.plugin.*;
11  import com.atlassian.plugin.classloader.PluginsClassLoader;
12  import com.atlassian.plugin.descriptors.CannotDisable;
13  import com.atlassian.plugin.descriptors.UnloadableModuleDescriptor;
14  import com.atlassian.plugin.descriptors.UnloadableModuleDescriptorFactory;
15  import com.atlassian.plugin.event.NotificationException;
16  import com.atlassian.plugin.event.PluginEventListener;
17  import com.atlassian.plugin.event.PluginEventManager;
18  import com.atlassian.plugin.event.events.PluginContainerUnavailableEvent;
19  import com.atlassian.plugin.event.events.PluginDisabledEvent;
20  import com.atlassian.plugin.event.events.PluginEnabledEvent;
21  import com.atlassian.plugin.event.events.PluginFrameworkShutdownEvent;
22  import com.atlassian.plugin.event.events.PluginFrameworkStartedEvent;
23  import com.atlassian.plugin.event.events.PluginFrameworkStartingEvent;
24  import com.atlassian.plugin.event.events.PluginFrameworkWarmRestartedEvent;
25  import com.atlassian.plugin.event.events.PluginFrameworkWarmRestartingEvent;
26  import com.atlassian.plugin.event.events.PluginModuleAvailableEvent;
27  import com.atlassian.plugin.event.events.PluginModuleDisabledEvent;
28  import com.atlassian.plugin.event.events.PluginModuleEnabledEvent;
29  import com.atlassian.plugin.event.events.PluginModuleUnavailableEvent;
30  import com.atlassian.plugin.event.events.PluginRefreshedEvent;
31  import com.atlassian.plugin.event.events.PluginUninstalledEvent;
32  import com.atlassian.plugin.event.events.PluginUpgradedEvent;
33  import com.atlassian.plugin.impl.UnloadablePlugin;
34  import com.atlassian.plugin.impl.UnloadablePluginFactory;
35  import com.atlassian.plugin.loaders.DynamicPluginLoader;
36  import com.atlassian.plugin.loaders.PluginLoader;
37  import com.atlassian.plugin.manager.PluginPersistentState.Builder;
38  import com.atlassian.plugin.metadata.ClasspathFilePluginMetadata;
39  import com.atlassian.plugin.metadata.RequiredPluginValidator;
40  import com.atlassian.plugin.parsers.DescriptorParserFactory;
41  import com.atlassian.plugin.predicate.EnabledModulePredicate;
42  import com.atlassian.plugin.predicate.EnabledPluginPredicate;
43  import com.atlassian.plugin.predicate.ModuleDescriptorOfClassPredicate;
44  import com.atlassian.plugin.predicate.ModuleDescriptorOfTypePredicate;
45  import com.atlassian.plugin.predicate.ModuleDescriptorPredicate;
46  import com.atlassian.plugin.predicate.ModuleOfClassPredicate;
47  import com.atlassian.plugin.predicate.PluginPredicate;
48  import com.atlassian.plugin.util.PluginUtils;
49  import com.atlassian.util.concurrent.CopyOnWriteMap;
50  
51  import org.apache.commons.lang.time.StopWatch;
52  import org.slf4j.Logger;
53  import org.slf4j.LoggerFactory;
54  
55  import com.google.common.base.Function;
56  import com.google.common.base.Predicate;
57  
58  import java.io.InputStream;
59  import java.util.ArrayList;
60  import java.util.Collection;
61  import java.util.Collections;
62  import java.util.HashMap;
63  import java.util.HashSet;
64  import java.util.LinkedHashMap;
65  import java.util.LinkedList;
66  import java.util.List;
67  import java.util.Map;
68  import java.util.Set;
69  import java.util.TreeSet;
70  
71  /**
72   * This implementation delegates the initiation and classloading of plugins to a
73   * list of {@link com.atlassian.plugin.loaders.PluginLoader}s and records the
74   * state of plugins in a
75   * {@link com.atlassian.plugin.manager.PluginPersistentStateStore}.
76   * <p/>
77   * This class is responsible for enabling and disabling plugins and plugin
78   * modules and reflecting these state changes in the PluginPersistentStateStore.
79   * <p/>
80   * An interesting quirk in the design is that
81   * {@link #installPlugin(com.atlassian.plugin.PluginArtifact)} explicitly stores
82   * the plugin via a {@link com.atlassian.plugin.PluginInstaller}, whereas
83   * {@link #uninstall(Plugin)} relies on the underlying
84   * {@link com.atlassian.plugin.loaders.PluginLoader} to remove the plugin if
85   * necessary.
86   */
87  public class DefaultPluginManager implements PluginController, PluginAccessor, PluginSystemLifecycle
88  {
89      private static final Logger log = LoggerFactory.getLogger(DefaultPluginManager.class);
90  
91      private final List<PluginLoader> pluginLoaders;
92      private final PluginPersistentStateStore store;
93      private final ModuleDescriptorFactory moduleDescriptorFactory;
94      private final PluginEventManager pluginEventManager;
95  
96      private final Map<String, Plugin> plugins = CopyOnWriteMap.<String, Plugin> builder().stableViews().newHashMap();
97      private final PluginsClassLoader classLoader;
98      private final PluginEnabler pluginEnabler = new PluginEnabler(this, this);
99      private final StateTracker tracker = new StateTracker();
100 
101     private final boolean verifyRequiredPlugins;
102 
103     /**
104      * Installer used for storing plugins. Used by
105      * {@link #installPlugin(PluginArtifact)}.
106      */
107     private RevertablePluginInstaller pluginInstaller = new NoOpRevertablePluginInstaller(new UnsupportedPluginInstaller());
108 
109     /**
110      * Stores {@link Plugin}s as a key and {@link PluginLoader} as a value.
111      */
112     private final Map<Plugin, PluginLoader> pluginToPluginLoader = new HashMap<Plugin, PluginLoader>();
113 
114     public DefaultPluginManager(final PluginPersistentStateStore store, final List<PluginLoader> pluginLoaders, final ModuleDescriptorFactory moduleDescriptorFactory, final PluginEventManager pluginEventManager)
115     {
116         this(store, pluginLoaders, moduleDescriptorFactory, pluginEventManager, false);
117     }
118 
119     public DefaultPluginManager(final PluginPersistentStateStore store, final List<PluginLoader> pluginLoaders, final ModuleDescriptorFactory moduleDescriptorFactory, final PluginEventManager pluginEventManager, boolean verifyRequiredPlugins)
120     {
121         this.pluginLoaders = notNull("Plugin Loaders list must not be null.", pluginLoaders);
122         this.store = notNull("PluginPersistentStateStore must not be null.", store);
123         this.moduleDescriptorFactory = notNull("ModuleDescriptorFactory must not be null.", moduleDescriptorFactory);
124         this.pluginEventManager = notNull("PluginEventManager must not be null.", pluginEventManager);
125 
126         this.pluginEventManager.register(this);
127         this.verifyRequiredPlugins = verifyRequiredPlugins;
128         classLoader = new PluginsClassLoader(null, this, pluginEventManager);
129     }
130 
131     public void init() throws PluginParseException, NotificationException
132     {
133         tracker.setState(StateTracker.State.STARTING);
134         final StopWatch stopWatch = new StopWatch();
135         stopWatch.start();
136         log.info("Initialising the plugin system");
137         pluginEventManager.broadcast(new PluginFrameworkStartingEvent(this, this));
138         pluginInstaller.clearBackups();
139         for (final PluginLoader loader : pluginLoaders)
140         {
141             if (loader == null)
142             {
143                 continue;
144             }
145 
146             final Iterable<Plugin> possiblePluginsToLoad = loader.loadAllPlugins(moduleDescriptorFactory);
147             final Collection<Plugin> pluginsToLoad = new ArrayList<Plugin>();
148             for (final Plugin plugin : possiblePluginsToLoad)
149             {
150                 if (getState().getPluginRestartState(plugin.getKey()) == PluginRestartState.REMOVE)
151                 {
152                     if (log.isInfoEnabled())
153                     {
154                         log.info("Plugin " + plugin.getKey() + " was marked to be removed on restart.  Removing now.");
155                     }
156                     loader.removePlugin(plugin);
157 
158                     // PLUG-13: Plugins should not save state across uninstalls.
159                     removeStateFromStore(getStore(), plugin);
160                 }
161                 else
162                 {
163                     pluginsToLoad.add(plugin);
164                 }
165             }
166             addPlugins(loader, pluginsToLoad);
167         }
168 
169         getStore().save(getBuilder().clearPluginRestartState().toState());
170 
171         stopWatch.stop();
172         log.info("Plugin system started in " + stopWatch);
173         tracker.setState(StateTracker.State.STARTED);
174         if (verifyRequiredPlugins)
175         {
176             validateRequiredPlugins();
177         }
178         pluginEventManager.broadcast(new PluginFrameworkStartedEvent(this, this));
179     }
180 
181     private void validateRequiredPlugins() throws PluginException
182     {
183         RequiredPluginValidator validator = new RequiredPluginValidator(this, new ClasspathFilePluginMetadata());
184         Collection<String> errors = validator.validate();
185         if (errors.size() > 0)
186         {
187             log.error("Unable to validate required plugins or modules - plugin system shutting down");
188             log.error("Failures:");
189             for (String error : errors)
190             {
191                 log.error("\t{}", error);
192             }
193             shutdown();
194             throw new PluginException("Unable to validate required plugins or modules");
195         }
196     }
197 
198     /**
199      * Fires the shutdown event
200      *
201      * @since 2.0.0
202      * @throws IllegalStateException if already shutdown or already in the
203      *             process of shutting down.
204      */
205     public void shutdown()
206     {
207         tracker.setState(StateTracker.State.SHUTTING_DOWN);
208         log.info("Shutting down the plugin system");
209         try
210         {
211             pluginEventManager.broadcast(new PluginFrameworkShutdownEvent(this, this));
212         }
213         catch (final NotificationException ex)
214         {
215             log.error("At least one error occured while broadcasting the PluginFrameworkShutdownEvent. We will continue to shutdown the Plugin Manager anyway.");
216         }
217         plugins.clear();
218         pluginEventManager.unregister(this);
219         tracker.setState(StateTracker.State.SHUTDOWN);
220     }
221 
222     public final void warmRestart()
223     {
224         tracker.setState(StateTracker.State.WARM_RESTARTING);
225         log.info("Initiating a warm restart of the plugin system");
226         pluginEventManager.broadcast(new PluginFrameworkWarmRestartingEvent(this, this));
227 
228         // Make sure we reload plugins in order
229         final List<Plugin> restartedPlugins = new ArrayList<Plugin>();
230         final List<PluginLoader> loaders = new ArrayList<PluginLoader>(pluginLoaders);
231         Collections.reverse(loaders);
232         for (final PluginLoader loader : pluginLoaders)
233         {
234             for (final Map.Entry<Plugin, PluginLoader> entry : pluginToPluginLoader.entrySet())
235             {
236                 if (entry.getValue() == loader)
237                 {
238                     final Plugin plugin = entry.getKey();
239                     if (isPluginEnabled(plugin.getKey()))
240                     {
241                         disablePluginModules(plugin);
242                         restartedPlugins.add(plugin);
243                     }
244                 }
245             }
246         }
247 
248         // then enable them in reverse order
249         Collections.reverse(restartedPlugins);
250         for (final Plugin plugin : restartedPlugins)
251         {
252             enableConfiguredPluginModules(plugin);
253         }
254 
255         pluginEventManager.broadcast(new PluginFrameworkWarmRestartedEvent(this, this));
256         tracker.setState(StateTracker.State.STARTED);
257     }
258 
259     @PluginEventListener
260     public void onPluginModuleAvailable(final PluginModuleAvailableEvent event)
261     {
262         enableConfiguredPluginModule(event.getModule().getPlugin(), event.getModule(), new HashSet<ModuleDescriptor<?>>());
263     }
264 
265     @PluginEventListener
266     public void onPluginModuleUnavailable(final PluginModuleUnavailableEvent event)
267     {
268         notifyModuleDisabled(event.getModule());
269     }
270 
271     @PluginEventListener
272     public void onPluginContainerUnavailable(final PluginContainerUnavailableEvent event)
273     {
274         disablePluginWithoutPersisting(event.getPluginKey());
275     }
276 
277     @PluginEventListener
278     public void onPluginRefresh(final PluginRefreshedEvent event)
279     {
280         final Plugin plugin = event.getPlugin();
281 
282         disablePluginModules(plugin);
283 
284         // enable the plugin, shamefully copied from notifyPluginEnabled()
285         if (enableConfiguredPluginModules(plugin))
286         {
287             pluginEventManager.broadcast(new PluginEnabledEvent(plugin));
288         }
289     }
290 
291     /**
292      * Set the plugin installation strategy for this manager
293      *
294      * @param pluginInstaller the plugin installation strategy to use
295      * @see PluginInstaller
296      */
297     public void setPluginInstaller(final PluginInstaller pluginInstaller)
298     {
299         if (pluginInstaller instanceof RevertablePluginInstaller)
300         {
301             this.pluginInstaller = (RevertablePluginInstaller) pluginInstaller;
302         }
303         else
304         {
305             this.pluginInstaller = new NoOpRevertablePluginInstaller(pluginInstaller);
306         }
307     }
308 
309     protected final PluginPersistentStateStore getStore()
310     {
311         return store;
312     }
313 
314     public String installPlugin(final PluginArtifact pluginArtifact) throws PluginParseException
315     {
316         final Set<String> keys = installPlugins(pluginArtifact);
317         if ((keys != null) && (keys.size() == 1))
318         {
319             return keys.iterator().next();
320         }
321         else
322         {
323             // should never happen
324             throw new PluginParseException("Could not install plugin");
325         }
326     }
327 
328     public Set<String> installPlugins(final PluginArtifact... pluginArtifacts) throws PluginParseException
329     {
330         final Map<String, PluginArtifact> validatedArtifacts = new LinkedHashMap<String, PluginArtifact>();
331         try
332         {
333             for (final PluginArtifact pluginArtifact : pluginArtifacts)
334             {
335                 validatedArtifacts.put(validatePlugin(pluginArtifact), pluginArtifact);
336             }
337         }
338         catch (final PluginParseException ex)
339         {
340             throw new PluginParseException("All plugins could not be validated", ex);
341         }
342 
343         for (final Map.Entry<String, PluginArtifact> entry : validatedArtifacts.entrySet())
344         {
345             pluginInstaller.installPlugin(entry.getKey(), entry.getValue());
346         }
347 
348         scanForNewPlugins();
349         return validatedArtifacts.keySet();
350     }
351 
352     /**
353      * Validate a plugin jar. Looks through all plugin loaders for ones that can
354      * load the plugin and extract the plugin key as proof.
355      *
356      * @param pluginArtifact the jar file representing the plugin
357      * @return The plugin key
358      * @throws PluginParseException if the plugin cannot be parsed
359      * @throws NullPointerException if <code>pluginJar</code> is null.
360      */
361     String validatePlugin(final PluginArtifact pluginArtifact) throws PluginParseException
362     {
363         boolean foundADynamicPluginLoader = false;
364         for (final PluginLoader loader : pluginLoaders)
365         {
366             if (loader instanceof DynamicPluginLoader)
367             {
368                 foundADynamicPluginLoader = true;
369                 final String key = ((DynamicPluginLoader) loader).canLoad(pluginArtifact);
370                 if (key != null)
371                 {
372                     return key;
373                 }
374             }
375         }
376 
377         if (!foundADynamicPluginLoader)
378         {
379             throw new IllegalStateException("Should be at least one DynamicPluginLoader in the plugin loader list");
380         }
381         throw new PluginParseException("Jar " + pluginArtifact.getName() + " is not a valid plugin");
382     }
383 
384     public int scanForNewPlugins() throws PluginParseException
385     {
386         int numberFound = 0;
387 
388         for (final PluginLoader loader : pluginLoaders)
389         {
390             if (loader != null)
391             {
392                 if (loader.supportsAddition())
393                 {
394                     final List<Plugin> pluginsToAdd = new ArrayList<Plugin>();
395                     for (Plugin plugin : loader.addFoundPlugins(moduleDescriptorFactory))
396                     {
397                         final Plugin oldPlugin = plugins.get(plugin.getKey());
398                         // Only actually install the plugin if its module
399                         // descriptors support it. Otherwise, mark it as
400                         // unloadable.
401                         if (!(plugin instanceof UnloadablePlugin))
402                         {
403                             if (PluginUtils.doesPluginRequireRestart(plugin))
404                             {
405                                 if (oldPlugin == null)
406                                 {
407                                     markPluginInstallThatRequiresRestart(plugin);
408 
409                                     final UnloadablePlugin unloadablePlugin = UnloadablePluginFactory.createUnloadablePlugin(plugin);
410                                     unloadablePlugin.setErrorText("Plugin requires a restart of the application due " + "to the following modules: " + PluginUtils.getPluginModulesThatRequireRestart(plugin));
411                                     plugin = unloadablePlugin;
412                                 }
413                                 else
414                                 {
415                                     // If a plugin has been installed but is waiting for restart then we do not want to
416                                     // put the plugin into the update state, we want to keep it in the install state.
417                                     if (!PluginRestartState.INSTALL.equals(getPluginRestartState(plugin.getKey())))
418                                     {
419                                         markPluginUpgradeThatRequiresRestart(plugin);
420                                     }
421                                     continue;
422                                 }
423                             }
424                             // If the new plugin does not require restart we need to check what the restart state of
425                             // the old plugin was and act accordingly
426                             else if (oldPlugin != null && PluginUtils.doesPluginRequireRestart(oldPlugin))
427                             {
428                                 // If you have installed the plugin that requires restart and before restart you have
429                                 // reinstalled a version of that plugin that does not require restart then you should
430                                 // just go ahead and install that plugin. This means reverting the previous install
431                                 // and letting the plugin fall into the plugins to add list
432                                 if (PluginRestartState.INSTALL.equals(getPluginRestartState(oldPlugin.getKey())))
433                                 {
434                                     revertRestartRequiredChange(oldPlugin.getKey());
435                                 }
436                                 else
437                                 {
438                                     markPluginUpgradeThatRequiresRestart(plugin);
439                                     continue;
440                                 }
441                             }
442                             pluginsToAdd.add(plugin);
443                         }
444                     }
445                     addPlugins(loader, pluginsToAdd);
446                     numberFound = pluginsToAdd.size();
447                 }
448             }
449         }
450         return numberFound;
451     }
452 
453     private void markPluginInstallThatRequiresRestart(final Plugin plugin)
454     {
455         if (log.isInfoEnabled())
456         {
457             log.info("Installed plugin '" + plugin.getKey() + "' requires a restart due to the following modules: " + PluginUtils.getPluginModulesThatRequireRestart(plugin));
458         }
459         updateRequiresRestartState(plugin.getKey(), PluginRestartState.INSTALL);
460     }
461 
462     private void markPluginUpgradeThatRequiresRestart(final Plugin plugin)
463     {
464         if (log.isInfoEnabled())
465         {
466             log.info("Upgraded plugin '" + plugin.getKey() + "' requires a restart due to the following modules: " + PluginUtils.getPluginModulesThatRequireRestart(plugin));
467         }
468         updateRequiresRestartState(plugin.getKey(), PluginRestartState.UPGRADE);
469     }
470 
471     private void markPluginUninstallThatRequiresRestart(final Plugin plugin)
472     {
473         if (log.isInfoEnabled())
474         {
475             log.info("Uninstalled plugin '" + plugin.getKey() + "' requires a restart due to the following modules: " + PluginUtils.getPluginModulesThatRequireRestart(plugin));
476         }
477         updateRequiresRestartState(plugin.getKey(), PluginRestartState.REMOVE);
478     }
479 
480     private void updateRequiresRestartState(final String pluginKey, final PluginRestartState pluginRestartState)
481     {
482         getStore().save(getBuilder().setPluginRestartState(pluginKey, pluginRestartState).toState());
483         onUpdateRequiresRestartState(pluginKey, pluginRestartState);
484     }
485 
486     protected void onUpdateRequiresRestartState(final String pluginKey, final PluginRestartState pluginRestartState)
487     {
488         // nothing to do in this implementation
489     }
490 
491     /**
492      * Uninstalls the given plugin, emitting disabled and uninstalled events as it does so.
493      * @param plugin the plugin to uninstall.
494      * @throws PluginException If the plugin or loader doesn't support
495      *             uninstallation
496      */
497     public void uninstall(final Plugin plugin) throws PluginException
498     {
499         if (PluginUtils.doesPluginRequireRestart(plugin))
500         {
501             ensurePluginAndLoaderSupportsUninstall(plugin);
502             markPluginUninstallThatRequiresRestart(plugin);
503         }
504         else
505         {
506             // Explicitly disable any plugins that require this plugin
507             disableDependentPlugins(plugin);
508 
509             uninstallNoEvent(plugin);
510 
511             pluginEventManager.broadcast(new PluginUninstalledEvent(plugin));
512         }
513     }
514 
515     /**
516      * Preforms an uninstallation without broadcasting the uninstallation event.
517      *
518      * @param plugin The plugin to uninstall
519      * @since 2.5.0
520      */
521     protected void uninstallNoEvent(final Plugin plugin)
522     {
523         unloadPlugin(plugin);
524 
525         // PLUG-13: Plugins should not save state across uninstalls.
526         removeStateFromStore(getStore(), plugin);
527     }
528 
529     /**
530      * @param pluginKey The plugin key to revert
531      * @throws PluginException If the revert cannot be completed
532      */
533     public void revertRestartRequiredChange(final String pluginKey) throws PluginException
534     {
535         notNull("pluginKey", pluginKey);
536         final PluginRestartState restartState = getState().getPluginRestartState(pluginKey);
537         if (restartState == PluginRestartState.UPGRADE)
538         {
539             pluginInstaller.revertInstalledPlugin(pluginKey);
540         }
541         else if (restartState == PluginRestartState.INSTALL)
542         {
543             pluginInstaller.revertInstalledPlugin(pluginKey);
544             plugins.remove(pluginKey);
545         }
546         updateRequiresRestartState(pluginKey, PluginRestartState.NONE);
547     }
548 
549     protected void removeStateFromStore(final PluginPersistentStateStore stateStore, final Plugin plugin)
550     {
551         final PluginPersistentState.Builder builder = PluginPersistentState.Builder.create(stateStore.load()).removeState(plugin.getKey());
552         for (final ModuleDescriptor<?> moduleDescriptor : plugin.getModuleDescriptors())
553         {
554             builder.removeState(moduleDescriptor.getCompleteKey());
555         }
556         stateStore.save(builder.toState());
557     }
558 
559     /**
560      * Unload a plugin. Called when plugins are added locally, or remotely in a
561      * clustered application.
562      *
563      * @param plugin the plugin to remove
564      * @throws PluginException if the plugin cannot be uninstalled
565      */
566     protected void unloadPlugin(final Plugin plugin) throws PluginException
567     {
568         final PluginLoader loader = ensurePluginAndLoaderSupportsUninstall(plugin);
569 
570         if (isPluginEnabled(plugin.getKey()))
571         {
572             notifyPluginDisabled(plugin);
573         }
574 
575         notifyUninstallPlugin(plugin);
576         if (loader != null)
577         {
578             removePluginFromLoader(plugin);
579         }
580 
581         plugins.remove(plugin.getKey());
582     }
583 
584     private PluginLoader ensurePluginAndLoaderSupportsUninstall(final Plugin plugin)
585     {
586         if (!plugin.isUninstallable())
587         {
588             throw new PluginException("Plugin is not uninstallable: " + plugin.getKey());
589         }
590 
591         final PluginLoader loader = pluginToPluginLoader.get(plugin);
592 
593         if ((loader != null) && !loader.supportsRemoval())
594         {
595             throw new PluginException("Not uninstalling plugin - loader doesn't allow removal. Plugin: " + plugin.getKey());
596         }
597         return loader;
598     }
599 
600     private void removePluginFromLoader(final Plugin plugin) throws PluginException
601     {
602         if (plugin.isDeleteable())
603         {
604             final PluginLoader pluginLoader = pluginToPluginLoader.get(plugin);
605             pluginLoader.removePlugin(plugin);
606         }
607 
608         pluginToPluginLoader.remove(plugin);
609     }
610 
611     protected void notifyUninstallPlugin(final Plugin plugin)
612     {
613         classLoader.notifyUninstallPlugin(plugin);
614 
615         for (final ModuleDescriptor<?> descriptor : plugin.getModuleDescriptors())
616         {
617             descriptor.destroy(plugin);
618         }
619     }
620 
621     protected PluginPersistentState getState()
622     {
623         return getStore().load();
624     }
625 
626     /**
627      * @deprecated Since 2.0.2, use {@link
628      *             #addPlugins(PluginLoader,Collection<Plugin>...)} instead
629      */
630     @Deprecated
631     protected void addPlugin(final PluginLoader loader, final Plugin plugin) throws PluginParseException
632     {
633         addPlugins(loader, Collections.singletonList(plugin));
634     }
635 
636     /**
637      * Update the local plugin state and enable state aware modules.
638      * <p>
639      * If there is an existing plugin with the same key, the version strings of
640      * the existing plugin and the plugin provided to this method will be parsed
641      * and compared. If the installed version is newer than the provided
642      * version, it will not be changed. If the specified plugin's version is the
643      * same or newer, the existing plugin state will be saved and the plugin
644      * will be unloaded before the provided plugin is installed. If the existing
645      * plugin cannot be unloaded a {@link PluginException} will be thrown.
646      *
647      * @param loader the loader used to load this plugin
648      * @param pluginsToInstall the plugins to add
649      * @throws PluginParseException if the plugin cannot be parsed
650      * @since 2.0.2
651      */
652     protected void addPlugins(final PluginLoader loader, final Collection<Plugin> pluginsToInstall) throws PluginParseException
653     {
654         final List<Plugin> pluginsToEnable = new ArrayList<Plugin>();
655 
656         // Install plugins, looking for upgrades and duplicates
657         for (final Plugin plugin : new TreeSet<Plugin>(pluginsToInstall))
658         {
659             boolean pluginUpgraded = false;
660             // testing to make sure plugin keys are unique
661             final Plugin existingPlugin = plugins.get(plugin.getKey());
662             if (existingPlugin != null)
663             {
664                 if (plugin.compareTo(existingPlugin) >= 0)
665                 {
666                     try
667                     {
668                         // Disable all plugins now before the upgrade, as an upgrade of a bundled plugin
669                         // may trigger a package refresh, bringing down dependent plugins
670                         pluginsToEnable.addAll(disableDependentPlugins(plugin));
671                         updatePlugin(existingPlugin, plugin);
672                         pluginsToEnable.remove(existingPlugin);
673                         pluginUpgraded = true;
674                     }
675                     catch (final PluginException e)
676                     {
677                         throw new PluginParseException(
678                             "Duplicate plugin found (installed version is the same or older) and could not be unloaded: '" + plugin.getKey() + "'", e);
679                     }
680                 }
681                 else
682                 {
683                     // If we find an older plugin, don't error, just ignore it.
684                     // PLUG-12.
685                     if (log.isDebugEnabled())
686                     {
687                         log.debug("Duplicate plugin found (installed version is newer): '" + plugin.getKey() + "'");
688                     }
689                     // and don't install the older plugin
690                     continue;
691                 }
692             }
693 
694             plugin.install();
695             final boolean isPluginEnabled = getState().isEnabled(plugin);
696             if (isPluginEnabled)
697             {
698                 pluginsToEnable.add(plugin);
699             }
700             if (plugin.isSystemPlugin() && !isPluginEnabled)
701             {
702                 log.warn("System plugin is disabled: " + plugin.getKey());
703             }
704             if (pluginUpgraded)
705             {
706                 pluginEventManager.broadcast(new PluginUpgradedEvent(plugin));
707             }
708             plugins.put(plugin.getKey(), plugin);
709             pluginToPluginLoader.put(plugin, loader);
710         }
711 
712         // enable all plugins, waiting a time period for them to enable
713         pluginEnabler.enable(pluginsToEnable);
714 
715         // handle the plugins that were able to be successfully enabled
716         for (final Plugin plugin : pluginsToEnable)
717         {
718             if (plugin.getPluginState() == PluginState.ENABLED)
719             {
720                 // This method enables the plugin modules
721                 if (enableConfiguredPluginModules(plugin))
722                 {
723                     pluginEventManager.broadcast(new PluginEnabledEvent(plugin));
724                 }
725             }
726         }
727     }
728 
729     /**
730      * Disables all dependent plugins to prevent a dependent plugin trying to access, indirectly,
731      * the felix global lock, which is held by the PackageAdmin while refreshing.
732      * see http://studio.atlassian.com/browse/PLUG-582
733      * @param plugin The plugin to disable
734      * @return A set of plugins that were disabled
735      */
736     private Set<Plugin> disableDependentPlugins(final Plugin plugin)
737     {
738         final Set<Plugin> dependentPlugins = new HashSet<Plugin>();
739         final Set<String> dependentPluginKeys = new HashSet<String>();
740 
741         for (final Plugin depPlugin : getEnabledPlugins())
742         {
743             if ((plugin != depPlugin) && depPlugin.getRequiredPlugins().contains(plugin.getKey()))
744             {
745                 dependentPlugins.add(depPlugin);
746                 dependentPluginKeys.add(depPlugin.getKey());
747             }
748         }
749         if (log.isInfoEnabled())
750         {
751             log.info("Found dependent enabled plugins for uninstalled plugin '" + plugin.getKey() + "': " + dependentPluginKeys + ".  Disabling...");
752         }
753         for (final Plugin depPlugin : dependentPlugins)
754         {
755             disablePluginWithoutPersisting(depPlugin.getKey());
756         }
757         return dependentPlugins;
758     }
759 
760     /**
761      * Replace an already loaded plugin with another version. Relevant stored
762      * configuration for the plugin will be preserved.
763      *
764      * @param oldPlugin Plugin to replace
765      * @param newPlugin New plugin to install
766      * @throws PluginException if the plugin cannot be updated
767      */
768     protected void updatePlugin(final Plugin oldPlugin, final Plugin newPlugin) throws PluginException
769     {
770         if (!oldPlugin.getKey().equals(newPlugin.getKey()))
771         {
772             throw new IllegalArgumentException("New plugin must have the same key as the old plugin");
773         }
774 
775         if (log.isInfoEnabled())
776         {
777             log.info("Updating plugin '" + oldPlugin + "' to '" + newPlugin + "'");
778         }
779 
780         // Preserve the old plugin configuration - uninstall changes it (as
781         // disable is called on all modules) and then
782         // removes it
783         final Map<String, Boolean> oldPluginState = new HashMap<String, Boolean>(getState().getPluginStateMap(oldPlugin));
784 
785         if (log.isDebugEnabled())
786         {
787             log.debug("Uninstalling old plugin: " + oldPlugin);
788         }
789         uninstallNoEvent(oldPlugin);
790         if (log.isDebugEnabled())
791         {
792             log.debug("Plugin uninstalled '" + oldPlugin + "', preserving old state");
793         }
794 
795         // Build a set of module keys from the new plugin version
796         final Set<String> newModuleKeys = new HashSet<String>();
797         newModuleKeys.add(newPlugin.getKey());
798         for (final ModuleDescriptor<?> moduleDescriptor : newPlugin.getModuleDescriptors())
799         {
800             newModuleKeys.add(moduleDescriptor.getCompleteKey());
801         }
802 
803         // for removing any keys from the old plugin state that do not exist in
804         // the
805         // new version
806         final Predicate<String> filter = new Predicate<String>()
807         {
808             public boolean apply(final String o)
809             {
810                 return newModuleKeys.contains(o);
811             }
812         };
813 
814         getStore().save(getBuilder().addState(filterKeys(oldPluginState, filter)).toState());
815     }
816 
817     public Collection<Plugin> getPlugins()
818     {
819         return plugins.values();
820     }
821 
822     /**
823      * @see PluginAccessor#getPlugins(com.atlassian.plugin.predicate.PluginPredicate)
824      * @since 0.17
825      */
826     public Collection<Plugin> getPlugins(final PluginPredicate pluginPredicate)
827     {
828         return toList(filter(getPlugins(), new Predicate<Plugin>()
829         {
830             public boolean apply(final Plugin plugin)
831             {
832                 return pluginPredicate.matches(plugin);
833             }
834         }));
835     }
836 
837     /**
838      * @see PluginAccessor#getEnabledPlugins()
839      */
840     public Collection<Plugin> getEnabledPlugins()
841     {
842         return getPlugins(new EnabledPluginPredicate(this));
843     }
844 
845     /**
846      * @see PluginAccessor#getModules(com.atlassian.plugin.predicate.ModuleDescriptorPredicate)
847      * @since 0.17
848      */
849     public <M> Collection<M> getModules(final ModuleDescriptorPredicate<M> moduleDescriptorPredicate)
850     {
851         return toList(getModules(getModuleDescriptors(getPlugins(), moduleDescriptorPredicate)));
852     }
853 
854     /**
855      * @see PluginAccessor#getModuleDescriptors(com.atlassian.plugin.predicate.ModuleDescriptorPredicate)
856      * @since 0.17
857      */
858     public <M> Collection<ModuleDescriptor<M>> getModuleDescriptors(final ModuleDescriptorPredicate<M> moduleDescriptorPredicate)
859     {
860         return toList(getModuleDescriptors(getPlugins(), moduleDescriptorPredicate));
861     }
862 
863     /**
864      * Get the all the module descriptors from the given collection of plugins,
865      * filtered by the predicate.
866      * <p>
867      * Be careful, your predicate must filter ModuleDescriptors that are not M,
868      * this method does not guarantee that the descriptors are of the correct
869      * type by itself.
870      *
871      * @param plugins a collection of {@link Plugin}s
872      * @return a collection of {@link ModuleDescriptor descriptors}
873      */
874     private <M> Iterable<ModuleDescriptor<M>> getModuleDescriptors(final Collection<Plugin> plugins, final ModuleDescriptorPredicate<M> predicate)
875     {
876         // hack way to get typed descriptors from plugin and
877         // keep generics happy
878         final Function<ModuleDescriptor<?>, ModuleDescriptor<M>> coercer = new Function<ModuleDescriptor<?>, ModuleDescriptor<M>>()
879         {
880             public ModuleDescriptor<M> apply(final ModuleDescriptor<?> input)
881             {
882                 @SuppressWarnings("unchecked")
883                 final ModuleDescriptor<M> result = (ModuleDescriptor<M>) input;
884                 return result;
885             }
886         };
887 
888         // google predicate adapter
889         final Predicate<ModuleDescriptor<M>> adapter = new Predicate<ModuleDescriptor<M>>()
890         {
891             public boolean apply(final ModuleDescriptor<M> input)
892             {
893                 return predicate.matches(input);
894             }
895         };
896 
897         // get the filtered module descriptors from a plugin
898         final Function<Plugin, Iterable<ModuleDescriptor<M>>> descriptorExtractor = new Function<Plugin, Iterable<ModuleDescriptor<M>>>()
899         {
900             public Iterable<ModuleDescriptor<M>> apply(final Plugin plugin)
901             {
902                 return filter(transform(plugin.getModuleDescriptors(), coercer), adapter);
903             }
904         };
905 
906         // concatenate all the descriptor iterables into one
907         return concat(transform(plugins, descriptorExtractor));
908     }
909 
910     /**
911      * Get the modules of all the given descriptor. If any of the getModule()
912      * calls fails, the error is recorded in the logs and the plugin is
913      * disabled.
914      *
915      * @param moduleDescriptors the collection of module descriptors to get the
916      *            modules from.
917      * @return a {@link Collection} modules that can be any type of object. This
918      *         collection will not contain any null value.
919      */
920     private <M> Iterable<M> getModules(final Iterable<ModuleDescriptor<M>> moduleDescriptors)
921     {
922         return transform(moduleDescriptors, new Function<ModuleDescriptor<M>, M>()
923         {
924             private final Set<String> disabled = new HashSet<String>();
925             public M apply(final ModuleDescriptor<M> input)
926             {
927                 try
928                 {
929                     return input.getModule();
930                 }
931                 catch (final RuntimeException ex)
932                 {
933                     final String key = input.getPlugin().getKey();
934                     log.error("Exception when retrieving plugin module " + input.getKey(), ex);
935                     if (disabled.contains(key))
936                         return null;
937 
938                     log.error("Disabling plugin" + key);
939                     disabled.add(key);
940                     disablePlugin(key);
941                     return null;
942                 }
943             }
944         });
945     }
946 
947     public Plugin getPlugin(final String key)
948     {
949         return plugins.get(notNull("The plugin key must be specified", key));
950     }
951 
952     public Plugin getEnabledPlugin(final String pluginKey)
953     {
954         if (!isPluginEnabled(pluginKey))
955         {
956             return null;
957         }
958         return getPlugin(pluginKey);
959     }
960 
961     public ModuleDescriptor<?> getPluginModule(final String completeKey)
962     {
963         return getPluginModule(new ModuleCompleteKey(completeKey));
964     }
965 
966     private ModuleDescriptor<?> getPluginModule(final ModuleCompleteKey key)
967     {
968         final Plugin plugin = getPlugin(key.getPluginKey());
969         if (plugin == null)
970         {
971             return null;
972         }
973         return plugin.getModuleDescriptor(key.getModuleKey());
974     }
975 
976     public ModuleDescriptor<?> getEnabledPluginModule(final String completeKey)
977     {
978         final ModuleCompleteKey key = new ModuleCompleteKey(completeKey);
979 
980         // If it's disabled, return null
981         if (!isPluginModuleEnabled(key))
982         {
983             return null;
984         }
985 
986         return getEnabledPlugin(key.getPluginKey()).getModuleDescriptor(key.getModuleKey());
987     }
988 
989     /**
990      * @see PluginAccessor#getEnabledModulesByClass(Class)
991      */
992     public <M> List<M> getEnabledModulesByClass(final Class<M> moduleClass)
993     {
994         return toList(getModules(getEnabledModuleDescriptorsByModuleClass(moduleClass)));
995     }
996 
997     /**
998      * @see PluginAccessor#getEnabledModulesByClassAndDescriptor(Class[], Class)
999      * @deprecated since 0.17, use
1000      *             {@link #getModules(com.atlassian.plugin.predicate.ModuleDescriptorPredicate)}
1001      *             with an appropriate predicate instead.
1002      */
1003     @Deprecated
1004     public <M> List<M> getEnabledModulesByClassAndDescriptor(final Class<ModuleDescriptor<M>>[] descriptorClasses, final Class<M> moduleClass)
1005     {
1006         final Iterable<ModuleDescriptor<M>> moduleDescriptors = filterDescriptors(getEnabledModuleDescriptorsByModuleClass(moduleClass), new ModuleDescriptorOfClassPredicate<M>(descriptorClasses));
1007 
1008         return toList(getModules(moduleDescriptors));
1009     }
1010 
1011     /**
1012      * @see PluginAccessor#getEnabledModulesByClassAndDescriptor(Class, Class)
1013      * @deprecated since 0.17, use
1014      *             {@link #getModules(com.atlassian.plugin.predicate.ModuleDescriptorPredicate)}
1015      *             with an appropriate predicate instead.
1016      */
1017     @Deprecated
1018     public <M> List<M> getEnabledModulesByClassAndDescriptor(final Class<ModuleDescriptor<M>> descriptorClass, final Class<M> moduleClass)
1019     {
1020         final Iterable<ModuleDescriptor<M>> moduleDescriptors = getEnabledModuleDescriptorsByModuleClass(moduleClass);
1021         return toList(getModules(filterDescriptors(moduleDescriptors, new ModuleDescriptorOfClassPredicate<M>(
1022             descriptorClass))));
1023     }
1024 
1025     /**
1026      * Get all module descriptor that are enabled and for which the module is an
1027      * instance of the given class.
1028      *
1029      * @param moduleClass the class of the module within the module descriptor.
1030      * @return a collection of {@link ModuleDescriptor}s
1031      */
1032     private <M> Collection<ModuleDescriptor<M>> getEnabledModuleDescriptorsByModuleClass(final Class<M> moduleClass)
1033     {
1034         final ModuleOfClassPredicate<M> ofType = new ModuleOfClassPredicate<M>(moduleClass);
1035         final EnabledModulePredicate<M> enabled = new EnabledModulePredicate<M>(this);
1036         return toList(getModuleDescriptors(getEnabledPlugins(), new ModuleDescriptorPredicate<M>()
1037         {
1038             public boolean matches(final ModuleDescriptor<? extends M> moduleDescriptor)
1039             {
1040                 return ofType.matches(moduleDescriptor) && enabled.matches(moduleDescriptor);
1041             }
1042         }));
1043     }
1044 
1045     /**
1046      * This method has been reverted to pre PLUG-40 to fix performance issues
1047      * that were encountered during load testing. This should be reverted to the
1048      * state it was in at 54639 when the fundamental issue leading to this
1049      * slowdown has been corrected (that is, slowness of PluginClassLoader).
1050      *
1051      * @see PluginAccessor#getEnabledModuleDescriptorsByClass(Class)
1052      */
1053     public <D extends ModuleDescriptor<?>> List<D> getEnabledModuleDescriptorsByClass(final Class<D> descriptorClazz)
1054     {
1055         final List<D> result = new LinkedList<D>();
1056         for (final Plugin plugin : plugins.values())
1057         {
1058             // Skip disabled plugins
1059             if (!isPluginEnabled(plugin.getKey()))
1060             {
1061                 if (log.isDebugEnabled())
1062                 {
1063                     log.debug("Plugin [" + plugin.getKey() + "] is disabled.");
1064                 }
1065                 continue;
1066             }
1067 
1068             for (final ModuleDescriptor<?> module : plugin.getModuleDescriptors())
1069             {
1070                 if (descriptorClazz.isInstance(module))
1071                 {
1072                     if (isPluginModuleEnabled(module.getCompleteKey()))
1073                     {
1074                         result.add(descriptorClazz.cast(module));
1075                     }
1076                     else if (log.isDebugEnabled())
1077                     {
1078                         log.debug("Module [" + module.getCompleteKey() + "] is disabled.");
1079                     }
1080                 }
1081             }
1082         }
1083 
1084         return result;
1085     }
1086 
1087     public <D extends ModuleDescriptor<?>> List<D> getEnabledModuleDescriptorsByClass(final Class<D> descriptorClazz, final boolean verbose)
1088     {
1089         return getEnabledModuleDescriptorsByClass(descriptorClazz);
1090     }
1091 
1092     /**
1093      * @see PluginAccessor#getEnabledModuleDescriptorsByType(String)
1094      * @deprecated since 0.17, use
1095      *             {@link #getModuleDescriptors(com.atlassian.plugin.predicate.ModuleDescriptorPredicate)}
1096      *             with an appropriate predicate instead.
1097      */
1098     @Deprecated
1099     public <M> List<ModuleDescriptor<M>> getEnabledModuleDescriptorsByType(final String type) throws PluginParseException, IllegalArgumentException
1100     {
1101         final ModuleDescriptorOfTypePredicate<M> ofType = new ModuleDescriptorOfTypePredicate<M>(moduleDescriptorFactory, type);
1102         final EnabledModulePredicate<M> enabled = new EnabledModulePredicate<M>(this);
1103         return toList(getModuleDescriptors(getEnabledPlugins(), new ModuleDescriptorPredicate<M>()
1104         {
1105             public boolean matches(final ModuleDescriptor<? extends M> moduleDescriptor)
1106             {
1107                 return ofType.matches(moduleDescriptor) && enabled.matches(moduleDescriptor);
1108             }
1109         }));
1110     }
1111 
1112     /**
1113      * Filters out a collection of {@link ModuleDescriptor}s given a predicate.
1114      *
1115      * @param descriptors the collection of {@link ModuleDescriptor}s to filter.
1116      * @param predicate the predicate to use for filtering.
1117      */
1118     private static <M> Iterable<ModuleDescriptor<M>> filterDescriptors(final Iterable<ModuleDescriptor<M>> descriptors, final ModuleDescriptorPredicate<M> predicate)
1119     {
1120         return filter(descriptors, new Predicate<ModuleDescriptor<M>>()
1121         {
1122             public boolean apply(final ModuleDescriptor<M> input)
1123             {
1124                 return predicate.matches(input);
1125             }
1126         });
1127     }
1128 
1129     /**
1130      * Enable a set of plugins by key. This will implicitly and recursively
1131      * enable all dependent plugins.
1132      *
1133      * @param keys The plugin keys. Must not be null.
1134      * @since 2.5.0
1135      */
1136     public void enablePlugins(final String... keys)
1137     {
1138         final Collection<Plugin> pluginsToEnable = new ArrayList<Plugin>(keys.length);
1139 
1140         for (final String key : keys)
1141         {
1142             if (key == null)
1143             {
1144                 throw new IllegalArgumentException("Keys passed to enablePlugins must be non-null");
1145             }
1146 
1147             final Plugin plugin = plugins.get(key);
1148             if (plugin == null)
1149             {
1150                 if (log.isInfoEnabled())
1151                 {
1152                     log.info("No plugin was found for key '" + key + "'. Not enabling.");
1153                 }
1154                 continue;
1155             }
1156 
1157             if (!plugin.getPluginInformation().satisfiesMinJavaVersion())
1158             {
1159                 log.error("Minimum Java version of '" + plugin.getPluginInformation().getMinJavaVersion() + "' was not satisfied for module '" + key + "'. Not enabling.");
1160                 continue;
1161             }
1162             pluginsToEnable.add(plugin);
1163         }
1164         final Collection<Plugin> enabledPlugins = pluginEnabler.enableAllRecursively(pluginsToEnable);
1165 
1166         for (final Plugin plugin : enabledPlugins)
1167         {
1168             enablePluginState(plugin, getStore());
1169             notifyPluginEnabled(plugin);
1170         }
1171     }
1172 
1173     /**
1174      * @deprecated since 2.5.0, use {#link enablePlugins(String... keys)} instead
1175      */
1176     @Deprecated
1177     public void enablePlugin(final String key)
1178     {
1179         enablePlugins(key);
1180     }
1181 
1182     protected void enablePluginState(final Plugin plugin, final PluginPersistentStateStore stateStore)
1183     {
1184         stateStore.save(getBuilder().setEnabled(plugin, true).toState());
1185     }
1186 
1187     /**
1188      * Called on all clustered application nodes, rather than
1189      * {@link #enablePlugin(String)} to just update the local state, state aware
1190      * modules and loaders, but not affect the global plugin state.
1191      *
1192      * @param plugin the plugin being enabled
1193      */
1194     protected void notifyPluginEnabled(final Plugin plugin)
1195     {
1196         plugin.enable();
1197         if (enableConfiguredPluginModules(plugin))
1198         {
1199             pluginEventManager.broadcast(new PluginEnabledEvent(plugin));
1200         }
1201     }
1202 
1203     /**
1204      * For each module in the plugin, call the module descriptor's enabled()
1205      * method if the module is StateAware and enabled.
1206      * <p>
1207      * If any modules fail to enable then the plugin is replaced by an
1208      * UnloadablePlugin, and this method will return {@code false}.
1209      *
1210      * @param plugin the plugin to enable
1211      * @return true if the modules were all enabled correctly, false otherwise.
1212      */
1213     private boolean enableConfiguredPluginModules(final Plugin plugin)
1214     {
1215         boolean success = true;
1216         final Set<ModuleDescriptor<?>> enabledDescriptors = new HashSet<ModuleDescriptor<?>>();
1217         for (final ModuleDescriptor<?> descriptor : plugin.getModuleDescriptors())
1218         {
1219             if (!enableConfiguredPluginModule(plugin, descriptor, enabledDescriptors))
1220             {
1221                 success = false;
1222                 break;
1223             }
1224         }
1225         return success;
1226     }
1227 
1228     private boolean enableConfiguredPluginModule(final Plugin plugin, final ModuleDescriptor<?> descriptor, final Set<ModuleDescriptor<?>> enabledDescriptors)
1229     {
1230         boolean success = true;
1231 
1232         // This can happen if the plugin available event is fired as part of the plugin initialization process
1233         if (pluginEnabler.isPluginBeingEnabled(plugin))
1234         {
1235             log.debug("The plugin is currently being enabled, so we won't bother trying to enable the '" + descriptor.getKey() + " module");
1236             return success;
1237         }
1238 
1239         // We only want to re-enable modules that weren't explicitly
1240         // disabled by the user.
1241         if (!isPluginModuleEnabled(descriptor.getCompleteKey()))
1242         {
1243             if (log.isDebugEnabled())
1244             {
1245                 String name = descriptor.getName() == null ? descriptor.getKey() : descriptor.getName();
1246                 log.debug("Plugin module '" + name + "' is explicitly disabled (or so by default), so not re-enabling.");
1247             }
1248             return success;
1249         }
1250 
1251         try
1252         {
1253             notifyModuleEnabled(descriptor);
1254             enabledDescriptors.add(descriptor);
1255         }
1256         catch (final Throwable exception)
1257         {
1258             // catch any errors and insert an UnloadablePlugin (PLUG-7)
1259             log.error("There was an error loading the descriptor '" + descriptor.getName() + "' of plugin '" + plugin.getKey() + "'. Disabling.",
1260                 exception);
1261 
1262             // Disable all previously enabled descriptors
1263             for (final ModuleDescriptor<?> desc : enabledDescriptors)
1264             {
1265                 notifyModuleDisabled(desc);
1266             }
1267 
1268             replacePluginWithUnloadablePlugin(plugin, descriptor, exception);
1269             success = false;
1270         }
1271 
1272         return success;
1273     }
1274 
1275     public void disablePlugin(final String key)
1276     {
1277         disablePluginInternal(key, true);
1278     }
1279 
1280     public void disablePluginWithoutPersisting(final String key)
1281     {
1282         disablePluginInternal(key, false);
1283     }
1284 
1285     protected void disablePluginInternal(final String key, final boolean persistDisabledState)
1286     {
1287         if (key == null)
1288         {
1289             throw new IllegalArgumentException("You must specify a plugin key to disable.");
1290         }
1291 
1292         final Plugin plugin = plugins.get(key);
1293         if (plugin == null)
1294         {
1295             if (log.isInfoEnabled())
1296             {
1297                 log.info("No plugin was found for key '" + key + "'. Not disabling.");
1298             }
1299             return;
1300         }
1301 
1302         notifyPluginDisabled(plugin);
1303         if (persistDisabledState)
1304         {
1305             disablePluginState(plugin, getStore());
1306         }
1307     }
1308 
1309     protected void disablePluginState(final Plugin plugin, final PluginPersistentStateStore stateStore)
1310     {
1311         stateStore.save(getBuilder().setEnabled(plugin, false).toState());
1312     }
1313 
1314     protected void notifyPluginDisabled(final Plugin plugin)
1315     {
1316         if (log.isInfoEnabled())
1317         {
1318             log.info("Disabling " + plugin.getKey());
1319         }
1320         disablePluginModules(plugin);
1321 
1322         // This needs to happen after modules are disabled to prevent errors
1323         plugin.disable();
1324         pluginEventManager.broadcast(new PluginDisabledEvent(plugin));
1325     }
1326 
1327     private void disablePluginModules(final Plugin plugin)
1328     {
1329         final List<ModuleDescriptor<?>> moduleDescriptors = new ArrayList<ModuleDescriptor<?>>(plugin.getModuleDescriptors());
1330         Collections.reverse(moduleDescriptors); // disable in reverse order
1331 
1332         for (final ModuleDescriptor<?> module : moduleDescriptors)
1333         {
1334             // don't actually disable the module, just fire the events because
1335             // its plugin is being disabled
1336             // if the module was actually disabled, you'd have to reenable each
1337             // one when enabling the plugin
1338 
1339             disablePluginModuleNoPersist(module);
1340         }
1341     }
1342 
1343     private void disablePluginModuleNoPersist(final ModuleDescriptor<?> module) {
1344         if (isPluginModuleEnabled(module.getCompleteKey()))
1345         {
1346             publishModuleDisabledEvents(module, false);
1347         }
1348     }
1349 
1350     public void disablePluginModule(final String completeKey)
1351     {
1352         if (completeKey == null)
1353         {
1354             throw new IllegalArgumentException("You must specify a plugin module key to disable.");
1355         }
1356 
1357         final ModuleDescriptor<?> module = getPluginModule(completeKey);
1358 
1359         if (module == null)
1360         {
1361             if (log.isInfoEnabled())
1362             {
1363                 log.info("Returned module for key '" + completeKey + "' was null. Not disabling.");
1364             }
1365             return;
1366         }
1367         if (module.getClass().isAnnotationPresent(CannotDisable.class))
1368         {
1369             if (log.isInfoEnabled())
1370             {
1371                 log.info("Plugin module " + completeKey + " cannot be disabled; it is annotated with" + CannotDisable.class.getName());
1372             }
1373             return;
1374         }
1375 
1376         disablePluginModuleState(module, getStore());
1377         notifyModuleDisabled(module);
1378     }
1379 
1380     protected void disablePluginModuleState(final ModuleDescriptor<?> module, final PluginPersistentStateStore stateStore)
1381     {
1382         stateStore.save(getBuilder().setEnabled(module, false).toState());
1383     }
1384 
1385     protected void notifyModuleDisabled(final ModuleDescriptor<?> module)
1386     {
1387         publishModuleDisabledEvents(module, true);
1388     }
1389 
1390     private void publishModuleDisabledEvents(final ModuleDescriptor<?> module, final boolean persistent)
1391     {
1392         if (log.isDebugEnabled())
1393         {
1394             log.debug("Disabling " + module.getKey());
1395         }
1396 
1397         if (module instanceof StateAware)
1398         {
1399             ((StateAware) module).disabled();
1400         }
1401 
1402         pluginEventManager.broadcast(new PluginModuleDisabledEvent(module, persistent));
1403     }
1404 
1405     public void enablePluginModule(final String completeKey)
1406     {
1407         if (completeKey == null)
1408         {
1409             throw new IllegalArgumentException("You must specify a plugin module key to disable.");
1410         }
1411 
1412         final ModuleDescriptor<?> module = getPluginModule(completeKey);
1413 
1414         if (module == null)
1415         {
1416             if (log.isInfoEnabled())
1417             {
1418                 log.info("Returned module for key '" + completeKey + "' was null. Not enabling.");
1419             }
1420 
1421             return;
1422         }
1423 
1424         if (!module.satisfiesMinJavaVersion())
1425         {
1426             log.error("Minimum Java version of '" + module.getMinJavaVersion() + "' was not satisfied for module '" + completeKey + "'. Not enabling.");
1427             return;
1428         }
1429         enablePluginModuleState(module, getStore());
1430         notifyModuleEnabled(module);
1431     }
1432 
1433     protected void enablePluginModuleState(final ModuleDescriptor<?> module, final PluginPersistentStateStore stateStore)
1434     {
1435         stateStore.save(getBuilder().setEnabled(module, true).toState());
1436     }
1437 
1438     protected void notifyModuleEnabled(final ModuleDescriptor<?> module)
1439     {
1440         if (log.isDebugEnabled())
1441         {
1442             log.debug("Enabling " + module.getKey());
1443         }
1444         if (module instanceof StateAware)
1445         {
1446             ((StateAware) module).enabled();
1447         }
1448         pluginEventManager.broadcast(new PluginModuleEnabledEvent(module));
1449     }
1450 
1451     public boolean isPluginModuleEnabled(final String completeKey)
1452     {
1453         // completeKey may be null
1454         return (completeKey == null) ? false : isPluginModuleEnabled(new ModuleCompleteKey(completeKey));
1455     }
1456 
1457     private boolean isPluginModuleEnabled(final ModuleCompleteKey key)
1458     {
1459         if (!isPluginEnabled(key.getPluginKey()))
1460         {
1461             return false;
1462         }
1463         final ModuleDescriptor<?> pluginModule = getPluginModule(key);
1464         return (pluginModule != null) && getState().isEnabled(pluginModule);
1465     }
1466 
1467     /**
1468      * This method checks to see if the plugin is enabled based on the state
1469      * manager and the plugin.
1470      *
1471      * @param key The plugin key
1472      * @return True if the plugin is enabled
1473      */
1474     public boolean isPluginEnabled(final String key)
1475     {
1476         final Plugin plugin = plugins.get(notNull("The plugin key must be specified", key));
1477 
1478         return plugin != null &&
1479                plugin.getPluginState() == PluginState.ENABLED &&
1480                getState().isEnabled(plugin) &&
1481                !pluginEnabler.isPluginBeingEnabled(plugin);
1482     }
1483 
1484     public InputStream getDynamicResourceAsStream(final String name)
1485     {
1486         return getClassLoader().getResourceAsStream(name);
1487     }
1488 
1489     public Class<?> getDynamicPluginClass(final String className) throws ClassNotFoundException
1490     {
1491         return getClassLoader().loadClass(className);
1492     }
1493 
1494     public PluginsClassLoader getClassLoader()
1495     {
1496         return classLoader;
1497     }
1498 
1499     public InputStream getPluginResourceAsStream(final String pluginKey, final String resourcePath)
1500     {
1501         final Plugin plugin = getEnabledPlugin(pluginKey);
1502         if (plugin == null)
1503         {
1504             log.error("Attempted to retreive resource " + resourcePath + " for non-existent or inactive plugin " + pluginKey);
1505             return null;
1506         }
1507 
1508         return plugin.getResourceAsStream(resourcePath);
1509     }
1510 
1511     /**
1512      * Disables and replaces a plugin currently loaded with an UnloadablePlugin.
1513      *
1514      * @param plugin the plugin to be replaced
1515      * @param descriptor the descriptor which caused the problem
1516      * @param throwable the problem caught when enabling the descriptor
1517      * @return the UnloadablePlugin which replaced the broken plugin
1518      */
1519     private UnloadablePlugin replacePluginWithUnloadablePlugin(final Plugin plugin, final ModuleDescriptor<?> descriptor, final Throwable throwable)
1520     {
1521         final UnloadableModuleDescriptor unloadableDescriptor = UnloadableModuleDescriptorFactory.createUnloadableModuleDescriptor(plugin,
1522             descriptor, throwable);
1523         final UnloadablePlugin unloadablePlugin = UnloadablePluginFactory.createUnloadablePlugin(plugin, unloadableDescriptor);
1524 
1525         // Add the error text at the plugin level as well. This is useful for
1526         // logging.
1527         unloadablePlugin.setErrorText(unloadableDescriptor.getErrorText());
1528         plugins.put(plugin.getKey(), unloadablePlugin);
1529 
1530         // PLUG-390: We used to persist the disabled state here, but we don't
1531         // want to do this.
1532         // We want to try load this plugin again on restart as the user may have
1533         // installed a fixed version of this plugin.
1534         return unloadablePlugin;
1535     }
1536 
1537     public boolean isSystemPlugin(final String key)
1538     {
1539         final Plugin plugin = getPlugin(key);
1540         return (plugin != null) && plugin.isSystemPlugin();
1541     }
1542 
1543     public PluginRestartState getPluginRestartState(final String key)
1544     {
1545         return getState().getPluginRestartState(key);
1546     }
1547 
1548     private Builder getBuilder()
1549     {
1550         return PluginPersistentState.Builder.create(getStore().load());
1551     }
1552 
1553     /**
1554      * @deprecated Since 2.0.0.beta2
1555      */
1556     @Deprecated
1557     public void setDescriptorParserFactory(final DescriptorParserFactory descriptorParserFactory)
1558     {}
1559 }