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
128
129
130
131
132
133
134
135
136
137
138
139
140
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
174
175
176
177 private final Predicate<Plugin> delayLoadOf;
178
179
180
181
182
183 private RevertablePluginInstaller pluginInstaller;
184
185
186
187
188 private final Map<Plugin, PluginLoader> installedPluginsToPluginLoader;
189
190
191
192
193
194
195
196
197 private final Map<Plugin, DiscardablePluginLoader> candidatePluginsToPluginLoader;
198
199
200
201
202
203
204
205 private final Collection<Plugin> additionalPluginsToEnable;
206
207 private final DefaultPluginManagerJmxBridge defaultPluginManagerJmxBridge;
208
209
210
211
212 private final List<Plugin> delayedPlugins;
213
214
215
216
217
218
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
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
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
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
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
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
541
542
543 delayedPluginRemovalsToLoader.put(plugin, loader);
544 } else {
545
546
547
548
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
565
566
567
568
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
579 addPlugins(null, immediatePlugins);
580
581
582
583
584 for (final Plugin plugin : immediatePlugins) {
585 candidatePluginsToPluginLoader.remove(plugin);
586 }
587
588 if (Boolean.getBoolean(getLateStartupEnableRetryProperty())) {
589 for (final Plugin plugin : immediatePlugins) {
590
591
592
593 if ((PluginState.ENABLED != plugin.getPluginState()) && pluginPersistentState.isEnabled(plugin)) {
594 additionalPluginsToEnable.add(plugin);
595 }
596 }
597 if (!additionalPluginsToEnable.isEmpty()) {
598
599 log.warn("Failed to enable some ({}) early plugins, will fallback during lateStartup. Plugins: {}",
600 additionalPluginsToEnable.size(), additionalPluginsToEnable);
601 }
602 }
603
604
605
606
607
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
614
615
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
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
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
680
681
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
697
698
699
700
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
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
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
772
773
774 broadcastIgnoreError(new PluginEnablingEvent(plugin));
775
776 if (enableConfiguredPluginModules(plugin)) {
777 broadcastIgnoreError(new PluginEnabledEvent(plugin));
778 }
779 }
780
781
782
783
784
785
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
815
816
817
818
819
820
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
854
855
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
867
868 if (!PluginRestartState.INSTALL.equals(getPluginRestartState(plugin.getKey()))) {
869 markPluginUpgradeThatRequiresRestart(plugin);
870 }
871 continue;
872 }
873 }
874
875
876 else if (oldPlugin != null && PluginUtils.doesPluginRequireRestart(oldPlugin)) {
877
878
879
880
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
922 }
923
924
925
926
927
928
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
966
967
968
969
970 protected void uninstallNoEvent(final Plugin plugin) {
971 unloadPlugin(plugin);
972
973
974 persistentStateModifier.removeState(plugin);
975 }
976
977
978
979
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
999
1000
1001
1002
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
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
1067
1068
1069
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
1078 for (final Plugin plugin : new TreeSet<>(pluginsToInstall)) {
1079 boolean pluginUpgraded = false;
1080
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
1087 discardPlugin(loader, plugin);
1088
1089
1090 continue;
1091
1092 } else if (null == existingPlugin) {
1093 broadcastIgnoreError(new PluginInstallingEvent(plugin));
1094 } else if (plugin.compareTo(existingPlugin) >= 0) {
1095
1096 try {
1097
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
1109
1110
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
1125 log.debug("Duplicate plugin found (installed version is newer): '{}'", pluginKey);
1126
1127 discardPlugin(loader, plugin);
1128
1129
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
1165
1166
1167 pluginsToEnable.addAll(additionalPluginsToEnable);
1168
1169
1170 enableDependentPlugins(pluginsToEnable);
1171
1172
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);
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
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
1224 candidatePluginsToPluginLoader.get(plugin).discardPlugin(plugin);
1225 } else if (loader instanceof DiscardablePluginLoader) {
1226
1227
1228 ((DiscardablePluginLoader) loader).discardPlugin(plugin);
1229 } else {
1230
1231
1232
1233
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
1244 return true;
1245 }
1246
1247 final String cleanMinimumVersion = cleanVersionString(rawMinimumVersion);
1248
1249
1250 try {
1251
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
1261 return true;
1262 }
1263 }
1264
1265
1266
1267
1268
1269
1270
1271
1272
1273
1274
1275
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
1302
1303
1304
1305
1306
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
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
1325
1326
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
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
1341
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
1352
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
1361
1362 public Collection<Plugin> getEnabledPlugins() {
1363 return getPlugins(new EnabledPluginPredicate(pluginEnabler.getPluginsBeingEnabled()));
1364 }
1365
1366
1367
1368
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
1379
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
1388
1389
1390
1391
1392
1393
1394
1395
1396
1397 private <M> Stream<ModuleDescriptor<M>> getModuleDescriptors(final Collection<Plugin> plugins,
1398 final Predicate<ModuleDescriptor<M>> predicate) {
1399
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
1433 if (!isPluginModuleEnabled(key)) {
1434 return null;
1435 }
1436
1437 return getEnabledPlugin(key.getPluginKey()).getModuleDescriptor(key.getModuleKey());
1438 }
1439
1440
1441
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
1452
1453
1454
1455
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
1465
1466
1467
1468
1469
1470
1471 public <D extends ModuleDescriptor<?>> List<D> getEnabledModuleDescriptorsByClass(final Class<D> descriptorClazz) {
1472
1473
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
1484
1485
1486
1487
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
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
1534
1535
1536
1537
1538
1539
1540
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
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
1562
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
1574 log.error("There was an error loading the descriptor '{}' of plugin '{}'. Disabling.",
1575 descriptor.getDisplayName(), plugin, enableException);
1576
1577
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
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
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
1640
1641
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
1672
1673
1674
1675
1676
1677
1678
1679
1680
1681 private void disablePluginWithModuleEvents(final Plugin plugin) {
1682 if (plugin.getPluginState() == PluginState.DISABLED) {
1683 return;
1684 }
1685
1686 disablePluginModules(plugin);
1687
1688
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
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);
1716
1717 for (final ModuleDescriptor<?> module : moduleDescriptors) {
1718
1719
1720
1721
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
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
1812
1813
1814
1815
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
1838
1839
1840
1841
1842
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
1850
1851 unloadablePlugin.setErrorText(unloadableDescriptor.getErrorText());
1852 pluginRegistry.put(unloadablePlugin);
1853
1854
1855
1856
1857
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
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
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
1898 if (plugin.getModuleDescriptor(moduleDescriptor.getKey()) != null) {
1899 throw new PluginException("duplicate module key '" + moduleDescriptor.getKey() + "' for plugin '" + plugin + "'");
1900 }
1901
1902
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
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
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
1932 persistentStateModifier.disable(module);
1933 notifyModuleDisabled(module);
1934
1935
1936 module.destroy();
1937 }
1938
1939 @VisibleForTesting
1940 PluginInternal checkPluginInternal(final Plugin maybePluginInternal) {
1941
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 }