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(getPlugins(), 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> Iterable<M> getModules(final Iterable<ModuleDescriptor<M>> moduleDescriptors)
921 {
922 return transform(moduleDescriptors, new Function<ModuleDescriptor<M>, M>()
923 {
924 private final Set<String> disabled = new HashSet<String>();
925 public M apply(final ModuleDescriptor<M> input)
926 {
927 try
928 {
929 return input.getModule();
930 }
931 catch (final RuntimeException ex)
932 {
933 final String key = input.getPlugin().getKey();
934 log.error("Exception when retrieving plugin module " + input.getKey(), ex);
935 if (disabled.contains(key))
936 return null;
937
938 log.error("Disabling plugin" + key);
939 disabled.add(key);
940 disablePlugin(key);
941 return null;
942 }
943 }
944 });
945 }
946
947 public Plugin getPlugin(final String key)
948 {
949 return plugins.get(notNull("The plugin key must be specified", key));
950 }
951
952 public Plugin getEnabledPlugin(final String pluginKey)
953 {
954 if (!isPluginEnabled(pluginKey))
955 {
956 return null;
957 }
958 return getPlugin(pluginKey);
959 }
960
961 public ModuleDescriptor<?> getPluginModule(final String completeKey)
962 {
963 return getPluginModule(new ModuleCompleteKey(completeKey));
964 }
965
966 private ModuleDescriptor<?> getPluginModule(final ModuleCompleteKey key)
967 {
968 final Plugin plugin = getPlugin(key.getPluginKey());
969 if (plugin == null)
970 {
971 return null;
972 }
973 return plugin.getModuleDescriptor(key.getModuleKey());
974 }
975
976 public ModuleDescriptor<?> getEnabledPluginModule(final String completeKey)
977 {
978 final ModuleCompleteKey key = new ModuleCompleteKey(completeKey);
979
980
981 if (!isPluginModuleEnabled(key))
982 {
983 return null;
984 }
985
986 return getEnabledPlugin(key.getPluginKey()).getModuleDescriptor(key.getModuleKey());
987 }
988
989
990
991
992 public <M> List<M> getEnabledModulesByClass(final Class<M> moduleClass)
993 {
994 return toList(getModules(getEnabledModuleDescriptorsByModuleClass(moduleClass)));
995 }
996
997
998
999
1000
1001
1002
1003 @Deprecated
1004 public <M> List<M> getEnabledModulesByClassAndDescriptor(final Class<ModuleDescriptor<M>>[] descriptorClasses, final Class<M> moduleClass)
1005 {
1006 final Iterable<ModuleDescriptor<M>> moduleDescriptors = filterDescriptors(getEnabledModuleDescriptorsByModuleClass(moduleClass), new ModuleDescriptorOfClassPredicate<M>(descriptorClasses));
1007
1008 return toList(getModules(moduleDescriptors));
1009 }
1010
1011
1012
1013
1014
1015
1016
1017 @Deprecated
1018 public <M> List<M> getEnabledModulesByClassAndDescriptor(final Class<ModuleDescriptor<M>> descriptorClass, final Class<M> moduleClass)
1019 {
1020 final Iterable<ModuleDescriptor<M>> moduleDescriptors = getEnabledModuleDescriptorsByModuleClass(moduleClass);
1021 return toList(getModules(filterDescriptors(moduleDescriptors, new ModuleDescriptorOfClassPredicate<M>(
1022 descriptorClass))));
1023 }
1024
1025
1026
1027
1028
1029
1030
1031
1032 private <M> Collection<ModuleDescriptor<M>> getEnabledModuleDescriptorsByModuleClass(final Class<M> moduleClass)
1033 {
1034 final ModuleOfClassPredicate<M> ofType = new ModuleOfClassPredicate<M>(moduleClass);
1035 final EnabledModulePredicate<M> enabled = new EnabledModulePredicate<M>(this);
1036 return toList(getModuleDescriptors(getEnabledPlugins(), new ModuleDescriptorPredicate<M>()
1037 {
1038 public boolean matches(final ModuleDescriptor<? extends M> moduleDescriptor)
1039 {
1040 return ofType.matches(moduleDescriptor) && enabled.matches(moduleDescriptor);
1041 }
1042 }));
1043 }
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053 public <D extends ModuleDescriptor<?>> List<D> getEnabledModuleDescriptorsByClass(final Class<D> descriptorClazz)
1054 {
1055 final List<D> result = new LinkedList<D>();
1056 for (final Plugin plugin : plugins.values())
1057 {
1058
1059 if (!isPluginEnabled(plugin.getKey()))
1060 {
1061 if (log.isDebugEnabled())
1062 {
1063 log.debug("Plugin [" + plugin.getKey() + "] is disabled.");
1064 }
1065 continue;
1066 }
1067
1068 for (final ModuleDescriptor<?> module : plugin.getModuleDescriptors())
1069 {
1070 if (descriptorClazz.isInstance(module))
1071 {
1072 if (isPluginModuleEnabled(module.getCompleteKey()))
1073 {
1074 result.add(descriptorClazz.cast(module));
1075 }
1076 else if (log.isDebugEnabled())
1077 {
1078 log.debug("Module [" + module.getCompleteKey() + "] is disabled.");
1079 }
1080 }
1081 }
1082 }
1083
1084 return result;
1085 }
1086
1087 public <D extends ModuleDescriptor<?>> List<D> getEnabledModuleDescriptorsByClass(final Class<D> descriptorClazz, final boolean verbose)
1088 {
1089 return getEnabledModuleDescriptorsByClass(descriptorClazz);
1090 }
1091
1092
1093
1094
1095
1096
1097
1098 @Deprecated
1099 public <M> List<ModuleDescriptor<M>> getEnabledModuleDescriptorsByType(final String type) throws PluginParseException, IllegalArgumentException
1100 {
1101 final ModuleDescriptorOfTypePredicate<M> ofType = new ModuleDescriptorOfTypePredicate<M>(moduleDescriptorFactory, type);
1102 final EnabledModulePredicate<M> enabled = new EnabledModulePredicate<M>(this);
1103 return toList(getModuleDescriptors(getEnabledPlugins(), new ModuleDescriptorPredicate<M>()
1104 {
1105 public boolean matches(final ModuleDescriptor<? extends M> moduleDescriptor)
1106 {
1107 return ofType.matches(moduleDescriptor) && enabled.matches(moduleDescriptor);
1108 }
1109 }));
1110 }
1111
1112
1113
1114
1115
1116
1117
1118 private static <M> Iterable<ModuleDescriptor<M>> filterDescriptors(final Iterable<ModuleDescriptor<M>> descriptors, final ModuleDescriptorPredicate<M> predicate)
1119 {
1120 return filter(descriptors, new Predicate<ModuleDescriptor<M>>()
1121 {
1122 public boolean apply(final ModuleDescriptor<M> input)
1123 {
1124 return predicate.matches(input);
1125 }
1126 });
1127 }
1128
1129
1130
1131
1132
1133
1134
1135
1136 public void enablePlugins(final String... keys)
1137 {
1138 final Collection<Plugin> pluginsToEnable = new ArrayList<Plugin>(keys.length);
1139
1140 for (final String key : keys)
1141 {
1142 if (key == null)
1143 {
1144 throw new IllegalArgumentException("Keys passed to enablePlugins must be non-null");
1145 }
1146
1147 final Plugin plugin = plugins.get(key);
1148 if (plugin == null)
1149 {
1150 if (log.isInfoEnabled())
1151 {
1152 log.info("No plugin was found for key '" + key + "'. Not enabling.");
1153 }
1154 continue;
1155 }
1156
1157 if (!plugin.getPluginInformation().satisfiesMinJavaVersion())
1158 {
1159 log.error("Minimum Java version of '" + plugin.getPluginInformation().getMinJavaVersion() + "' was not satisfied for module '" + key + "'. Not enabling.");
1160 continue;
1161 }
1162 pluginsToEnable.add(plugin);
1163 }
1164 final Collection<Plugin> enabledPlugins = pluginEnabler.enableAllRecursively(pluginsToEnable);
1165
1166 for (final Plugin plugin : enabledPlugins)
1167 {
1168 enablePluginState(plugin, getStore());
1169 notifyPluginEnabled(plugin);
1170 }
1171 }
1172
1173
1174
1175
1176 @Deprecated
1177 public void enablePlugin(final String key)
1178 {
1179 enablePlugins(key);
1180 }
1181
1182 protected void enablePluginState(final Plugin plugin, final PluginPersistentStateStore stateStore)
1183 {
1184 stateStore.save(getBuilder().setEnabled(plugin, true).toState());
1185 }
1186
1187
1188
1189
1190
1191
1192
1193
1194 protected void notifyPluginEnabled(final Plugin plugin)
1195 {
1196 plugin.enable();
1197 if (enableConfiguredPluginModules(plugin))
1198 {
1199 pluginEventManager.broadcast(new PluginEnabledEvent(plugin));
1200 }
1201 }
1202
1203
1204
1205
1206
1207
1208
1209
1210
1211
1212
1213 private boolean enableConfiguredPluginModules(final Plugin plugin)
1214 {
1215 boolean success = true;
1216 final Set<ModuleDescriptor<?>> enabledDescriptors = new HashSet<ModuleDescriptor<?>>();
1217 for (final ModuleDescriptor<?> descriptor : plugin.getModuleDescriptors())
1218 {
1219 if (!enableConfiguredPluginModule(plugin, descriptor, enabledDescriptors))
1220 {
1221 success = false;
1222 break;
1223 }
1224 }
1225 return success;
1226 }
1227
1228 private boolean enableConfiguredPluginModule(final Plugin plugin, final ModuleDescriptor<?> descriptor, final Set<ModuleDescriptor<?>> enabledDescriptors)
1229 {
1230 boolean success = true;
1231
1232
1233 if (pluginEnabler.isPluginBeingEnabled(plugin))
1234 {
1235 log.debug("The plugin is currently being enabled, so we won't bother trying to enable the '" + descriptor.getKey() + " module");
1236 return success;
1237 }
1238
1239
1240
1241 if (!isPluginModuleEnabled(descriptor.getCompleteKey()))
1242 {
1243 if (log.isDebugEnabled())
1244 {
1245 String name = descriptor.getName() == null ? descriptor.getKey() : descriptor.getName();
1246 log.debug("Plugin module '" + name + "' is explicitly disabled (or so by default), so not re-enabling.");
1247 }
1248 return success;
1249 }
1250
1251 try
1252 {
1253 notifyModuleEnabled(descriptor);
1254 enabledDescriptors.add(descriptor);
1255 }
1256 catch (final Throwable exception)
1257 {
1258
1259 log.error("There was an error loading the descriptor '" + descriptor.getName() + "' of plugin '" + plugin.getKey() + "'. Disabling.",
1260 exception);
1261
1262
1263 for (final ModuleDescriptor<?> desc : enabledDescriptors)
1264 {
1265 notifyModuleDisabled(desc);
1266 }
1267
1268 replacePluginWithUnloadablePlugin(plugin, descriptor, exception);
1269 success = false;
1270 }
1271
1272 return success;
1273 }
1274
1275 public void disablePlugin(final String key)
1276 {
1277 disablePluginInternal(key, true);
1278 }
1279
1280 public void disablePluginWithoutPersisting(final String key)
1281 {
1282 disablePluginInternal(key, false);
1283 }
1284
1285 protected void disablePluginInternal(final String key, final boolean persistDisabledState)
1286 {
1287 if (key == null)
1288 {
1289 throw new IllegalArgumentException("You must specify a plugin key to disable.");
1290 }
1291
1292 final Plugin plugin = plugins.get(key);
1293 if (plugin == null)
1294 {
1295 if (log.isInfoEnabled())
1296 {
1297 log.info("No plugin was found for key '" + key + "'. Not disabling.");
1298 }
1299 return;
1300 }
1301
1302 notifyPluginDisabled(plugin);
1303 if (persistDisabledState)
1304 {
1305 disablePluginState(plugin, getStore());
1306 }
1307 }
1308
1309 protected void disablePluginState(final Plugin plugin, final PluginPersistentStateStore stateStore)
1310 {
1311 stateStore.save(getBuilder().setEnabled(plugin, false).toState());
1312 }
1313
1314 protected void notifyPluginDisabled(final Plugin plugin)
1315 {
1316 if (log.isInfoEnabled())
1317 {
1318 log.info("Disabling " + plugin.getKey());
1319 }
1320 disablePluginModules(plugin);
1321
1322
1323 plugin.disable();
1324 pluginEventManager.broadcast(new PluginDisabledEvent(plugin));
1325 }
1326
1327 private void disablePluginModules(final Plugin plugin)
1328 {
1329 final List<ModuleDescriptor<?>> moduleDescriptors = new ArrayList<ModuleDescriptor<?>>(plugin.getModuleDescriptors());
1330 Collections.reverse(moduleDescriptors);
1331
1332 for (final ModuleDescriptor<?> module : moduleDescriptors)
1333 {
1334
1335
1336
1337
1338
1339 disablePluginModuleNoPersist(module);
1340 }
1341 }
1342
1343 private void disablePluginModuleNoPersist(final ModuleDescriptor<?> module) {
1344 if (isPluginModuleEnabled(module.getCompleteKey()))
1345 {
1346 publishModuleDisabledEvents(module, false);
1347 }
1348 }
1349
1350 public void disablePluginModule(final String completeKey)
1351 {
1352 if (completeKey == null)
1353 {
1354 throw new IllegalArgumentException("You must specify a plugin module key to disable.");
1355 }
1356
1357 final ModuleDescriptor<?> module = getPluginModule(completeKey);
1358
1359 if (module == null)
1360 {
1361 if (log.isInfoEnabled())
1362 {
1363 log.info("Returned module for key '" + completeKey + "' was null. Not disabling.");
1364 }
1365 return;
1366 }
1367 if (module.getClass().isAnnotationPresent(CannotDisable.class))
1368 {
1369 if (log.isInfoEnabled())
1370 {
1371 log.info("Plugin module " + completeKey + " cannot be disabled; it is annotated with" + CannotDisable.class.getName());
1372 }
1373 return;
1374 }
1375
1376 disablePluginModuleState(module, getStore());
1377 notifyModuleDisabled(module);
1378 }
1379
1380 protected void disablePluginModuleState(final ModuleDescriptor<?> module, final PluginPersistentStateStore stateStore)
1381 {
1382 stateStore.save(getBuilder().setEnabled(module, false).toState());
1383 }
1384
1385 protected void notifyModuleDisabled(final ModuleDescriptor<?> module)
1386 {
1387 publishModuleDisabledEvents(module, true);
1388 }
1389
1390 private void publishModuleDisabledEvents(final ModuleDescriptor<?> module, final boolean persistent)
1391 {
1392 if (log.isDebugEnabled())
1393 {
1394 log.debug("Disabling " + module.getKey());
1395 }
1396
1397 if (module instanceof StateAware)
1398 {
1399 ((StateAware) module).disabled();
1400 }
1401
1402 pluginEventManager.broadcast(new PluginModuleDisabledEvent(module, persistent));
1403 }
1404
1405 public void enablePluginModule(final String completeKey)
1406 {
1407 if (completeKey == null)
1408 {
1409 throw new IllegalArgumentException("You must specify a plugin module key to disable.");
1410 }
1411
1412 final ModuleDescriptor<?> module = getPluginModule(completeKey);
1413
1414 if (module == null)
1415 {
1416 if (log.isInfoEnabled())
1417 {
1418 log.info("Returned module for key '" + completeKey + "' was null. Not enabling.");
1419 }
1420
1421 return;
1422 }
1423
1424 if (!module.satisfiesMinJavaVersion())
1425 {
1426 log.error("Minimum Java version of '" + module.getMinJavaVersion() + "' was not satisfied for module '" + completeKey + "'. Not enabling.");
1427 return;
1428 }
1429 enablePluginModuleState(module, getStore());
1430 notifyModuleEnabled(module);
1431 }
1432
1433 protected void enablePluginModuleState(final ModuleDescriptor<?> module, final PluginPersistentStateStore stateStore)
1434 {
1435 stateStore.save(getBuilder().setEnabled(module, true).toState());
1436 }
1437
1438 protected void notifyModuleEnabled(final ModuleDescriptor<?> module)
1439 {
1440 if (log.isDebugEnabled())
1441 {
1442 log.debug("Enabling " + module.getKey());
1443 }
1444 if (module instanceof StateAware)
1445 {
1446 ((StateAware) module).enabled();
1447 }
1448 pluginEventManager.broadcast(new PluginModuleEnabledEvent(module));
1449 }
1450
1451 public boolean isPluginModuleEnabled(final String completeKey)
1452 {
1453
1454 return (completeKey == null) ? false : isPluginModuleEnabled(new ModuleCompleteKey(completeKey));
1455 }
1456
1457 private boolean isPluginModuleEnabled(final ModuleCompleteKey key)
1458 {
1459 if (!isPluginEnabled(key.getPluginKey()))
1460 {
1461 return false;
1462 }
1463 final ModuleDescriptor<?> pluginModule = getPluginModule(key);
1464 return (pluginModule != null) && getState().isEnabled(pluginModule);
1465 }
1466
1467
1468
1469
1470
1471
1472
1473
1474 public boolean isPluginEnabled(final String key)
1475 {
1476 final Plugin plugin = plugins.get(notNull("The plugin key must be specified", key));
1477
1478 return plugin != null &&
1479 plugin.getPluginState() == PluginState.ENABLED &&
1480 getState().isEnabled(plugin) &&
1481 !pluginEnabler.isPluginBeingEnabled(plugin);
1482 }
1483
1484 public InputStream getDynamicResourceAsStream(final String name)
1485 {
1486 return getClassLoader().getResourceAsStream(name);
1487 }
1488
1489 public Class<?> getDynamicPluginClass(final String className) throws ClassNotFoundException
1490 {
1491 return getClassLoader().loadClass(className);
1492 }
1493
1494 public PluginsClassLoader getClassLoader()
1495 {
1496 return classLoader;
1497 }
1498
1499 public InputStream getPluginResourceAsStream(final String pluginKey, final String resourcePath)
1500 {
1501 final Plugin plugin = getEnabledPlugin(pluginKey);
1502 if (plugin == null)
1503 {
1504 log.error("Attempted to retreive resource " + resourcePath + " for non-existent or inactive plugin " + pluginKey);
1505 return null;
1506 }
1507
1508 return plugin.getResourceAsStream(resourcePath);
1509 }
1510
1511
1512
1513
1514
1515
1516
1517
1518
1519 private UnloadablePlugin replacePluginWithUnloadablePlugin(final Plugin plugin, final ModuleDescriptor<?> descriptor, final Throwable throwable)
1520 {
1521 final UnloadableModuleDescriptor unloadableDescriptor = UnloadableModuleDescriptorFactory.createUnloadableModuleDescriptor(plugin,
1522 descriptor, throwable);
1523 final UnloadablePlugin unloadablePlugin = UnloadablePluginFactory.createUnloadablePlugin(plugin, unloadableDescriptor);
1524
1525
1526
1527 unloadablePlugin.setErrorText(unloadableDescriptor.getErrorText());
1528 plugins.put(plugin.getKey(), unloadablePlugin);
1529
1530
1531
1532
1533
1534 return unloadablePlugin;
1535 }
1536
1537 public boolean isSystemPlugin(final String key)
1538 {
1539 final Plugin plugin = getPlugin(key);
1540 return (plugin != null) && plugin.isSystemPlugin();
1541 }
1542
1543 public PluginRestartState getPluginRestartState(final String key)
1544 {
1545 return getState().getPluginRestartState(key);
1546 }
1547
1548 private Builder getBuilder()
1549 {
1550 return PluginPersistentState.Builder.create(getStore().load());
1551 }
1552
1553
1554
1555
1556 @Deprecated
1557 public void setDescriptorParserFactory(final DescriptorParserFactory descriptorParserFactory)
1558 {}
1559 }