View Javadoc

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