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