1 package com.atlassian.plugin.manager;
2
3 import static com.atlassian.plugin.util.Assertions.notNull;
4 import static com.atlassian.plugin.util.collect.CollectionUtil.toList;
5 import static com.google.common.collect.Iterables.concat;
6 import static com.google.common.collect.Iterables.filter;
7 import static com.google.common.collect.Iterables.transform;
8 import static com.google.common.collect.Maps.filterKeys;
9
10 import com.atlassian.plugin.*;
11 import com.atlassian.plugin.classloader.PluginsClassLoader;
12 import com.atlassian.plugin.descriptors.CannotDisable;
13 import com.atlassian.plugin.descriptors.UnloadableModuleDescriptor;
14 import com.atlassian.plugin.descriptors.UnloadableModuleDescriptorFactory;
15 import com.atlassian.plugin.event.NotificationException;
16 import com.atlassian.plugin.event.PluginEventListener;
17 import com.atlassian.plugin.event.PluginEventManager;
18 import com.atlassian.plugin.event.events.PluginContainerUnavailableEvent;
19 import com.atlassian.plugin.event.events.PluginDisabledEvent;
20 import com.atlassian.plugin.event.events.PluginEnabledEvent;
21 import com.atlassian.plugin.event.events.PluginFrameworkShutdownEvent;
22 import com.atlassian.plugin.event.events.PluginFrameworkStartedEvent;
23 import com.atlassian.plugin.event.events.PluginFrameworkStartingEvent;
24 import com.atlassian.plugin.event.events.PluginFrameworkWarmRestartedEvent;
25 import com.atlassian.plugin.event.events.PluginFrameworkWarmRestartingEvent;
26 import com.atlassian.plugin.event.events.PluginModuleAvailableEvent;
27 import com.atlassian.plugin.event.events.PluginModuleDisabledEvent;
28 import com.atlassian.plugin.event.events.PluginModuleEnabledEvent;
29 import com.atlassian.plugin.event.events.PluginModuleUnavailableEvent;
30 import com.atlassian.plugin.event.events.PluginRefreshedEvent;
31 import com.atlassian.plugin.event.events.PluginUninstalledEvent;
32 import com.atlassian.plugin.event.events.PluginUpgradedEvent;
33 import com.atlassian.plugin.impl.UnloadablePlugin;
34 import com.atlassian.plugin.impl.UnloadablePluginFactory;
35 import com.atlassian.plugin.loaders.DynamicPluginLoader;
36 import com.atlassian.plugin.loaders.PluginLoader;
37 import com.atlassian.plugin.manager.PluginPersistentState.Builder;
38 import com.atlassian.plugin.metadata.ClasspathFilePluginMetadata;
39 import com.atlassian.plugin.metadata.RequiredPluginValidator;
40 import com.atlassian.plugin.parsers.DescriptorParserFactory;
41 import com.atlassian.plugin.predicate.EnabledModulePredicate;
42 import com.atlassian.plugin.predicate.EnabledPluginPredicate;
43 import com.atlassian.plugin.predicate.ModuleDescriptorOfClassPredicate;
44 import com.atlassian.plugin.predicate.ModuleDescriptorOfTypePredicate;
45 import com.atlassian.plugin.predicate.ModuleDescriptorPredicate;
46 import com.atlassian.plugin.predicate.ModuleOfClassPredicate;
47 import com.atlassian.plugin.predicate.PluginPredicate;
48 import com.atlassian.plugin.util.PluginUtils;
49 import com.atlassian.util.concurrent.CopyOnWriteMap;
50
51 import org.apache.commons.lang.time.StopWatch;
52 import org.slf4j.Logger;
53 import org.slf4j.LoggerFactory;
54
55 import com.google.common.base.Function;
56 import com.google.common.base.Predicate;
57
58 import java.io.InputStream;
59 import java.util.ArrayList;
60 import java.util.Collection;
61 import java.util.Collections;
62 import java.util.HashMap;
63 import java.util.HashSet;
64 import java.util.LinkedHashMap;
65 import java.util.LinkedList;
66 import java.util.List;
67 import java.util.Map;
68 import java.util.Set;
69 import java.util.TreeSet;
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87 public class DefaultPluginManager implements PluginController, PluginAccessor, PluginSystemLifecycle
88 {
89 private static final Logger log = LoggerFactory.getLogger(DefaultPluginManager.class);
90
91 private final List<PluginLoader> pluginLoaders;
92 private final PluginPersistentStateStore store;
93 private final ModuleDescriptorFactory moduleDescriptorFactory;
94 private final PluginEventManager pluginEventManager;
95
96 private final Map<String, Plugin> plugins = CopyOnWriteMap.<String, Plugin> builder().stableViews().newHashMap();
97 private final PluginsClassLoader classLoader;
98 private final PluginEnabler pluginEnabler = new PluginEnabler(this, this);
99 private final StateTracker tracker = new StateTracker();
100
101 private final boolean verifyRequiredPlugins;
102
103
104
105
106
107 private RevertablePluginInstaller pluginInstaller = new NoOpRevertablePluginInstaller(new UnsupportedPluginInstaller());
108
109
110
111
112 private final Map<Plugin, PluginLoader> pluginToPluginLoader = new HashMap<Plugin, PluginLoader>();
113
114 public DefaultPluginManager(final PluginPersistentStateStore store, final List<PluginLoader> pluginLoaders, final ModuleDescriptorFactory moduleDescriptorFactory, final PluginEventManager pluginEventManager)
115 {
116 this(store, pluginLoaders, moduleDescriptorFactory, pluginEventManager, false);
117 }
118
119 public DefaultPluginManager(final PluginPersistentStateStore store, final List<PluginLoader> pluginLoaders, final ModuleDescriptorFactory moduleDescriptorFactory, final PluginEventManager pluginEventManager, boolean verifyRequiredPlugins)
120 {
121 this.pluginLoaders = notNull("Plugin Loaders list must not be null.", pluginLoaders);
122 this.store = notNull("PluginPersistentStateStore must not be null.", store);
123 this.moduleDescriptorFactory = notNull("ModuleDescriptorFactory must not be null.", moduleDescriptorFactory);
124 this.pluginEventManager = notNull("PluginEventManager must not be null.", pluginEventManager);
125
126 this.pluginEventManager.register(this);
127 this.verifyRequiredPlugins = verifyRequiredPlugins;
128 classLoader = new PluginsClassLoader(null, this, pluginEventManager);
129 }
130
131 public void init() throws PluginParseException, NotificationException
132 {
133 tracker.setState(StateTracker.State.STARTING);
134 final StopWatch stopWatch = new StopWatch();
135 stopWatch.start();
136 log.info("Initialising the plugin system");
137 pluginEventManager.broadcast(new PluginFrameworkStartingEvent(this, this));
138 pluginInstaller.clearBackups();
139 for (final PluginLoader loader : pluginLoaders)
140 {
141 if (loader == null)
142 {
143 continue;
144 }
145
146 final Iterable<Plugin> possiblePluginsToLoad = loader.loadAllPlugins(moduleDescriptorFactory);
147 final Collection<Plugin> pluginsToLoad = new ArrayList<Plugin>();
148 for (final Plugin plugin : possiblePluginsToLoad)
149 {
150 if (getState().getPluginRestartState(plugin.getKey()) == PluginRestartState.REMOVE)
151 {
152 if (log.isInfoEnabled())
153 {
154 log.info("Plugin " + plugin.getKey() + " was marked to be removed on restart. Removing now.");
155 }
156 loader.removePlugin(plugin);
157
158
159 removeStateFromStore(getStore(), plugin);
160 }
161 else
162 {
163 pluginsToLoad.add(plugin);
164 }
165 }
166 addPlugins(loader, pluginsToLoad);
167 }
168
169 getStore().save(getBuilder().clearPluginRestartState().toState());
170
171 stopWatch.stop();
172 log.info("Plugin system started in " + stopWatch);
173 tracker.setState(StateTracker.State.STARTED);
174 if (verifyRequiredPlugins)
175 {
176 validateRequiredPlugins();
177 }
178 pluginEventManager.broadcast(new PluginFrameworkStartedEvent(this, this));
179 }
180
181 private void validateRequiredPlugins() throws PluginException
182 {
183 RequiredPluginValidator validator = new RequiredPluginValidator(this, new ClasspathFilePluginMetadata());
184 Collection<String> errors = validator.validate();
185 if (errors.size() > 0)
186 {
187 log.error("Unable to validate required plugins or modules - plugin system shutting down");
188 log.error("Failures:");
189 for (String error : errors)
190 {
191 log.error("\t{}", error);
192 }
193 shutdown();
194 throw new PluginException("Unable to validate required plugins or modules");
195 }
196 }
197
198
199
200
201
202
203
204
205 public void shutdown()
206 {
207 tracker.setState(StateTracker.State.SHUTTING_DOWN);
208 log.info("Shutting down the plugin system");
209 try
210 {
211 pluginEventManager.broadcast(new PluginFrameworkShutdownEvent(this, this));
212 }
213 catch (final NotificationException ex)
214 {
215 log.error("At least one error occured while broadcasting the PluginFrameworkShutdownEvent. We will continue to shutdown the Plugin Manager anyway.");
216 }
217 plugins.clear();
218 pluginEventManager.unregister(this);
219 tracker.setState(StateTracker.State.SHUTDOWN);
220 }
221
222 public final void warmRestart()
223 {
224 tracker.setState(StateTracker.State.WARM_RESTARTING);
225 log.info("Initiating a warm restart of the plugin system");
226 pluginEventManager.broadcast(new PluginFrameworkWarmRestartingEvent(this, this));
227
228
229 final List<Plugin> restartedPlugins = new ArrayList<Plugin>();
230 final List<PluginLoader> loaders = new ArrayList<PluginLoader>(pluginLoaders);
231 Collections.reverse(loaders);
232 for (final PluginLoader loader : pluginLoaders)
233 {
234 for (final Map.Entry<Plugin, PluginLoader> entry : pluginToPluginLoader.entrySet())
235 {
236 if (entry.getValue() == loader)
237 {
238 final Plugin plugin = entry.getKey();
239 if (isPluginEnabled(plugin.getKey()))
240 {
241 disablePluginModules(plugin);
242 restartedPlugins.add(plugin);
243 }
244 }
245 }
246 }
247
248
249 Collections.reverse(restartedPlugins);
250 for (final Plugin plugin : restartedPlugins)
251 {
252 enableConfiguredPluginModules(plugin);
253 }
254
255 pluginEventManager.broadcast(new PluginFrameworkWarmRestartedEvent(this, this));
256 tracker.setState(StateTracker.State.STARTED);
257 }
258
259 @PluginEventListener
260 public void onPluginModuleAvailable(final PluginModuleAvailableEvent event)
261 {
262 enableConfiguredPluginModule(event.getModule().getPlugin(), event.getModule(), new HashSet<ModuleDescriptor<?>>());
263 }
264
265 @PluginEventListener
266 public void onPluginModuleUnavailable(final PluginModuleUnavailableEvent event)
267 {
268 notifyModuleDisabled(event.getModule());
269 }
270
271 @PluginEventListener
272 public void onPluginContainerUnavailable(final PluginContainerUnavailableEvent event)
273 {
274 disablePluginWithoutPersisting(event.getPluginKey());
275 }
276
277 @PluginEventListener
278 public void onPluginRefresh(final PluginRefreshedEvent event)
279 {
280 final Plugin plugin = event.getPlugin();
281
282 disablePluginModules(plugin);
283
284
285 if (enableConfiguredPluginModules(plugin))
286 {
287 pluginEventManager.broadcast(new PluginEnabledEvent(plugin));
288 }
289 }
290
291
292
293
294
295
296
297 public void setPluginInstaller(final PluginInstaller pluginInstaller)
298 {
299 if (pluginInstaller instanceof RevertablePluginInstaller)
300 {
301 this.pluginInstaller = (RevertablePluginInstaller) pluginInstaller;
302 }
303 else
304 {
305 this.pluginInstaller = new NoOpRevertablePluginInstaller(pluginInstaller);
306 }
307 }
308
309 protected final PluginPersistentStateStore getStore()
310 {
311 return store;
312 }
313
314 public String installPlugin(final PluginArtifact pluginArtifact) throws PluginParseException
315 {
316 final Set<String> keys = installPlugins(pluginArtifact);
317 if ((keys != null) && (keys.size() == 1))
318 {
319 return keys.iterator().next();
320 }
321 else
322 {
323
324 throw new PluginParseException("Could not install plugin");
325 }
326 }
327
328 public Set<String> installPlugins(final PluginArtifact... pluginArtifacts) throws PluginParseException
329 {
330 final Map<String, PluginArtifact> validatedArtifacts = new LinkedHashMap<String, PluginArtifact>();
331 try
332 {
333 for (final PluginArtifact pluginArtifact : pluginArtifacts)
334 {
335 validatedArtifacts.put(validatePlugin(pluginArtifact), pluginArtifact);
336 }
337 }
338 catch (final PluginParseException ex)
339 {
340 throw new PluginParseException("All plugins could not be validated", ex);
341 }
342
343 for (final Map.Entry<String, PluginArtifact> entry : validatedArtifacts.entrySet())
344 {
345 pluginInstaller.installPlugin(entry.getKey(), entry.getValue());
346 }
347
348 scanForNewPlugins();
349 return validatedArtifacts.keySet();
350 }
351
352
353
354
355
356
357
358
359
360
361 String validatePlugin(final PluginArtifact pluginArtifact) throws PluginParseException
362 {
363 boolean foundADynamicPluginLoader = false;
364 for (final PluginLoader loader : pluginLoaders)
365 {
366 if (loader instanceof DynamicPluginLoader)
367 {
368 foundADynamicPluginLoader = true;
369 final String key = ((DynamicPluginLoader) loader).canLoad(pluginArtifact);
370 if (key != null)
371 {
372 return key;
373 }
374 }
375 }
376
377 if (!foundADynamicPluginLoader)
378 {
379 throw new IllegalStateException("Should be at least one DynamicPluginLoader in the plugin loader list");
380 }
381 throw new PluginParseException("Jar " + pluginArtifact.getName() + " is not a valid plugin");
382 }
383
384 public int scanForNewPlugins() throws PluginParseException
385 {
386 int numberFound = 0;
387
388 for (final PluginLoader loader : pluginLoaders)
389 {
390 if (loader != null)
391 {
392 if (loader.supportsAddition())
393 {
394 final List<Plugin> pluginsToAdd = new ArrayList<Plugin>();
395 for (Plugin plugin : loader.addFoundPlugins(moduleDescriptorFactory))
396 {
397 final Plugin oldPlugin = plugins.get(plugin.getKey());
398
399
400
401 if (!(plugin instanceof UnloadablePlugin))
402 {
403 if (PluginUtils.doesPluginRequireRestart(plugin))
404 {
405 if (oldPlugin == null)
406 {
407 markPluginInstallThatRequiresRestart(plugin);
408
409 final UnloadablePlugin unloadablePlugin = UnloadablePluginFactory.createUnloadablePlugin(plugin);
410 unloadablePlugin.setErrorText("Plugin requires a restart of the application due " + "to the following modules: " + PluginUtils.getPluginModulesThatRequireRestart(plugin));
411 plugin = unloadablePlugin;
412 }
413 else
414 {
415
416
417 if (!PluginRestartState.INSTALL.equals(getPluginRestartState(plugin.getKey())))
418 {
419 markPluginUpgradeThatRequiresRestart(plugin);
420 }
421 continue;
422 }
423 }
424
425
426 else if (oldPlugin != null && PluginUtils.doesPluginRequireRestart(oldPlugin))
427 {
428
429
430
431
432 if (PluginRestartState.INSTALL.equals(getPluginRestartState(oldPlugin.getKey())))
433 {
434 revertRestartRequiredChange(oldPlugin.getKey());
435 }
436 else
437 {
438 markPluginUpgradeThatRequiresRestart(plugin);
439 continue;
440 }
441 }
442 pluginsToAdd.add(plugin);
443 }
444 }
445 addPlugins(loader, pluginsToAdd);
446 numberFound = pluginsToAdd.size();
447 }
448 }
449 }
450 return numberFound;
451 }
452
453 private void markPluginInstallThatRequiresRestart(final Plugin plugin)
454 {
455 if (log.isInfoEnabled())
456 {
457 log.info("Installed plugin '" + plugin.getKey() + "' requires a restart due to the following modules: " + PluginUtils.getPluginModulesThatRequireRestart(plugin));
458 }
459 updateRequiresRestartState(plugin.getKey(), PluginRestartState.INSTALL);
460 }
461
462 private void markPluginUpgradeThatRequiresRestart(final Plugin plugin)
463 {
464 if (log.isInfoEnabled())
465 {
466 log.info("Upgraded plugin '" + plugin.getKey() + "' requires a restart due to the following modules: " + PluginUtils.getPluginModulesThatRequireRestart(plugin));
467 }
468 updateRequiresRestartState(plugin.getKey(), PluginRestartState.UPGRADE);
469 }
470
471 private void markPluginUninstallThatRequiresRestart(final Plugin plugin)
472 {
473 if (log.isInfoEnabled())
474 {
475 log.info("Uninstalled plugin '" + plugin.getKey() + "' requires a restart due to the following modules: " + PluginUtils.getPluginModulesThatRequireRestart(plugin));
476 }
477 updateRequiresRestartState(plugin.getKey(), PluginRestartState.REMOVE);
478 }
479
480 private void updateRequiresRestartState(final String pluginKey, final PluginRestartState pluginRestartState)
481 {
482 getStore().save(getBuilder().setPluginRestartState(pluginKey, pluginRestartState).toState());
483 onUpdateRequiresRestartState(pluginKey, pluginRestartState);
484 }
485
486 protected void onUpdateRequiresRestartState(final String pluginKey, final PluginRestartState pluginRestartState)
487 {
488
489 }
490
491
492
493
494
495
496
497 public void uninstall(final Plugin plugin) throws PluginException
498 {
499 if (PluginUtils.doesPluginRequireRestart(plugin))
500 {
501 ensurePluginAndLoaderSupportsUninstall(plugin);
502 markPluginUninstallThatRequiresRestart(plugin);
503 }
504 else
505 {
506
507 disableDependentPlugins(plugin);
508
509 uninstallNoEvent(plugin);
510
511 pluginEventManager.broadcast(new PluginUninstalledEvent(plugin));
512 }
513 }
514
515
516
517
518
519
520
521 protected void uninstallNoEvent(final Plugin plugin)
522 {
523 unloadPlugin(plugin);
524
525
526 removeStateFromStore(getStore(), plugin);
527 }
528
529
530
531
532
533 public void revertRestartRequiredChange(final String pluginKey) throws PluginException
534 {
535 notNull("pluginKey", pluginKey);
536 final PluginRestartState restartState = getState().getPluginRestartState(pluginKey);
537 if (restartState == PluginRestartState.UPGRADE)
538 {
539 pluginInstaller.revertInstalledPlugin(pluginKey);
540 }
541 else if (restartState == PluginRestartState.INSTALL)
542 {
543 pluginInstaller.revertInstalledPlugin(pluginKey);
544 plugins.remove(pluginKey);
545 }
546 updateRequiresRestartState(pluginKey, PluginRestartState.NONE);
547 }
548
549 protected void removeStateFromStore(final PluginPersistentStateStore stateStore, final Plugin plugin)
550 {
551 final PluginPersistentState.Builder builder = PluginPersistentState.Builder.create(stateStore.load()).removeState(plugin.getKey());
552 for (final ModuleDescriptor<?> moduleDescriptor : plugin.getModuleDescriptors())
553 {
554 builder.removeState(moduleDescriptor.getCompleteKey());
555 }
556 stateStore.save(builder.toState());
557 }
558
559
560
561
562
563
564
565
566 protected void unloadPlugin(final Plugin plugin) throws PluginException
567 {
568 final PluginLoader loader = ensurePluginAndLoaderSupportsUninstall(plugin);
569
570 if (isPluginEnabled(plugin.getKey()))
571 {
572 notifyPluginDisabled(plugin);
573 }
574
575 notifyUninstallPlugin(plugin);
576 if (loader != null)
577 {
578 removePluginFromLoader(plugin);
579 }
580
581 plugins.remove(plugin.getKey());
582 }
583
584 private PluginLoader ensurePluginAndLoaderSupportsUninstall(final Plugin plugin)
585 {
586 if (!plugin.isUninstallable())
587 {
588 throw new PluginException("Plugin is not uninstallable: " + plugin.getKey());
589 }
590
591 final PluginLoader loader = pluginToPluginLoader.get(plugin);
592
593 if ((loader != null) && !loader.supportsRemoval())
594 {
595 throw new PluginException("Not uninstalling plugin - loader doesn't allow removal. Plugin: " + plugin.getKey());
596 }
597 return loader;
598 }
599
600 private void removePluginFromLoader(final Plugin plugin) throws PluginException
601 {
602 if (plugin.isDeleteable())
603 {
604 final PluginLoader pluginLoader = pluginToPluginLoader.get(plugin);
605 pluginLoader.removePlugin(plugin);
606 }
607
608 pluginToPluginLoader.remove(plugin);
609 }
610
611 protected void notifyUninstallPlugin(final Plugin plugin)
612 {
613 classLoader.notifyUninstallPlugin(plugin);
614
615 for (final ModuleDescriptor<?> descriptor : plugin.getModuleDescriptors())
616 {
617 descriptor.destroy(plugin);
618 }
619 }
620
621 protected PluginPersistentState getState()
622 {
623 return getStore().load();
624 }
625
626
627
628
629
630 @Deprecated
631 protected void addPlugin(final PluginLoader loader, final Plugin plugin) throws PluginParseException
632 {
633 addPlugins(loader, Collections.singletonList(plugin));
634 }
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652 protected void addPlugins(final PluginLoader loader, final Collection<Plugin> pluginsToInstall) throws PluginParseException
653 {
654 final List<Plugin> pluginsToEnable = new ArrayList<Plugin>();
655
656
657 for (final Plugin plugin : new TreeSet<Plugin>(pluginsToInstall))
658 {
659 boolean pluginUpgraded = false;
660
661 final Plugin existingPlugin = plugins.get(plugin.getKey());
662 if (existingPlugin != null)
663 {
664 if (plugin.compareTo(existingPlugin) >= 0)
665 {
666 try
667 {
668
669
670 pluginsToEnable.addAll(disableDependentPlugins(plugin));
671 updatePlugin(existingPlugin, plugin);
672 pluginsToEnable.remove(existingPlugin);
673 pluginUpgraded = true;
674 }
675 catch (final PluginException e)
676 {
677 throw new PluginParseException(
678 "Duplicate plugin found (installed version is the same or older) and could not be unloaded: '" + plugin.getKey() + "'", e);
679 }
680 }
681 else
682 {
683
684
685 if (log.isDebugEnabled())
686 {
687 log.debug("Duplicate plugin found (installed version is newer): '" + plugin.getKey() + "'");
688 }
689
690 continue;
691 }
692 }
693
694 plugin.install();
695 final boolean isPluginEnabled = getState().isEnabled(plugin);
696 if (isPluginEnabled)
697 {
698 pluginsToEnable.add(plugin);
699 }
700 if (plugin.isSystemPlugin() && !isPluginEnabled)
701 {
702 log.warn("System plugin is disabled: " + plugin.getKey());
703 }
704 if (pluginUpgraded)
705 {
706 pluginEventManager.broadcast(new PluginUpgradedEvent(plugin));
707 }
708 plugins.put(plugin.getKey(), plugin);
709 pluginToPluginLoader.put(plugin, loader);
710 }
711
712
713 pluginEnabler.enable(pluginsToEnable);
714
715
716 for (final Plugin plugin : pluginsToEnable)
717 {
718 if (plugin.getPluginState() == PluginState.ENABLED)
719 {
720
721 if (enableConfiguredPluginModules(plugin))
722 {
723 pluginEventManager.broadcast(new PluginEnabledEvent(plugin));
724 }
725 }
726 }
727 }
728
729
730
731
732
733
734
735
736 private Set<Plugin> disableDependentPlugins(final Plugin plugin)
737 {
738 final Set<Plugin> dependentPlugins = new HashSet<Plugin>();
739 final Set<String> dependentPluginKeys = new HashSet<String>();
740
741 for (final Plugin depPlugin : getEnabledPlugins())
742 {
743 if ((plugin != depPlugin) && depPlugin.getRequiredPlugins().contains(plugin.getKey()))
744 {
745 dependentPlugins.add(depPlugin);
746 dependentPluginKeys.add(depPlugin.getKey());
747 }
748 }
749 if (log.isInfoEnabled())
750 {
751 log.info("Found dependent enabled plugins for uninstalled plugin '" + plugin.getKey() + "': " + dependentPluginKeys + ". Disabling...");
752 }
753 for (final Plugin depPlugin : dependentPlugins)
754 {
755 disablePluginWithoutPersisting(depPlugin.getKey());
756 }
757 return dependentPlugins;
758 }
759
760
761
762
763
764
765
766
767
768 protected void updatePlugin(final Plugin oldPlugin, final Plugin newPlugin) throws PluginException
769 {
770 if (!oldPlugin.getKey().equals(newPlugin.getKey()))
771 {
772 throw new IllegalArgumentException("New plugin must have the same key as the old plugin");
773 }
774
775 if (log.isInfoEnabled())
776 {
777 log.info("Updating plugin '" + oldPlugin + "' to '" + newPlugin + "'");
778 }
779
780
781
782
783 final Map<String, Boolean> oldPluginState = new HashMap<String, Boolean>(getState().getPluginStateMap(oldPlugin));
784
785 if (log.isDebugEnabled())
786 {
787 log.debug("Uninstalling old plugin: " + oldPlugin);
788 }
789 uninstallNoEvent(oldPlugin);
790 if (log.isDebugEnabled())
791 {
792 log.debug("Plugin uninstalled '" + oldPlugin + "', preserving old state");
793 }
794
795
796 final Set<String> newModuleKeys = new HashSet<String>();
797 newModuleKeys.add(newPlugin.getKey());
798 for (final ModuleDescriptor<?> moduleDescriptor : newPlugin.getModuleDescriptors())
799 {
800 newModuleKeys.add(moduleDescriptor.getCompleteKey());
801 }
802
803
804
805
806 final Predicate<String> filter = new Predicate<String>()
807 {
808 public boolean apply(final String o)
809 {
810 return newModuleKeys.contains(o);
811 }
812 };
813
814 getStore().save(getBuilder().addState(filterKeys(oldPluginState, filter)).toState());
815 }
816
817 public Collection<Plugin> getPlugins()
818 {
819 return plugins.values();
820 }
821
822
823
824
825
826 public Collection<Plugin> getPlugins(final PluginPredicate pluginPredicate)
827 {
828 return toList(filter(getPlugins(), new Predicate<Plugin>()
829 {
830 public boolean apply(final Plugin plugin)
831 {
832 return pluginPredicate.matches(plugin);
833 }
834 }));
835 }
836
837
838
839
840 public Collection<Plugin> getEnabledPlugins()
841 {
842 return getPlugins(new EnabledPluginPredicate(this));
843 }
844
845
846
847
848
849 public <M> Collection<M> getModules(final ModuleDescriptorPredicate<M> moduleDescriptorPredicate)
850 {
851 return toList(getModules(getModuleDescriptors(moduleDescriptorPredicate)));
852 }
853
854
855
856
857
858 public <M> Collection<ModuleDescriptor<M>> getModuleDescriptors(final ModuleDescriptorPredicate<M> moduleDescriptorPredicate)
859 {
860 return toList(getModuleDescriptors(getPlugins(), moduleDescriptorPredicate));
861 }
862
863
864
865
866
867
868
869
870
871
872
873
874 private <M> Iterable<ModuleDescriptor<M>> getModuleDescriptors(final Collection<Plugin> plugins, final ModuleDescriptorPredicate<M> predicate)
875 {
876
877
878 final Function<ModuleDescriptor<?>, ModuleDescriptor<M>> coercer = new Function<ModuleDescriptor<?>, ModuleDescriptor<M>>()
879 {
880 public ModuleDescriptor<M> apply(final ModuleDescriptor<?> input)
881 {
882 @SuppressWarnings("unchecked")
883 final ModuleDescriptor<M> result = (ModuleDescriptor<M>) input;
884 return result;
885 }
886 };
887
888
889 final Predicate<ModuleDescriptor<M>> adapter = new Predicate<ModuleDescriptor<M>>()
890 {
891 public boolean apply(final ModuleDescriptor<M> input)
892 {
893 return predicate.matches(input);
894 }
895 };
896
897
898 final Function<Plugin, Iterable<ModuleDescriptor<M>>> descriptorExtractor = new Function<Plugin, Iterable<ModuleDescriptor<M>>>()
899 {
900 public Iterable<ModuleDescriptor<M>> apply(final Plugin plugin)
901 {
902 return filter(transform(plugin.getModuleDescriptors(), coercer), adapter);
903 }
904 };
905
906
907 return concat(transform(plugins, descriptorExtractor));
908 }
909
910
911
912
913
914
915
916
917
918
919
920 private <M> List<M> getModules(final Iterable<ModuleDescriptor<M>> moduleDescriptors)
921 {
922 final Set<String> pluginsToDisable = new HashSet<String>();
923 try
924 {
925 return toList(transform(moduleDescriptors, new Function<ModuleDescriptor<M>, M>()
926 {
927 public M apply(final ModuleDescriptor<M> input)
928 {
929 M result = null;
930 try
931 {
932 result = input.getModule();
933 }
934 catch (final RuntimeException ex)
935 {
936 log.error(
937 "Exception when retrieving plugin module " + input.getKey() + ", will disable plugin " + input.getPlugin().getKey(), ex);
938 pluginsToDisable.add(input.getPlugin().getKey());
939 }
940 return result;
941 }
942 }));
943 }
944 finally
945 {
946 for (final String badPluginKey : pluginsToDisable)
947 {
948 disablePlugin(badPluginKey);
949 }
950 }
951 }
952
953 public Plugin getPlugin(final String key)
954 {
955 return plugins.get(notNull("The plugin key must be specified", key));
956 }
957
958 public Plugin getEnabledPlugin(final String pluginKey)
959 {
960 if (!isPluginEnabled(pluginKey))
961 {
962 return null;
963 }
964 return getPlugin(pluginKey);
965 }
966
967 public ModuleDescriptor<?> getPluginModule(final String completeKey)
968 {
969 return getPluginModule(new ModuleCompleteKey(completeKey));
970 }
971
972 private ModuleDescriptor<?> getPluginModule(final ModuleCompleteKey key)
973 {
974 final Plugin plugin = getPlugin(key.getPluginKey());
975 if (plugin == null)
976 {
977 return null;
978 }
979 return plugin.getModuleDescriptor(key.getModuleKey());
980 }
981
982 public ModuleDescriptor<?> getEnabledPluginModule(final String completeKey)
983 {
984 final ModuleCompleteKey key = new ModuleCompleteKey(completeKey);
985
986
987 if (!isPluginModuleEnabled(key))
988 {
989 return null;
990 }
991
992 return getEnabledPlugin(key.getPluginKey()).getModuleDescriptor(key.getModuleKey());
993 }
994
995
996
997
998 public <M> List<M> getEnabledModulesByClass(final Class<M> moduleClass)
999 {
1000 return getModules(getEnabledModuleDescriptorsByModuleClass(moduleClass));
1001 }
1002
1003
1004
1005
1006
1007
1008
1009 @Deprecated
1010 public <M> List<M> getEnabledModulesByClassAndDescriptor(final Class<ModuleDescriptor<M>>[] descriptorClasses, final Class<M> moduleClass)
1011 {
1012 final Iterable<ModuleDescriptor<M>> moduleDescriptors = filterDescriptors(getEnabledModuleDescriptorsByModuleClass(moduleClass), new ModuleDescriptorOfClassPredicate<M>(descriptorClasses));
1013
1014 return getModules(moduleDescriptors);
1015 }
1016
1017
1018
1019
1020
1021
1022
1023 @Deprecated
1024 public <M> List<M> getEnabledModulesByClassAndDescriptor(final Class<ModuleDescriptor<M>> descriptorClass, final Class<M> moduleClass)
1025 {
1026 final Iterable<ModuleDescriptor<M>> moduleDescriptors = getEnabledModuleDescriptorsByModuleClass(moduleClass);
1027 return getModules(filterDescriptors(moduleDescriptors, new ModuleDescriptorOfClassPredicate<M>(descriptorClass)));
1028 }
1029
1030
1031
1032
1033
1034
1035
1036
1037 private <M> Collection<ModuleDescriptor<M>> getEnabledModuleDescriptorsByModuleClass(final Class<M> moduleClass)
1038 {
1039 final ModuleOfClassPredicate<M> ofType = new ModuleOfClassPredicate<M>(moduleClass);
1040 final EnabledModulePredicate<M> enabled = new EnabledModulePredicate<M>(this);
1041 return toList(getModuleDescriptors(getEnabledPlugins(), new ModuleDescriptorPredicate<M>()
1042 {
1043 public boolean matches(final ModuleDescriptor<? extends M> moduleDescriptor)
1044 {
1045 return ofType.matches(moduleDescriptor) && enabled.matches(moduleDescriptor);
1046 }
1047 }));
1048 }
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058 public <D extends ModuleDescriptor<?>> List<D> getEnabledModuleDescriptorsByClass(final Class<D> descriptorClazz)
1059 {
1060 final List<D> result = new LinkedList<D>();
1061 for (final Plugin plugin : plugins.values())
1062 {
1063
1064 if (!isPluginEnabled(plugin.getKey()))
1065 {
1066 if (log.isDebugEnabled())
1067 {
1068 log.debug("Plugin [" + plugin.getKey() + "] is disabled.");
1069 }
1070 continue;
1071 }
1072
1073 for (final ModuleDescriptor<?> module : plugin.getModuleDescriptors())
1074 {
1075 if (descriptorClazz.isInstance(module))
1076 {
1077 if (isPluginModuleEnabled(module.getCompleteKey()))
1078 {
1079 result.add(descriptorClazz.cast(module));
1080 }
1081 else if (log.isDebugEnabled())
1082 {
1083 log.debug("Module [" + module.getCompleteKey() + "] is disabled.");
1084 }
1085 }
1086 }
1087 }
1088
1089 return result;
1090 }
1091
1092 public <D extends ModuleDescriptor<?>> List<D> getEnabledModuleDescriptorsByClass(final Class<D> descriptorClazz, final boolean verbose)
1093 {
1094 return getEnabledModuleDescriptorsByClass(descriptorClazz);
1095 }
1096
1097
1098
1099
1100
1101
1102
1103 @Deprecated
1104 public <M> List<ModuleDescriptor<M>> getEnabledModuleDescriptorsByType(final String type) throws PluginParseException, IllegalArgumentException
1105 {
1106 final ModuleDescriptorOfTypePredicate<M> ofType = new ModuleDescriptorOfTypePredicate<M>(moduleDescriptorFactory, type);
1107 final EnabledModulePredicate<M> enabled = new EnabledModulePredicate<M>(this);
1108 return toList(getModuleDescriptors(getEnabledPlugins(), new ModuleDescriptorPredicate<M>()
1109 {
1110 public boolean matches(final ModuleDescriptor<? extends M> moduleDescriptor)
1111 {
1112 return ofType.matches(moduleDescriptor) && enabled.matches(moduleDescriptor);
1113 }
1114 }));
1115 }
1116
1117
1118
1119
1120
1121
1122
1123 private static <M> Iterable<ModuleDescriptor<M>> filterDescriptors(final Iterable<ModuleDescriptor<M>> descriptors, final ModuleDescriptorPredicate<M> predicate)
1124 {
1125 return filter(descriptors, new Predicate<ModuleDescriptor<M>>()
1126 {
1127 public boolean apply(final ModuleDescriptor<M> input)
1128 {
1129 return predicate.matches(input);
1130 }
1131 });
1132 }
1133
1134
1135
1136
1137
1138
1139
1140
1141 public void enablePlugins(final String... keys)
1142 {
1143 final Collection<Plugin> pluginsToEnable = new ArrayList<Plugin>(keys.length);
1144
1145 for (final String key : keys)
1146 {
1147 if (key == null)
1148 {
1149 throw new IllegalArgumentException("Keys passed to enablePlugins must be non-null");
1150 }
1151
1152 final Plugin plugin = plugins.get(key);
1153 if (plugin == null)
1154 {
1155 if (log.isInfoEnabled())
1156 {
1157 log.info("No plugin was found for key '" + key + "'. Not enabling.");
1158 }
1159 continue;
1160 }
1161
1162 if (!plugin.getPluginInformation().satisfiesMinJavaVersion())
1163 {
1164 log.error("Minimum Java version of '" + plugin.getPluginInformation().getMinJavaVersion() + "' was not satisfied for module '" + key + "'. Not enabling.");
1165 continue;
1166 }
1167 pluginsToEnable.add(plugin);
1168 }
1169 final Collection<Plugin> enabledPlugins = pluginEnabler.enableAllRecursively(pluginsToEnable);
1170
1171 for (final Plugin plugin : enabledPlugins)
1172 {
1173 enablePluginState(plugin, getStore());
1174 notifyPluginEnabled(plugin);
1175 }
1176 }
1177
1178
1179
1180
1181 @Deprecated
1182 public void enablePlugin(final String key)
1183 {
1184 enablePlugins(key);
1185 }
1186
1187 protected void enablePluginState(final Plugin plugin, final PluginPersistentStateStore stateStore)
1188 {
1189 stateStore.save(getBuilder().setEnabled(plugin, true).toState());
1190 }
1191
1192
1193
1194
1195
1196
1197
1198
1199 protected void notifyPluginEnabled(final Plugin plugin)
1200 {
1201 plugin.enable();
1202 if (enableConfiguredPluginModules(plugin))
1203 {
1204 pluginEventManager.broadcast(new PluginEnabledEvent(plugin));
1205 }
1206 }
1207
1208
1209
1210
1211
1212
1213
1214
1215
1216
1217
1218 private boolean enableConfiguredPluginModules(final Plugin plugin)
1219 {
1220 boolean success = true;
1221 final Set<ModuleDescriptor<?>> enabledDescriptors = new HashSet<ModuleDescriptor<?>>();
1222 for (final ModuleDescriptor<?> descriptor : plugin.getModuleDescriptors())
1223 {
1224 if (!enableConfiguredPluginModule(plugin, descriptor, enabledDescriptors))
1225 {
1226 success = false;
1227 break;
1228 }
1229 }
1230 return success;
1231 }
1232
1233 private boolean enableConfiguredPluginModule(final Plugin plugin, final ModuleDescriptor<?> descriptor, final Set<ModuleDescriptor<?>> enabledDescriptors)
1234 {
1235 boolean success = true;
1236
1237
1238 if (pluginEnabler.isPluginBeingEnabled(plugin))
1239 {
1240 log.debug("The plugin is currently being enabled, so we won't bother trying to enable the '" + descriptor.getKey() + " module");
1241 return success;
1242 }
1243
1244
1245
1246 if (!isPluginModuleEnabled(descriptor.getCompleteKey()))
1247 {
1248 if (log.isDebugEnabled())
1249 {
1250 String name = descriptor.getName() == null ? descriptor.getKey() : descriptor.getName();
1251 log.debug("Plugin module '" + name + "' is explicitly disabled (or so by default), so not re-enabling.");
1252 }
1253 return success;
1254 }
1255
1256 try
1257 {
1258 notifyModuleEnabled(descriptor);
1259 enabledDescriptors.add(descriptor);
1260 }
1261 catch (final Throwable exception)
1262 {
1263
1264 log.error("There was an error loading the descriptor '" + descriptor.getName() + "' of plugin '" + plugin.getKey() + "'. Disabling.",
1265 exception);
1266
1267
1268 for (final ModuleDescriptor<?> desc : enabledDescriptors)
1269 {
1270 notifyModuleDisabled(desc);
1271 }
1272
1273 replacePluginWithUnloadablePlugin(plugin, descriptor, exception);
1274 success = false;
1275 }
1276
1277 return success;
1278 }
1279
1280 public void disablePlugin(final String key)
1281 {
1282 disablePluginInternal(key, true);
1283 }
1284
1285 public void disablePluginWithoutPersisting(final String key)
1286 {
1287 disablePluginInternal(key, false);
1288 }
1289
1290 protected void disablePluginInternal(final String key, final boolean persistDisabledState)
1291 {
1292 if (key == null)
1293 {
1294 throw new IllegalArgumentException("You must specify a plugin key to disable.");
1295 }
1296
1297 final Plugin plugin = plugins.get(key);
1298 if (plugin == null)
1299 {
1300 if (log.isInfoEnabled())
1301 {
1302 log.info("No plugin was found for key '" + key + "'. Not disabling.");
1303 }
1304 return;
1305 }
1306
1307 notifyPluginDisabled(plugin);
1308 if (persistDisabledState)
1309 {
1310 disablePluginState(plugin, getStore());
1311 }
1312 }
1313
1314 protected void disablePluginState(final Plugin plugin, final PluginPersistentStateStore stateStore)
1315 {
1316 stateStore.save(getBuilder().setEnabled(plugin, false).toState());
1317 }
1318
1319 protected void notifyPluginDisabled(final Plugin plugin)
1320 {
1321 if (log.isInfoEnabled())
1322 {
1323 log.info("Disabling " + plugin.getKey());
1324 }
1325 disablePluginModules(plugin);
1326
1327
1328 plugin.disable();
1329 pluginEventManager.broadcast(new PluginDisabledEvent(plugin));
1330 }
1331
1332 private void disablePluginModules(final Plugin plugin)
1333 {
1334 final List<ModuleDescriptor<?>> moduleDescriptors = new ArrayList<ModuleDescriptor<?>>(plugin.getModuleDescriptors());
1335 Collections.reverse(moduleDescriptors);
1336
1337 for (final ModuleDescriptor<?> module : moduleDescriptors)
1338 {
1339
1340
1341
1342
1343
1344 disablePluginModuleNoPersist(module);
1345 }
1346 }
1347
1348 private void disablePluginModuleNoPersist(final ModuleDescriptor<?> module) {
1349 if (isPluginModuleEnabled(module.getCompleteKey()))
1350 {
1351 publishModuleDisabledEvents(module, false);
1352 }
1353 }
1354
1355 public void disablePluginModule(final String completeKey)
1356 {
1357 if (completeKey == null)
1358 {
1359 throw new IllegalArgumentException("You must specify a plugin module key to disable.");
1360 }
1361
1362 final ModuleDescriptor<?> module = getPluginModule(completeKey);
1363
1364 if (module == null)
1365 {
1366 if (log.isInfoEnabled())
1367 {
1368 log.info("Returned module for key '" + completeKey + "' was null. Not disabling.");
1369 }
1370 return;
1371 }
1372 if (module.getClass().isAnnotationPresent(CannotDisable.class))
1373 {
1374 if (log.isInfoEnabled())
1375 {
1376 log.info("Plugin module " + completeKey + " cannot be disabled; it is annotated with" + CannotDisable.class.getName());
1377 }
1378 return;
1379 }
1380
1381 disablePluginModuleState(module, getStore());
1382 notifyModuleDisabled(module);
1383 }
1384
1385 protected void disablePluginModuleState(final ModuleDescriptor<?> module, final PluginPersistentStateStore stateStore)
1386 {
1387 stateStore.save(getBuilder().setEnabled(module, false).toState());
1388 }
1389
1390 protected void notifyModuleDisabled(final ModuleDescriptor<?> module)
1391 {
1392 publishModuleDisabledEvents(module, true);
1393 }
1394
1395 private void publishModuleDisabledEvents(final ModuleDescriptor<?> module, final boolean persistent)
1396 {
1397 if (log.isDebugEnabled())
1398 {
1399 log.debug("Disabling " + module.getKey());
1400 }
1401
1402 if (module instanceof StateAware)
1403 {
1404 ((StateAware) module).disabled();
1405 }
1406
1407 pluginEventManager.broadcast(new PluginModuleDisabledEvent(module, persistent));
1408 }
1409
1410 public void enablePluginModule(final String completeKey)
1411 {
1412 if (completeKey == null)
1413 {
1414 throw new IllegalArgumentException("You must specify a plugin module key to disable.");
1415 }
1416
1417 final ModuleDescriptor<?> module = getPluginModule(completeKey);
1418
1419 if (module == null)
1420 {
1421 if (log.isInfoEnabled())
1422 {
1423 log.info("Returned module for key '" + completeKey + "' was null. Not enabling.");
1424 }
1425
1426 return;
1427 }
1428
1429 if (!module.satisfiesMinJavaVersion())
1430 {
1431 log.error("Minimum Java version of '" + module.getMinJavaVersion() + "' was not satisfied for module '" + completeKey + "'. Not enabling.");
1432 return;
1433 }
1434 enablePluginModuleState(module, getStore());
1435 notifyModuleEnabled(module);
1436 }
1437
1438 protected void enablePluginModuleState(final ModuleDescriptor<?> module, final PluginPersistentStateStore stateStore)
1439 {
1440 stateStore.save(getBuilder().setEnabled(module, true).toState());
1441 }
1442
1443 protected void notifyModuleEnabled(final ModuleDescriptor<?> module)
1444 {
1445 if (log.isDebugEnabled())
1446 {
1447 log.debug("Enabling " + module.getKey());
1448 }
1449 if (module instanceof StateAware)
1450 {
1451 ((StateAware) module).enabled();
1452 }
1453 pluginEventManager.broadcast(new PluginModuleEnabledEvent(module));
1454 }
1455
1456 public boolean isPluginModuleEnabled(final String completeKey)
1457 {
1458
1459 return (completeKey == null) ? false : isPluginModuleEnabled(new ModuleCompleteKey(completeKey));
1460 }
1461
1462 private boolean isPluginModuleEnabled(final ModuleCompleteKey key)
1463 {
1464 if (!isPluginEnabled(key.getPluginKey()))
1465 {
1466 return false;
1467 }
1468 final ModuleDescriptor<?> pluginModule = getPluginModule(key);
1469 return (pluginModule != null) && getState().isEnabled(pluginModule);
1470 }
1471
1472
1473
1474
1475
1476
1477
1478
1479 public boolean isPluginEnabled(final String key)
1480 {
1481 final Plugin plugin = plugins.get(notNull("The plugin key must be specified", key));
1482
1483 return plugin != null &&
1484 plugin.getPluginState() == PluginState.ENABLED &&
1485 getState().isEnabled(plugin) &&
1486 !pluginEnabler.isPluginBeingEnabled(plugin);
1487 }
1488
1489 public InputStream getDynamicResourceAsStream(final String name)
1490 {
1491 return getClassLoader().getResourceAsStream(name);
1492 }
1493
1494 public Class<?> getDynamicPluginClass(final String className) throws ClassNotFoundException
1495 {
1496 return getClassLoader().loadClass(className);
1497 }
1498
1499 public PluginsClassLoader getClassLoader()
1500 {
1501 return classLoader;
1502 }
1503
1504 public InputStream getPluginResourceAsStream(final String pluginKey, final String resourcePath)
1505 {
1506 final Plugin plugin = getEnabledPlugin(pluginKey);
1507 if (plugin == null)
1508 {
1509 log.error("Attempted to retreive resource " + resourcePath + " for non-existent or inactive plugin " + pluginKey);
1510 return null;
1511 }
1512
1513 return plugin.getResourceAsStream(resourcePath);
1514 }
1515
1516
1517
1518
1519
1520
1521
1522
1523
1524 private UnloadablePlugin replacePluginWithUnloadablePlugin(final Plugin plugin, final ModuleDescriptor<?> descriptor, final Throwable throwable)
1525 {
1526 final UnloadableModuleDescriptor unloadableDescriptor = UnloadableModuleDescriptorFactory.createUnloadableModuleDescriptor(plugin,
1527 descriptor, throwable);
1528 final UnloadablePlugin unloadablePlugin = UnloadablePluginFactory.createUnloadablePlugin(plugin, unloadableDescriptor);
1529
1530
1531
1532 unloadablePlugin.setErrorText(unloadableDescriptor.getErrorText());
1533 plugins.put(plugin.getKey(), unloadablePlugin);
1534
1535
1536
1537
1538
1539 return unloadablePlugin;
1540 }
1541
1542 public boolean isSystemPlugin(final String key)
1543 {
1544 final Plugin plugin = getPlugin(key);
1545 return (plugin != null) && plugin.isSystemPlugin();
1546 }
1547
1548 public PluginRestartState getPluginRestartState(final String key)
1549 {
1550 return getState().getPluginRestartState(key);
1551 }
1552
1553 private Builder getBuilder()
1554 {
1555 return PluginPersistentState.Builder.create(getStore().load());
1556 }
1557
1558
1559
1560
1561 @Deprecated
1562 public void setDescriptorParserFactory(final DescriptorParserFactory descriptorParserFactory)
1563 {}
1564 }