View Javadoc

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