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