View Javadoc

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