View Javadoc
1   package com.atlassian.plugin.manager;
2   
3   import com.atlassian.plugin.DefaultModuleDescriptorFactory;
4   import com.atlassian.plugin.ModuleDescriptor;
5   import com.atlassian.plugin.ModuleDescriptorFactory;
6   import com.atlassian.plugin.Permissions;
7   import com.atlassian.plugin.Plugin;
8   import com.atlassian.plugin.PluginArtifact;
9   import com.atlassian.plugin.PluginController;
10  import com.atlassian.plugin.PluginDependencies;
11  import com.atlassian.plugin.PluginException;
12  import com.atlassian.plugin.PluginInstaller;
13  import com.atlassian.plugin.PluginInternal;
14  import com.atlassian.plugin.PluginParseException;
15  import com.atlassian.plugin.PluginRestartState;
16  import com.atlassian.plugin.PluginState;
17  import com.atlassian.plugin.StateAware;
18  import com.atlassian.plugin.event.NotificationException;
19  import com.atlassian.plugin.event.PluginEventManager;
20  import com.atlassian.plugin.event.events.BeforePluginDisabledEvent;
21  import com.atlassian.plugin.event.events.BeforePluginModuleDisabledEvent;
22  import com.atlassian.plugin.event.events.PluginDependentsChangedEvent;
23  import com.atlassian.plugin.event.events.PluginDisabledEvent;
24  import com.atlassian.plugin.event.events.PluginDisablingEvent;
25  import com.atlassian.plugin.event.events.PluginEnabledEvent;
26  import com.atlassian.plugin.event.events.PluginEnablingEvent;
27  import com.atlassian.plugin.event.events.PluginEvent;
28  import com.atlassian.plugin.event.events.PluginFrameworkShutdownEvent;
29  import com.atlassian.plugin.event.events.PluginFrameworkShuttingDownEvent;
30  import com.atlassian.plugin.event.events.PluginInstalledEvent;
31  import com.atlassian.plugin.event.events.PluginInstallingEvent;
32  import com.atlassian.plugin.event.events.PluginModuleDisabledEvent;
33  import com.atlassian.plugin.event.events.PluginModuleDisablingEvent;
34  import com.atlassian.plugin.event.events.PluginModuleEnabledEvent;
35  import com.atlassian.plugin.event.events.PluginModuleEnablingEvent;
36  import com.atlassian.plugin.event.events.PluginModuleEvent;
37  import com.atlassian.plugin.event.events.PluginUninstalledEvent;
38  import com.atlassian.plugin.event.events.PluginUninstallingEvent;
39  import com.atlassian.plugin.event.events.PluginUpgradedEvent;
40  import com.atlassian.plugin.event.events.PluginUpgradingEvent;
41  import com.atlassian.plugin.event.impl.DefaultPluginEventManager;
42  import com.atlassian.plugin.event.listeners.PassListener;
43  import com.atlassian.plugin.exception.PluginExceptionInterception;
44  import com.atlassian.plugin.hostcontainer.DefaultHostContainer;
45  import com.atlassian.plugin.impl.StaticPlugin;
46  import com.atlassian.plugin.loaders.DiscardablePluginLoader;
47  import com.atlassian.plugin.loaders.DynamicPluginLoader;
48  import com.atlassian.plugin.loaders.PluginLoader;
49  import com.atlassian.plugin.manager.store.MemoryPluginPersistentStateStore;
50  import com.atlassian.plugin.parsers.DescriptorParser;
51  import com.google.common.base.Predicates;
52  import com.google.common.collect.ImmutableList;
53  import com.google.common.collect.ImmutableSet;
54  import org.dom4j.Element;
55  import org.junit.Rule;
56  import org.junit.Test;
57  import org.junit.rules.ExpectedException;
58  import org.mockito.ArgumentCaptor;
59  
60  import java.io.IOException;
61  import java.util.ArrayList;
62  import java.util.Arrays;
63  import java.util.Collection;
64  import java.util.Collections;
65  import java.util.List;
66  import java.util.stream.Collectors;
67  
68  import static com.atlassian.plugin.manager.DefaultPluginManagerMocks.anyPluginEvent;
69  import static com.atlassian.plugin.manager.DefaultPluginManagerMocks.anyPluginStateChange;
70  import static com.atlassian.plugin.manager.DefaultPluginManagerMocks.mockPluginLoaderForPlugins;
71  import static com.atlassian.plugin.manager.DefaultPluginManagerMocks.mockPluginPersistentStateStore;
72  import static com.atlassian.plugin.manager.DefaultPluginManagerMocks.mockPluginsSortOrder;
73  import static com.atlassian.plugin.manager.DefaultPluginManagerMocks.mockStateChangePlugin;
74  import static com.atlassian.plugin.manager.DefaultPluginManagerMocks.mockStateChangePluginModule;
75  import static com.atlassian.plugin.manager.DefaultPluginManagerMocks.pluginEvent;
76  import static com.atlassian.plugin.manager.DefaultPluginManagerMocks.pluginModuleEvent;
77  import static com.atlassian.plugin.manager.DefaultPluginManagerMocks.pluginModuleStateChange;
78  import static com.atlassian.plugin.manager.DefaultPluginManagerMocks.pluginStateChange;
79  import static com.google.common.collect.ImmutableList.copyOf;
80  import static com.google.common.collect.Iterables.filter;
81  import static com.google.common.collect.Iterables.get;
82  import static com.google.common.collect.Iterables.getLast;
83  import static com.google.common.collect.Iterables.indexOf;
84  import static org.hamcrest.MatcherAssert.assertThat;
85  import static org.hamcrest.Matchers.contains;
86  import static org.hamcrest.Matchers.containsInAnyOrder;
87  import static org.hamcrest.Matchers.containsString;
88  import static org.hamcrest.Matchers.empty;
89  import static org.hamcrest.Matchers.greaterThan;
90  import static org.hamcrest.Matchers.instanceOf;
91  import static org.hamcrest.Matchers.is;
92  import static org.hamcrest.Matchers.lessThan;
93  import static org.junit.Assert.assertEquals;
94  import static org.junit.Assert.assertFalse;
95  import static org.junit.Assert.assertTrue;
96  import static org.junit.Assert.fail;
97  import static org.mockito.Matchers.any;
98  import static org.mockito.Matchers.isA;
99  import static org.mockito.Mockito.RETURNS_DEEP_STUBS;
100 import static org.mockito.Mockito.doNothing;
101 import static org.mockito.Mockito.doThrow;
102 import static org.mockito.Mockito.mock;
103 import static org.mockito.Mockito.never;
104 import static org.mockito.Mockito.times;
105 import static org.mockito.Mockito.verify;
106 import static org.mockito.Mockito.when;
107 import static org.mockito.Mockito.withSettings;
108 
109 public class TestDefaultPluginManagerController {
110     @Rule
111     public ExpectedException expectedException = ExpectedException.none();
112 
113     /**
114      * the object being tested
115      */
116     private PluginController manager;
117 
118     private PluginEventManager pluginEventManager = new DefaultPluginEventManager();
119 
120     private ModuleDescriptorFactory moduleDescriptorFactory = new DefaultModuleDescriptorFactory(new DefaultHostContainer());
121 
122     private DefaultPluginManager newDefaultPluginManager(PluginLoader... pluginLoaders) {
123         DefaultPluginManager dpm = DefaultPluginManager.newBuilder().
124                 withPluginLoaders(copyOf(pluginLoaders))
125                 .withModuleDescriptorFactory(moduleDescriptorFactory)
126                 .withPluginEventManager(pluginEventManager)
127                 .withStore(new MemoryPluginPersistentStateStore())
128                 .withVerifyRequiredPlugins(true)
129                 .build();
130         manager = dpm;
131         return dpm;
132     }
133 
134     private PluginController initNewDefaultPluginManager(PluginLoader... pluginLoaders) {
135         DefaultPluginManager dpm = newDefaultPluginManager(pluginLoaders);
136         dpm.init();
137         return dpm;
138     }
139 
140     @Test
141     public void pluginReturnedByLoadAllPluginsButNotUsedIsDiscarded() {
142         final String pluginKey = "pluginKey";
143         final PluginPersistentStateStore pluginPersistentStateStore = mock(
144                 PluginPersistentStateStore.class, RETURNS_DEEP_STUBS);
145         when(pluginPersistentStateStore.load().getPluginRestartState(pluginKey)).thenReturn(PluginRestartState.NONE);
146 
147         DiscardablePluginLoader pluginLoader = mock(DiscardablePluginLoader.class);
148         Plugin pluginV1 = mock(Plugin.class, RETURNS_DEEP_STUBS);
149         Plugin pluginV2 = mock(Plugin.class, RETURNS_DEEP_STUBS);
150         when(pluginV1.getKey()).thenReturn(pluginKey);
151         when(pluginV2.getKey()).thenReturn(pluginKey);
152         // Set up so that pluginV1 < pluginV2 so DefaultPluginManager should install only pluginV2
153         when(pluginV1.compareTo(pluginV2)).thenReturn(-1);
154         when(pluginV2.compareTo(pluginV1)).thenReturn(1);
155         when(pluginLoader.loadAllPlugins(isA(ModuleDescriptorFactory.class))).thenReturn(Arrays.asList(pluginV1, pluginV2));
156 
157         final DefaultPluginManager defaultPluginManager = new DefaultPluginManager(
158                 pluginPersistentStateStore,
159                 Arrays.asList((PluginLoader) pluginLoader),
160                 mock(ModuleDescriptorFactory.class),
161                 mock(PluginEventManager.class),
162                 mock(PluginExceptionInterception.class)
163         );
164         defaultPluginManager.init();
165         verify(pluginLoader).discardPlugin(pluginV1);
166     }
167 
168     @Test
169     public void oldPluginReturnedByLoadFoundPluginsIsDiscarded() {
170         final String pluginKey = "pluginKey";
171         final PluginPersistentStateStore pluginPersistentStateStore = mock(
172                 PluginPersistentStateStore.class, RETURNS_DEEP_STUBS);
173         when(pluginPersistentStateStore.load().getPluginRestartState(pluginKey)).thenReturn(PluginRestartState.NONE);
174 
175         DiscardablePluginLoader pluginLoader = mock(DiscardablePluginLoader.class);
176         Plugin pluginV1 = mock(Plugin.class, RETURNS_DEEP_STUBS);
177         Plugin pluginV2 = mock(Plugin.class, RETURNS_DEEP_STUBS);
178         when(pluginV1.getKey()).thenReturn(pluginKey);
179         when(pluginV2.getKey()).thenReturn(pluginKey);
180         // Set up so that pluginV1 < pluginV2 so DefaultPluginManager should install only pluginV2
181         when(pluginV1.compareTo(pluginV2)).thenReturn(-1);
182         when(pluginV2.compareTo(pluginV1)).thenReturn(1);
183         when(pluginLoader.loadAllPlugins(isA(ModuleDescriptorFactory.class))).thenReturn(Arrays.asList(pluginV2));
184         when(pluginLoader.supportsAddition()).thenReturn(true);
185         when(pluginLoader.loadFoundPlugins(isA(ModuleDescriptorFactory.class))).thenReturn(Arrays.asList(pluginV1));
186 
187         final DefaultPluginManager defaultPluginManager = new DefaultPluginManager(
188                 pluginPersistentStateStore,
189                 Arrays.asList((PluginLoader) pluginLoader),
190                 mock(ModuleDescriptorFactory.class),
191                 mock(PluginEventManager.class),
192                 mock(PluginExceptionInterception.class)
193         );
194         defaultPluginManager.init();
195         final int found = defaultPluginManager.scanForNewPlugins();
196         assertThat(found, is(1));
197         verify(pluginLoader).discardPlugin(pluginV1);
198     }
199 
200     @Test
201     public void upgradePluginDisablesDependentPlugins() {
202         final PluginPersistentStateStore pluginPersistentStateStore = mockPluginPersistentStateStore();
203 
204         final String pluginKey = "pluginKey";
205         final Plugin pluginV1 = mockStateChangePlugin(pluginKey, pluginEventManager);
206         final Plugin pluginV2 = mockStateChangePlugin(pluginKey, pluginEventManager);
207         when(pluginV1.isDeleteable()).thenReturn(true);
208 
209         final String dependentPluginKey = "dependentPluginKey";
210         final Plugin dependentPlugin = mockStateChangePlugin(dependentPluginKey, pluginEventManager);
211         when(dependentPlugin.isEnabledByDefault()).thenReturn(true);
212         when(dependentPlugin.getDependencies()).thenReturn(new PluginDependencies(ImmutableSet.of(pluginKey), null, null));
213 
214         // We need to compareTo to work for plugins that participate in the same addPlugins, so set a good global order
215         mockPluginsSortOrder(pluginV1, pluginV2, dependentPlugin);
216 
217         final DiscardablePluginLoader pluginLoader = mock(DiscardablePluginLoader.class);
218         when(pluginLoader.supportsAddition()).thenReturn(true);
219         when(pluginLoader.supportsRemoval()).thenReturn(true);
220         final List<Plugin> initialPlugins = Arrays.asList(pluginV1, dependentPlugin);
221         when(pluginLoader.loadAllPlugins(isA(ModuleDescriptorFactory.class))).thenReturn(initialPlugins);
222         when(pluginLoader.loadFoundPlugins(isA(ModuleDescriptorFactory.class))).thenReturn(Arrays.asList(pluginV2));
223 
224         final DefaultPluginManager defaultPluginManager = new DefaultPluginManager(
225                 pluginPersistentStateStore,
226                 Arrays.asList((PluginLoader) pluginLoader),
227                 mock(ModuleDescriptorFactory.class),
228                 mock(PluginEventManager.class),
229                 mock(PluginExceptionInterception.class)
230         );
231         defaultPluginManager.init();
232         verify(pluginV1).enable();
233         verify(dependentPlugin).enable();
234         when(pluginV1.getPluginState()).thenReturn(PluginState.ENABLED);
235         when(dependentPlugin.getPluginState()).thenReturn(PluginState.ENABLED);
236 
237         final int found = defaultPluginManager.scanForNewPlugins();
238         assertThat(found, is(1));
239         verify(dependentPlugin).disable();
240         verify(dependentPlugin, times(2)).enable();
241     }
242 
243     @Test
244     public void scanForNewPluginsScansAllPluginLoaders() {
245         final PluginPersistentStateStore pluginPersistentStateStore = mockPluginPersistentStateStore();
246 
247         final PluginLoader pluginLoaderAlpha = mockPluginLoaderForPlugins();
248         when(pluginLoaderAlpha.supportsAddition()).thenReturn(true);
249         final PluginLoader pluginLoaderBeta = mockPluginLoaderForPlugins();
250         when(pluginLoaderBeta.supportsAddition()).thenReturn(true);
251 
252         final DefaultPluginManager defaultPluginManager = new DefaultPluginManager(
253                 pluginPersistentStateStore,
254                 Arrays.asList(pluginLoaderAlpha, pluginLoaderBeta),
255                 mock(ModuleDescriptorFactory.class),
256                 mock(PluginEventManager.class),
257                 mock(PluginExceptionInterception.class)
258         );
259         defaultPluginManager.init();
260 
261         assertThat(defaultPluginManager.getPlugins(), empty());
262 
263         final Plugin pluginAlpha = mock(Plugin.class, RETURNS_DEEP_STUBS);
264         when(pluginAlpha.getKey()).thenReturn("alpha");
265         when(pluginLoaderAlpha.loadFoundPlugins(isA(ModuleDescriptorFactory.class))).thenReturn(Arrays.asList(pluginAlpha));
266         final Plugin pluginBeta = mock(Plugin.class, RETURNS_DEEP_STUBS);
267         when(pluginBeta.getKey()).thenReturn("beta");
268         when(pluginLoaderBeta.loadFoundPlugins(isA(ModuleDescriptorFactory.class))).thenReturn(Arrays.asList(pluginBeta));
269 
270         final int found = defaultPluginManager.scanForNewPlugins();
271 
272         assertThat(found, is(2));
273         assertThat(defaultPluginManager.getPlugins(), containsInAnyOrder(pluginAlpha, pluginBeta));
274     }
275 
276 
277     @Test
278     public void installEventSequencing() {
279         final PluginEventManager pluginEventManager = mock(PluginEventManager.class);
280 
281         final Plugin alphaPlugin = mockStateChangePlugin("alpha", pluginEventManager);
282         final Plugin betaPlugin = mockStateChangePlugin("beta", pluginEventManager);
283 
284         final PluginPersistentStateStore pluginPersistentStateStore = mockPluginPersistentStateStore();
285 
286         final PluginLoader pluginLoader = mockPluginLoaderForPlugins(alphaPlugin);
287         when(pluginLoader.supportsAddition()).thenReturn(true);
288 
289         final DefaultPluginManager defaultPluginManager = new DefaultPluginManager(
290                 pluginPersistentStateStore,
291                 ImmutableList.of(pluginLoader),
292                 mock(ModuleDescriptorFactory.class),
293                 pluginEventManager,
294                 mock(PluginExceptionInterception.class)
295         );
296 
297         final ArgumentCaptor<Object> initEvents = ArgumentCaptor.forClass(Object.class);
298         doNothing().when(pluginEventManager).broadcast(initEvents.capture());
299 
300         defaultPluginManager.init();
301 
302         assertThat(filter(initEvents.getAllValues(), PluginEvent.class), contains(
303                 pluginEvent(PluginInstallingEvent.class, alphaPlugin),
304                 pluginStateChange(alphaPlugin, PluginState.INSTALLED),
305                 pluginEvent(PluginInstalledEvent.class, alphaPlugin),
306                 pluginEvent(PluginEnablingEvent.class, alphaPlugin),
307                 pluginStateChange(alphaPlugin, PluginState.ENABLED),
308                 pluginEvent(PluginEnabledEvent.class, alphaPlugin)
309         ));
310 
311         when(pluginLoader.loadFoundPlugins(isA(ModuleDescriptorFactory.class))).thenReturn(Arrays.asList(betaPlugin));
312 
313         final ArgumentCaptor<Object> scanEvents = ArgumentCaptor.forClass(Object.class);
314         doNothing().when(pluginEventManager).broadcast(scanEvents.capture());
315 
316         final int found = defaultPluginManager.scanForNewPlugins();
317         // This is really just checking the test isn't broken
318         assertThat(found, is(1));
319 
320         assertThat(filter(scanEvents.getAllValues(), PluginEvent.class), contains(
321                 pluginEvent(PluginInstallingEvent.class, betaPlugin),
322                 pluginStateChange(betaPlugin, PluginState.INSTALLED),
323                 pluginEvent(PluginInstalledEvent.class, betaPlugin),
324                 pluginEvent(PluginEnablingEvent.class, betaPlugin),
325                 pluginStateChange(betaPlugin, PluginState.ENABLED),
326                 pluginEvent(PluginEnabledEvent.class, betaPlugin)
327         ));
328     }
329 
330 
331     @Test
332     public void upgradeEventSequencing() {
333         final PluginEventManager pluginEventManager = mock(PluginEventManager.class);
334 
335         final String pluginKey = "pluginKey";
336         final Plugin pluginV1 = mockStateChangePlugin(pluginKey, pluginEventManager);
337         final Plugin pluginV2 = mockStateChangePlugin(pluginKey, pluginEventManager);
338 
339         final Plugin dependentPlugin = mockStateChangePlugin("dependentPluginKey", pluginEventManager);
340         when(dependentPlugin.getDependencies()).thenReturn(new PluginDependencies(ImmutableSet.of(pluginKey), null, null));
341 
342         mockPluginsSortOrder(dependentPlugin, pluginV1, pluginV2);
343 
344         final PluginPersistentStateStore pluginPersistentStateStore = mockPluginPersistentStateStore();
345 
346         final PluginLoader pluginLoader = mockPluginLoaderForPlugins(pluginV1, dependentPlugin);
347         when(pluginLoader.supportsAddition()).thenReturn(true);
348         when(pluginLoader.supportsRemoval()).thenReturn(true);
349 
350         final DefaultPluginManager defaultPluginManager = new DefaultPluginManager(
351                 pluginPersistentStateStore,
352                 ImmutableList.of(pluginLoader),
353                 mock(ModuleDescriptorFactory.class),
354                 pluginEventManager,
355                 mock(PluginExceptionInterception.class)
356         );
357 
358         defaultPluginManager.init();
359 
360         when(pluginLoader.loadFoundPlugins(isA(ModuleDescriptorFactory.class))).thenReturn(ImmutableList.of(pluginV2));
361         final ArgumentCaptor<Object> events = ArgumentCaptor.forClass(Object.class);
362         doNothing().when(pluginEventManager).broadcast(events.capture());
363 
364         final int found = defaultPluginManager.scanForNewPlugins();
365         // This is really just checking the test isn't broken
366         assertThat(found, is(1));
367 
368         assertThat(filter(events.getAllValues(), PluginEvent.class), contains(
369                 pluginEvent(BeforePluginDisabledEvent.class, dependentPlugin),
370                 pluginEvent(PluginDisablingEvent.class, dependentPlugin),
371                 pluginStateChange(dependentPlugin, PluginState.DISABLED),
372                 pluginEvent(PluginDisabledEvent.class, dependentPlugin),
373                 pluginEvent(PluginUpgradingEvent.class, pluginV1),
374                 pluginEvent(BeforePluginDisabledEvent.class, pluginV1),
375                 pluginEvent(PluginDisablingEvent.class, pluginV1),
376                 pluginStateChange(pluginV1, PluginState.DISABLED),
377                 pluginEvent(PluginDisabledEvent.class, pluginV1),
378                 pluginStateChange(pluginV2, PluginState.INSTALLED),
379                 pluginEvent(PluginUpgradedEvent.class, pluginV2),
380                 pluginEvent(PluginEnablingEvent.class, pluginV2),
381                 pluginEvent(PluginEnablingEvent.class, dependentPlugin),
382                 pluginStateChange(pluginV2, PluginState.ENABLED),
383                 pluginStateChange(dependentPlugin, PluginState.ENABLED),
384                 pluginEvent(PluginEnabledEvent.class, pluginV2),
385                 pluginEvent(PluginEnabledEvent.class, dependentPlugin),
386                 pluginEvent(PluginDependentsChangedEvent.class, pluginV2)
387         ));
388     }
389 
390     @Test
391     public void uninstallEventSequencing() {
392         final PluginEventManager pluginEventManager = mock(PluginEventManager.class);
393 
394         final String pluginKey = "pluginKey";
395         final Plugin plugin = mockStateChangePlugin(pluginKey, pluginEventManager);
396 
397         final Plugin dependentPlugin = mockStateChangePlugin("dependentPluginKey", pluginEventManager);
398         when(dependentPlugin.getDependencies()).thenReturn(new PluginDependencies(ImmutableSet.of(pluginKey), null, null));
399 
400         mockPluginsSortOrder(dependentPlugin, plugin);
401 
402         final PluginPersistentStateStore pluginPersistentStateStore = mockPluginPersistentStateStore();
403 
404         final PluginLoader pluginLoader = mockPluginLoaderForPlugins(plugin, dependentPlugin);
405         when(pluginLoader.supportsRemoval()).thenReturn(true);
406 
407         final DefaultPluginManager defaultPluginManager = new DefaultPluginManager(
408                 pluginPersistentStateStore,
409                 ImmutableList.of(pluginLoader),
410                 mock(ModuleDescriptorFactory.class),
411                 pluginEventManager,
412                 mock(PluginExceptionInterception.class)
413         );
414 
415         defaultPluginManager.init();
416 
417         final ArgumentCaptor<Object> events = ArgumentCaptor.forClass(Object.class);
418         doNothing().when(pluginEventManager).broadcast(events.capture());
419 
420         defaultPluginManager.uninstall(defaultPluginManager.getPlugin(pluginKey));
421 
422         assertThat(filter(events.getAllValues(), PluginEvent.class), contains(
423                 pluginEvent(BeforePluginDisabledEvent.class, dependentPlugin),
424                 pluginEvent(PluginDisablingEvent.class, dependentPlugin),
425                 pluginStateChange(dependentPlugin, PluginState.DISABLED),
426                 pluginEvent(PluginDisabledEvent.class, dependentPlugin),
427                 pluginEvent(PluginUninstallingEvent.class, plugin),
428                 pluginEvent(BeforePluginDisabledEvent.class, plugin),
429                 pluginEvent(PluginDisablingEvent.class, plugin),
430                 pluginStateChange(plugin, PluginState.DISABLED),
431                 pluginEvent(PluginDisabledEvent.class, plugin),
432                 pluginEvent(PluginUninstalledEvent.class, plugin),
433                 pluginEvent(PluginDependentsChangedEvent.class, plugin)
434         ));
435     }
436 
437     @Test
438     public void uninstallPluginsEventSequencing() {
439         final PluginEventManager pluginEventManager = mock(PluginEventManager.class);
440 
441         final String pluginKey = "pluginKey";
442         final Plugin plugin = mockStateChangePlugin(pluginKey, pluginEventManager);
443 
444         final Plugin dependentPlugin = mockStateChangePlugin("dependentPluginKey", pluginEventManager);
445         when(dependentPlugin.getDependencies()).thenReturn(new PluginDependencies(ImmutableSet.of(pluginKey), null, null));
446 
447         final String dependent2PluginKey = "dependent2PluginKey";
448         final Plugin dependent2Plugin = mockStateChangePlugin(dependent2PluginKey, pluginEventManager);
449         when(dependent2Plugin.getDependencies()).thenReturn(new PluginDependencies(ImmutableSet.of(pluginKey), null, null));
450 
451         mockPluginsSortOrder(dependentPlugin, dependent2Plugin, plugin);
452 
453         final PluginPersistentStateStore pluginPersistentStateStore = mockPluginPersistentStateStore();
454 
455         final PluginLoader pluginLoader = mockPluginLoaderForPlugins(plugin, dependentPlugin, dependent2Plugin);
456         when(pluginLoader.supportsRemoval()).thenReturn(true);
457 
458         final DefaultPluginManager defaultPluginManager = new DefaultPluginManager(
459                 pluginPersistentStateStore,
460                 ImmutableList.of(pluginLoader),
461                 mock(ModuleDescriptorFactory.class),
462                 pluginEventManager,
463                 mock(PluginExceptionInterception.class)
464         );
465 
466         defaultPluginManager.init();
467 
468         final ArgumentCaptor<Object> events = ArgumentCaptor.forClass(Object.class);
469         doNothing().when(pluginEventManager).broadcast(events.capture());
470 
471         Plugin[] uninstalled = new Plugin[]{plugin, dependent2Plugin};
472 
473         defaultPluginManager.uninstallPlugins(Arrays.asList(uninstalled));
474 
475 
476         List<PluginEvent> pluginEvents = events.getAllValues().stream()
477                 .filter(PluginEvent.class::isInstance)
478                 .map(PluginEvent.class::cast)
479                 .collect(Collectors.toList());
480 
481         // Check event sequencing for each of the disabled plugins
482         Arrays.stream(uninstalled)
483                 .forEach(p -> assertThat(
484                         pluginEvents.stream()
485                                 .filter(e -> e.getPlugin() == p)
486                                 .collect(Collectors.toList()),
487                         contains(
488                                 pluginEvent(PluginUninstallingEvent.class, p),
489                                 pluginEvent(BeforePluginDisabledEvent.class, p),
490                                 pluginEvent(PluginDisablingEvent.class, p),
491                                 pluginStateChange(p, PluginState.DISABLED),
492                                 pluginEvent(PluginDisabledEvent.class, p),
493                                 pluginEvent(PluginUninstalledEvent.class, p),
494                                 pluginEvent(PluginDependentsChangedEvent.class, p)
495                         )));
496 
497         // check event sequencing for all plugins in relation to each other
498         assertThat(pluginEvents,
499                 contains(
500                         // dependent is disabled
501                         pluginEvent(BeforePluginDisabledEvent.class, dependentPlugin),
502                         pluginEvent(PluginDisablingEvent.class, dependentPlugin),
503                         pluginStateChange(dependentPlugin, PluginState.DISABLED),
504                         pluginEvent(PluginDisabledEvent.class, dependentPlugin),
505 
506                         // uninstalling for both
507                         anyPluginEvent(PluginUninstallingEvent.class, uninstalled),
508                         anyPluginEvent(PluginUninstallingEvent.class, uninstalled),
509 
510                         // disabling sequence for one of them
511                         anyPluginEvent(BeforePluginDisabledEvent.class, uninstalled),
512                         anyPluginEvent(PluginDisablingEvent.class, uninstalled),
513                         anyPluginStateChange(PluginState.DISABLED, uninstalled),
514                         anyPluginEvent(PluginDisabledEvent.class, uninstalled),
515 
516                         // disabling sequence for the second one
517                         anyPluginEvent(BeforePluginDisabledEvent.class, uninstalled),
518                         anyPluginEvent(PluginDisablingEvent.class, uninstalled),
519                         anyPluginStateChange(PluginState.DISABLED, uninstalled),
520                         anyPluginEvent(PluginDisabledEvent.class, uninstalled),
521 
522                         //uninstalled for both
523                         anyPluginEvent(PluginUninstalledEvent.class, uninstalled),
524                         anyPluginEvent(PluginUninstalledEvent.class, uninstalled),
525 
526                         // dependent changed
527                         anyPluginEvent(PluginDependentsChangedEvent.class, uninstalled),
528                         anyPluginEvent(PluginDependentsChangedEvent.class, uninstalled)
529                 ));
530     }
531 
532     @Test
533     public void moduleEnableDisableEventSequencing() {
534         final PluginEventManager pluginEventManager = mock(PluginEventManager.class);
535 
536         final String pluginKey = "pluginKey";
537         final Plugin plugin = mockStateChangePlugin(pluginKey, pluginEventManager);
538 
539         final String moduleKeyAlpha = "alpha";
540         final ModuleDescriptor moduleAlpha = mockStateChangePluginModule(pluginKey, moduleKeyAlpha, pluginEventManager);
541         final String moduleKeyBeta = "beta";
542         final ModuleDescriptor moduleBeta = mockStateChangePluginModule(pluginKey, moduleKeyBeta, pluginEventManager);
543 
544         when(plugin.getModuleDescriptors()).thenReturn(ImmutableList.<ModuleDescriptor<?>>of(moduleAlpha, moduleBeta));
545         when(plugin.getModuleDescriptor(moduleKeyAlpha)).thenReturn(moduleAlpha);
546         when(plugin.getModuleDescriptor(moduleKeyBeta)).thenReturn(moduleBeta);
547 
548         final PluginPersistentStateStore pluginPersistentStateStore = mockPluginPersistentStateStore();
549 
550         final PluginLoader pluginLoader = mockPluginLoaderForPlugins(plugin);
551 
552         final DefaultPluginManager defaultPluginManager = new DefaultPluginManager(
553                 pluginPersistentStateStore,
554                 ImmutableList.of(pluginLoader),
555                 mock(ModuleDescriptorFactory.class),
556                 pluginEventManager,
557                 mock(PluginExceptionInterception.class)
558         );
559 
560         defaultPluginManager.init();
561 
562         final ArgumentCaptor<Object> disableEventsCaptor = ArgumentCaptor.forClass(Object.class);
563         doNothing().when(pluginEventManager).broadcast(disableEventsCaptor.capture());
564 
565         defaultPluginManager.disablePlugin(pluginKey);
566 
567         final List<Object> disableEvents = disableEventsCaptor.getAllValues();
568         final Iterable<PluginModuleEvent> pluginModuleDisableEvents = filter(disableEvents, PluginModuleEvent.class);
569         assertThat(pluginModuleDisableEvents, contains(
570                 pluginModuleEvent(BeforePluginModuleDisabledEvent.class, moduleBeta),
571                 pluginModuleEvent(PluginModuleDisablingEvent.class, moduleBeta),
572                 pluginModuleStateChange(moduleBeta, false),
573                 pluginModuleEvent(PluginModuleDisabledEvent.class, moduleBeta),
574                 pluginModuleEvent(BeforePluginModuleDisabledEvent.class, moduleAlpha),
575                 pluginModuleEvent(PluginModuleDisablingEvent.class, moduleAlpha),
576                 pluginModuleStateChange(moduleAlpha, false),
577                 pluginModuleEvent(PluginModuleDisabledEvent.class, moduleAlpha)
578         ));
579 
580         // We now want to check that the plugin events surround the module events
581         final int pluginDisablingIndex = indexOf(disableEvents, Predicates.instanceOf(PluginDisablingEvent.class));
582         final int firstPluginModuleDisableEvent = disableEvents.indexOf(get(pluginModuleDisableEvents, 0));
583         assertThat(firstPluginModuleDisableEvent, greaterThan(pluginDisablingIndex));
584         final int lastPluginModuleDisableEventIndex = disableEvents.indexOf(getLast(pluginModuleDisableEvents));
585         final int pluginDisabledIndex = indexOf(disableEvents, Predicates.instanceOf(PluginDisabledEvent.class));
586         assertThat(lastPluginModuleDisableEventIndex, lessThan(pluginDisabledIndex));
587 
588         final ArgumentCaptor<Object> enableEventsCaptor = ArgumentCaptor.forClass(Object.class);
589         doNothing().when(pluginEventManager).broadcast(enableEventsCaptor.capture());
590 
591         defaultPluginManager.enablePlugins(pluginKey);
592 
593         final List<Object> enableEvents = enableEventsCaptor.getAllValues();
594         final Iterable<PluginModuleEvent> pluginModuleEnableEvents = filter(enableEvents, PluginModuleEvent.class);
595         assertThat(pluginModuleEnableEvents, contains(
596                 pluginModuleEvent(PluginModuleEnablingEvent.class, moduleAlpha),
597                 pluginModuleStateChange(moduleAlpha, true),
598                 pluginModuleEvent(PluginModuleEnabledEvent.class, moduleAlpha),
599                 pluginModuleEvent(PluginModuleEnablingEvent.class, moduleBeta),
600                 pluginModuleStateChange(moduleBeta, true),
601                 pluginModuleEvent(PluginModuleEnabledEvent.class, moduleBeta)
602         ));
603 
604         // We now want to check that the plugin events surround the module events
605         final int pluginEnablingIndex = indexOf(disableEvents, Predicates.instanceOf(PluginDisablingEvent.class));
606         final int firstPluginModuleEnableEvent = disableEvents.indexOf(get(pluginModuleDisableEvents, 0));
607         assertThat(firstPluginModuleEnableEvent, greaterThan(pluginEnablingIndex));
608         final int lastPluginModuleEnableEventIndex = disableEvents.indexOf(getLast(pluginModuleDisableEvents));
609         final int pluginEnabledIndex = indexOf(disableEvents, Predicates.instanceOf(PluginDisabledEvent.class));
610         assertThat(lastPluginModuleEnableEventIndex, lessThan(pluginEnabledIndex));
611     }
612 
613     @Test
614     public void shutdownEventsAreSent() {
615         final PluginPersistentStateStore pluginPersistentStateStore = mockPluginPersistentStateStore();
616         final List<PluginLoader> pluginLoaders = Collections.emptyList();
617         final ModuleDescriptorFactory moduleDescriptorFactory = mock(ModuleDescriptorFactory.class);
618         final PluginEventManager pluginEventManager = mock(PluginEventManager.class);
619 
620         final DefaultPluginManager defaultPluginManager = new DefaultPluginManager(
621                 pluginPersistentStateStore, pluginLoaders, moduleDescriptorFactory, pluginEventManager);
622 
623         defaultPluginManager.init();
624 
625         // Make PluginEventManager#broadcast throw during shutdown to validate the exception handling in DefaultPluginManager
626         final ArgumentCaptor<Object> shutdownEvents = ArgumentCaptor.forClass(Object.class);
627         final NotificationException notificationException = new NotificationException(new Throwable());
628         doThrow(notificationException).when(pluginEventManager).broadcast(shutdownEvents.capture());
629 
630         defaultPluginManager.shutdown();
631 
632         // Check that both broadcasts were attempted
633         assertThat(shutdownEvents.getAllValues(), contains(
634                 instanceOf(PluginFrameworkShuttingDownEvent.class), instanceOf(PluginFrameworkShutdownEvent.class)));
635     }
636 
637     @Test
638     public void enableAndDisableEventSequencing() {
639         final PluginEventManager pluginEventManager = mock(PluginEventManager.class);
640 
641         final String pluginKey = "pluginKey";
642         final Plugin plugin = mockStateChangePlugin(pluginKey, pluginEventManager);
643 
644         final PluginPersistentStateStore pluginPersistentStateStore = mockPluginPersistentStateStore();
645 
646         final PluginLoader pluginLoader = mockPluginLoaderForPlugins(plugin);
647 
648         final DefaultPluginManager defaultPluginManager = new DefaultPluginManager(
649                 pluginPersistentStateStore,
650                 ImmutableList.of(pluginLoader),
651                 mock(ModuleDescriptorFactory.class),
652                 pluginEventManager,
653                 mock(PluginExceptionInterception.class)
654         );
655 
656         defaultPluginManager.init();
657 
658         final ArgumentCaptor<Object> disableEvents = ArgumentCaptor.forClass(Object.class);
659         doNothing().when(pluginEventManager).broadcast(disableEvents.capture());
660 
661         defaultPluginManager.disablePlugin(pluginKey);
662 
663         assertThat(filter(disableEvents.getAllValues(), PluginEvent.class), contains(
664                 pluginEvent(BeforePluginDisabledEvent.class, plugin),
665                 pluginEvent(PluginDisablingEvent.class, plugin),
666                 pluginStateChange(plugin, PluginState.DISABLED),
667                 pluginEvent(PluginDisabledEvent.class, plugin)
668         ));
669 
670         final ArgumentCaptor<Object> enableEvents = ArgumentCaptor.forClass(Object.class);
671         doNothing().when(pluginEventManager).broadcast(enableEvents.capture());
672 
673         defaultPluginManager.enablePlugins(pluginKey);
674 
675         assertThat(filter(enableEvents.getAllValues(), PluginEvent.class), contains(
676                 pluginEvent(PluginEnablingEvent.class, plugin),
677                 pluginStateChange(plugin, PluginState.ENABLED),
678                 pluginEvent(PluginEnabledEvent.class, plugin)
679         ));
680     }
681 
682     @Test
683     public void upgradePluginUpgradesPlugin() {
684         final String pluginKey = "pluginKey";
685         final PluginPersistentStateStore pluginPersistentStateStore = mock(
686                 PluginPersistentStateStore.class, RETURNS_DEEP_STUBS);
687         when(pluginPersistentStateStore.load().getPluginRestartState(pluginKey)).thenReturn(PluginRestartState.NONE);
688 
689         Plugin pluginV1 = mock(Plugin.class, RETURNS_DEEP_STUBS);
690         Plugin pluginV2 = mock(Plugin.class, RETURNS_DEEP_STUBS);
691         when(pluginV1.getKey()).thenReturn(pluginKey);
692         when(pluginV2.getKey()).thenReturn(pluginKey);
693         when(pluginV1.isDeleteable()).thenReturn(true);
694         when(pluginV1.isUninstallable()).thenReturn(true);
695         // Set up so that pluginV1 < pluginV2
696         when(pluginV1.compareTo(pluginV2)).thenReturn(-1);
697         when(pluginV2.compareTo(pluginV1)).thenReturn(1);
698 
699         DiscardablePluginLoader pluginLoader = mock(DiscardablePluginLoader.class);
700         when(pluginLoader.supportsAddition()).thenReturn(true);
701         when(pluginLoader.supportsRemoval()).thenReturn(true);
702         when(pluginLoader.loadAllPlugins(isA(ModuleDescriptorFactory.class))).thenReturn(Arrays.asList(pluginV1));
703         when(pluginLoader.loadFoundPlugins(isA(ModuleDescriptorFactory.class))).thenReturn(Arrays.asList(pluginV2));
704 
705         final DefaultPluginManager defaultPluginManager = new DefaultPluginManager(
706                 pluginPersistentStateStore,
707                 Arrays.asList((PluginLoader) pluginLoader),
708                 mock(ModuleDescriptorFactory.class),
709                 mock(PluginEventManager.class),
710                 mock(PluginExceptionInterception.class)
711         );
712         defaultPluginManager.init();
713 
714         assertThat(defaultPluginManager.getPlugin(pluginKey), is(pluginV1));
715 
716         final int found = defaultPluginManager.scanForNewPlugins();
717 
718         assertThat(found, is(1));
719         assertThat(defaultPluginManager.getPlugin(pluginKey), is(pluginV2));
720 
721         verify(pluginLoader).removePlugin(pluginV1);
722     }
723 
724 
725     @Test
726     public void uninstallingNotDeletableUninstallablePluginRemovesItFromLoader() {
727         final Plugin plugin = mock(Plugin.class, RETURNS_DEEP_STUBS);
728         final PluginLoader pluginLoader = mock(PluginLoader.class);
729         final DefaultPluginManager defaultPluginManager = setupUninstallTest(false, true, plugin, pluginLoader);
730         defaultPluginManager.uninstall(plugin);
731         verify(pluginLoader).removePlugin(plugin);
732     }
733 
734     @Test
735     public void uninstallingDeletableUninstallablePluginRemovesItFromLoader() {
736         final Plugin plugin = mock(Plugin.class, RETURNS_DEEP_STUBS);
737         final PluginLoader pluginLoader = mock(PluginLoader.class);
738         final DefaultPluginManager defaultPluginManager = setupUninstallTest(true, true, plugin, pluginLoader);
739         defaultPluginManager.uninstall(plugin);
740         verify(pluginLoader).removePlugin(plugin);
741     }
742 
743     @Test
744     public void uninstallingDeletableNotUninstallablePluginDoesNotRemoveItFromLoader() {
745         final Plugin plugin = mock(Plugin.class, RETURNS_DEEP_STUBS);
746         final PluginLoader pluginLoader = mock(PluginLoader.class);
747         final DefaultPluginManager defaultPluginManager = setupUninstallTest(true, false, plugin, pluginLoader);
748         // We want to ensure the removal is not attempted, so we make removal throw something unexpected
749         doThrow(new AssertionError("Unexpected PluginLoader.removePlugin call")).when(pluginLoader).removePlugin(plugin);
750         expectedException.expect(PluginException.class);
751         expectedException.expectMessage(plugin.getKey());
752         defaultPluginManager.uninstall(plugin);
753     }
754 
755     @Test
756     public void uninstallingNotDeletableNotUninstallablePluginDoesNotRemoveItFromLoader() {
757         final Plugin plugin = mock(Plugin.class, RETURNS_DEEP_STUBS);
758         final PluginLoader pluginLoader = mock(PluginLoader.class);
759         final DefaultPluginManager defaultPluginManager = setupUninstallTest(false, false, plugin, pluginLoader);
760         // We want to ensure the removal is not attempted, so we make removal throw something unexpected
761         doThrow(new AssertionError("Unexpected PluginLoader.removePlugin call")).when(pluginLoader).removePlugin(plugin);
762         expectedException.expect(PluginException.class);
763         expectedException.expectMessage(plugin.getKey());
764         defaultPluginManager.uninstall(plugin);
765     }
766 
767     private DefaultPluginManager setupUninstallTest(
768             final boolean isDeleteable,
769             final boolean isUninstallable,
770             final Plugin plugin,
771             final PluginLoader pluginLoader) {
772         final String pluginKey = "uninstall-test-plugin-key";
773         when(plugin.getKey()).thenReturn(pluginKey);
774         when(plugin.toString()).thenReturn(pluginKey);
775         when(plugin.isDeleteable()).thenReturn(isDeleteable);
776         when(plugin.isUninstallable()).thenReturn(isUninstallable);
777         when(pluginLoader.loadAllPlugins(isA(ModuleDescriptorFactory.class))).thenReturn(Arrays.asList(plugin));
778         when(pluginLoader.supportsRemoval()).thenReturn(true);
779 
780         final DefaultPluginManager defaultPluginManager = new DefaultPluginManager(
781                 mock(PluginPersistentStateStore.class, RETURNS_DEEP_STUBS),
782                 Arrays.asList(pluginLoader),
783                 mock(ModuleDescriptorFactory.class),
784                 mock(PluginEventManager.class),
785                 mock(PluginExceptionInterception.class)
786         );
787         defaultPluginManager.init();
788         return defaultPluginManager;
789     }
790 
791     @Test
792     public void addDynamicModuleNoLoader() {
793         final PluginLoader pluginLoader = mock(PluginLoader.class);
794         final PluginInternal plugin = mock(PluginInternal.class);
795 
796         manager = newDefaultPluginManager(pluginLoader);
797 
798         when(plugin.toString()).thenReturn("bleh");
799 
800         expectedException.expect(PluginException.class);
801         expectedException.expectMessage(containsString("bleh"));
802 
803         manager.addDynamicModule(plugin, mock(Element.class));
804     }
805 
806 
807     @Test
808     public void removeDynamicModuleNotPresent() {
809         final PluginLoader pluginLoader = mock(PluginLoader.class);
810         final PluginInternal plugin = mock(PluginInternal.class);
811         final ModuleDescriptor moduleDescriptor = mock(ModuleDescriptor.class, withSettings().extraInterfaces(StateAware.class));
812 
813         manager = newDefaultPluginManager(pluginLoader);
814 
815         when(plugin.removeDynamicModuleDescriptor(moduleDescriptor)).thenReturn(false);
816         when(plugin.toString()).thenReturn("crapPlugin");
817         when(moduleDescriptor.getKey()).thenReturn("moduleKey");
818 
819         expectedException.expect(PluginException.class);
820         expectedException.expectMessage(containsString("crapPlugin"));
821         expectedException.expectMessage(containsString("moduleKey"));
822 
823         manager.removeDynamicModule(plugin, moduleDescriptor);
824 
825         verify((StateAware) moduleDescriptor, never()).enabled();
826     }
827 
828     @Test
829     public void removeDynamicModule() {
830         final PluginLoader pluginLoader = mock(PluginLoader.class);
831         final PluginInternal plugin = mock(PluginInternal.class);
832         final ModuleDescriptor<?> moduleDescriptor = mock(ModuleDescriptor.class, withSettings().extraInterfaces(StateAware.class));
833 
834         manager = newDefaultPluginManager(pluginLoader);
835 
836         when(plugin.removeDynamicModuleDescriptor(moduleDescriptor)).thenReturn(true);
837 
838         manager.removeDynamicModule(plugin, moduleDescriptor);
839 
840         verify(plugin).removeDynamicModuleDescriptor(moduleDescriptor);
841         verify(moduleDescriptor).destroy();
842         verify((StateAware) moduleDescriptor).disabled();
843     }
844 
845     @Test
846     public void testUninstallPluginWithMultiLevelDependencies() throws PluginException, IOException {
847 
848         Plugin child = mock(Plugin.class);
849         when(child.getKey()).thenReturn("child");
850         when(child.isEnabledByDefault()).thenReturn(true);
851         when(child.getPluginState()).thenReturn(PluginState.ENABLED);
852         when(child.getDependencies()).thenReturn(new PluginDependencies(ImmutableSet.of("parent"), null, null));
853         when(child.compareTo(any(Plugin.class))).thenReturn(-1);
854 
855         Plugin parent = mock(Plugin.class);
856         when(parent.getKey()).thenReturn("parent");
857         when(parent.isEnabledByDefault()).thenReturn(true);
858         when(parent.getPluginState()).thenReturn(PluginState.ENABLED);
859         when(parent.getDependencies()).thenReturn(new PluginDependencies(ImmutableSet.of("grandparent"), null, null));
860         when(parent.compareTo(any(Plugin.class))).thenReturn(-1);
861 
862         Plugin grandparent = mock(Plugin.class);
863         when(grandparent.getKey()).thenReturn("grandparent");
864         when(grandparent.isEnabledByDefault()).thenReturn(true);
865         when(grandparent.isDeleteable()).thenReturn(true);
866         when(grandparent.isUninstallable()).thenReturn(true);
867         when(grandparent.getPluginState()).thenReturn(PluginState.ENABLED);
868         when(grandparent.compareTo(any(Plugin.class))).thenReturn(-1);
869         when(grandparent.getDependencies()).thenReturn(new PluginDependencies());
870 
871         PluginLoader pluginLoader = mockPluginLoaderForPlugins(child, parent, grandparent);
872         when(pluginLoader.supportsRemoval()).thenReturn(true);
873 
874         manager = initNewDefaultPluginManager(pluginLoader);
875 
876         manager.uninstall(grandparent);
877         verify(grandparent).enable();
878         verify(grandparent).disable();
879         verify(pluginLoader).removePlugin(grandparent);
880 
881         verify(parent).enable();
882         verify(parent).disable();
883         verify(child).enable();
884         verify(child).disable();
885     }
886 
887     @Test
888     public void testUninstallPluginWithDependencies() throws PluginException, IOException {
889         Plugin child = mock(Plugin.class);
890         when(child.getKey()).thenReturn("child");
891         when(child.isEnabledByDefault()).thenReturn(true);
892         when(child.getPluginState()).thenReturn(PluginState.ENABLED);
893         when(child.getDependencies()).thenReturn(new PluginDependencies(ImmutableSet.of("parent"), null, null));
894         when(child.compareTo(any(Plugin.class))).thenReturn(-1);
895         Plugin parent = mock(Plugin.class);
896         when(parent.getKey()).thenReturn("parent");
897         when(parent.isEnabledByDefault()).thenReturn(true);
898         when(parent.isDeleteable()).thenReturn(true);
899         when(parent.isUninstallable()).thenReturn(true);
900         when(parent.getPluginState()).thenReturn(PluginState.ENABLED);
901         when(parent.compareTo(any(Plugin.class))).thenReturn(-1);
902         when(parent.getDependencies()).thenReturn(new PluginDependencies());
903 
904         PluginLoader pluginLoader = mockPluginLoaderForPlugins(child, parent);
905         when(pluginLoader.supportsRemoval()).thenReturn(true);
906 
907         manager = initNewDefaultPluginManager(pluginLoader);
908 
909         manager.uninstall(parent);
910         verify(parent).enable();
911         verify(parent).disable();
912         verify(pluginLoader).removePlugin(parent);
913 
914         verify(child).enable();
915         verify(child).disable();
916     }
917 
918     @Test
919     public void testInstallPluginsWithTwoButOneFailsValidationWithException() {
920         DynamicPluginLoader loader = mock(DynamicPluginLoader.class);
921         when(loader.isDynamicPluginLoader()).thenReturn(true);
922 
923         moduleDescriptorFactory = mock(ModuleDescriptorFactory.class);
924         PluginInstaller installer = mock(PluginInstaller.class);
925         DefaultPluginManager pm = newDefaultPluginManager(loader);
926         pm.setPluginInstaller(installer);
927         PluginArtifact artifactA = mock(PluginArtifact.class);
928         Plugin pluginA = mock(Plugin.class);
929         when(loader.canLoad(artifactA)).thenReturn("a");
930         PluginArtifact artifactB = mock(PluginArtifact.class);
931         Plugin pluginB = mock(Plugin.class);
932         doThrow(new PluginParseException()).when(loader).canLoad(artifactB);
933 
934         when(loader.loadFoundPlugins(moduleDescriptorFactory)).thenReturn(Arrays.asList(pluginA, pluginB));
935 
936         try {
937             manager.installPlugins(artifactA, artifactB);
938             fail("Should have not installed plugins");
939         } catch (PluginParseException ex) {
940             // this is good
941         }
942 
943         verify(loader).canLoad(artifactA);
944         verify(loader).canLoad(artifactB);
945         verify(installer, never()).installPlugin("a", artifactA);
946         verify(installer, never()).installPlugin("b", artifactB);
947     }
948 
949     @Test
950     public void testInstallPlugin() throws Exception {
951         final PluginPersistentStateStore mockPluginStateStore = mock(PluginPersistentStateStore.class);
952         final ModuleDescriptorFactory moduleDescriptorFactory = mock(ModuleDescriptorFactory.class);
953         final DynamicPluginLoader mockPluginLoader = mock(DynamicPluginLoader.class);
954         when(mockPluginLoader.isDynamicPluginLoader()).thenReturn(true);
955 
956         final DescriptorParser mockDescriptorParser = mock(DescriptorParser.class);
957         final PluginArtifact pluginArtifact = mock(PluginArtifact.class);
958         final PluginInstaller mockRepository = mock(PluginInstaller.class);
959         final Plugin plugin = mock(Plugin.class);
960 
961 
962         final DefaultPluginManager pluginManager = new DefaultPluginManager(mockPluginStateStore,
963                 Collections.<PluginLoader>singletonList(mockPluginLoader), moduleDescriptorFactory, pluginEventManager);
964 
965         when(mockPluginStateStore.load()).thenReturn(new DefaultPluginPersistentState());
966         when(mockPluginStateStore.load()).thenReturn(new DefaultPluginPersistentState());
967         when(mockPluginStateStore.load()).thenReturn(new DefaultPluginPersistentState());
968         when(mockDescriptorParser.getKey()).thenReturn("test");
969         when(mockPluginLoader.loadAllPlugins(moduleDescriptorFactory)).thenReturn((Iterable) Collections.emptyList());
970         when(mockPluginLoader.supportsAddition()).thenReturn(true);
971         when(mockPluginLoader.loadFoundPlugins(moduleDescriptorFactory)).thenReturn(Collections.singletonList(plugin));
972         when(mockPluginLoader.canLoad(pluginArtifact)).thenReturn("test");
973         when(plugin.getKey()).thenReturn("test");
974         when(plugin.getModuleDescriptors()).thenReturn((Collection) new ArrayList<Object>());
975         when(plugin.getModuleDescriptors()).thenReturn((Collection) new ArrayList<Object>());
976         when(plugin.isEnabledByDefault()).thenReturn(true);
977 
978         when(plugin.isEnabledByDefault()).thenReturn(true);
979         when(plugin.isEnabled()).thenReturn(true);
980         when(plugin.getPluginState()).thenReturn(PluginState.ENABLED);
981         when(plugin.hasAllPermissions()).thenReturn(true);
982         when(plugin.getActivePermissions()).thenReturn(ImmutableSet.of(Permissions.ALL_PERMISSIONS));
983         when(plugin.getDependencies()).thenReturn(new PluginDependencies());
984 
985         pluginManager.setPluginInstaller(mockRepository);
986         pluginManager.init();
987         final PassListener enabledListener = new PassListener(PluginEnabledEvent.class);
988         pluginEventManager.register(enabledListener);
989         pluginManager.installPlugin(pluginArtifact);
990 
991         assertEquals(plugin, pluginManager.getPlugin("test"));
992         assertTrue(pluginManager.isPluginEnabled("test"));
993 
994         enabledListener.assertCalled();
995     }
996 
997     @Test
998     public void testInstallPluginsWithOne() {
999         DynamicPluginLoader loader = mock(DynamicPluginLoader.class);
1000         when(loader.isDynamicPluginLoader()).thenReturn(true);
1001 
1002         moduleDescriptorFactory = mock(ModuleDescriptorFactory.class);
1003         PluginInstaller installer = mock(PluginInstaller.class);
1004         DefaultPluginManager pm = newDefaultPluginManager(loader);
1005         pm.setPluginInstaller(installer);
1006         when(loader.loadAllPlugins(moduleDescriptorFactory)).thenReturn(Arrays.<Plugin>asList());
1007         PluginArtifact artifact = mock(PluginArtifact.class);
1008         Plugin plugin = mock(Plugin.class);
1009         when(loader.canLoad(artifact)).thenReturn("foo");
1010         when(loader.loadFoundPlugins(moduleDescriptorFactory)).thenReturn(Arrays.asList(plugin));
1011 
1012         pm.init();
1013         manager.installPlugins(artifact);
1014 
1015         verify(loader).canLoad(artifact);
1016         verify(installer).installPlugin("foo", artifact);
1017     }
1018 
1019     @Test
1020     public void testInstallPluginsWithTwo() {
1021         DynamicPluginLoader loader = mock(DynamicPluginLoader.class);
1022         when(loader.isDynamicPluginLoader()).thenReturn(true);
1023 
1024         moduleDescriptorFactory = mock(ModuleDescriptorFactory.class);
1025         PluginInstaller installer = mock(PluginInstaller.class);
1026         DefaultPluginManager pm = newDefaultPluginManager(loader);
1027         pm.setPluginInstaller(installer);
1028         when(loader.loadAllPlugins(moduleDescriptorFactory)).thenReturn(Arrays.<Plugin>asList());
1029         PluginArtifact artifactA = mock(PluginArtifact.class);
1030         Plugin pluginA = mock(Plugin.class);
1031         when(loader.canLoad(artifactA)).thenReturn("a");
1032         PluginArtifact artifactB = mock(PluginArtifact.class);
1033         Plugin pluginB = mock(Plugin.class);
1034         when(loader.canLoad(artifactB)).thenReturn("b");
1035 
1036         when(loader.loadFoundPlugins(moduleDescriptorFactory)).thenReturn(Arrays.asList(pluginA, pluginB));
1037 
1038         pm.init();
1039         manager.installPlugins(artifactA, artifactB);
1040 
1041         verify(loader).canLoad(artifactA);
1042         verify(loader).canLoad(artifactB);
1043         verify(installer).installPlugin("a", artifactA);
1044         verify(installer).installPlugin("b", artifactB);
1045     }
1046 
1047     @Test
1048     public void testInstallPluginsWithTwoButOneFailsValidation() {
1049         DynamicPluginLoader loader = mock(DynamicPluginLoader.class);
1050         when(loader.isDynamicPluginLoader()).thenReturn(true);
1051 
1052         ModuleDescriptorFactory descriptorFactory = mock(ModuleDescriptorFactory.class);
1053         PluginEventManager eventManager = mock(PluginEventManager.class);
1054         PluginInstaller installer = mock(PluginInstaller.class);
1055         DefaultPluginManager pm = newDefaultPluginManager(loader);
1056         pm.setPluginInstaller(installer);
1057         PluginArtifact artifactA = mock(PluginArtifact.class);
1058         Plugin pluginA = mock(Plugin.class);
1059         when(loader.canLoad(artifactA)).thenReturn("a");
1060         PluginArtifact artifactB = mock(PluginArtifact.class);
1061         Plugin pluginB = mock(Plugin.class);
1062         when(loader.canLoad(artifactB)).thenReturn(null);
1063 
1064         when(loader.loadFoundPlugins(descriptorFactory)).thenReturn(Arrays.asList(pluginA, pluginB));
1065 
1066         try {
1067             manager.installPlugins(artifactA, artifactB);
1068             fail("Should have not installed plugins");
1069         } catch (PluginParseException ex) {
1070             // this is good
1071         }
1072 
1073         verify(loader).canLoad(artifactA);
1074         verify(loader).canLoad(artifactB);
1075         verify(installer, never()).installPlugin("a", artifactA);
1076         verify(installer, never()).installPlugin("b", artifactB);
1077     }
1078 
1079     @Test
1080     public void testCircularDependencyWouldNotCauseInfiniteLoop() throws PluginException, IOException {
1081 
1082         Plugin p1 = mock(Plugin.class);
1083         when(p1.getKey()).thenReturn("p1");
1084         when(p1.isEnabledByDefault()).thenReturn(true);
1085         when(p1.getPluginState()).thenReturn(PluginState.ENABLED);
1086         when(p1.getDependencies()).thenReturn(new PluginDependencies(ImmutableSet.of("p2", "parent"), null, null));
1087         when(p1.compareTo(any(Plugin.class))).thenReturn(-1);
1088 
1089         // Create a circular dependency between p1 and p2. This should not happen, but test anyway.
1090         Plugin p2 = mock(Plugin.class);
1091         when(p2.getKey()).thenReturn("p2");
1092         when(p2.isEnabledByDefault()).thenReturn(true);
1093         when(p2.getPluginState()).thenReturn(PluginState.ENABLED);
1094         when(p2.getDependencies()).thenReturn(new PluginDependencies(ImmutableSet.of("p1"), null, null));
1095         when(p2.compareTo(any(Plugin.class))).thenReturn(-1);
1096 
1097         Plugin parent = mock(Plugin.class);
1098         when(parent.getKey()).thenReturn("parent");
1099         when(parent.isEnabledByDefault()).thenReturn(true);
1100         when(parent.isDeleteable()).thenReturn(true);
1101         when(parent.isUninstallable()).thenReturn(true);
1102         when(parent.getPluginState()).thenReturn(PluginState.ENABLED);
1103         when(parent.compareTo(any(Plugin.class))).thenReturn(-1);
1104         when(parent.getDependencies()).thenReturn(new PluginDependencies());
1105 
1106         PluginLoader pluginLoader = mockPluginLoaderForPlugins(p1, p2, parent);
1107         when(pluginLoader.supportsRemoval()).thenReturn(true);
1108 
1109         manager = initNewDefaultPluginManager(pluginLoader);
1110 
1111         manager.uninstall(parent);
1112         verify(parent, times(1)).enable();
1113         verify(parent, times(1)).disable();
1114         verify(pluginLoader).removePlugin(parent);
1115 
1116         verify(p1, times(1)).enable();
1117         verify(p1, times(1)).disable();
1118         verify(p2, times(1)).enable();
1119         verify(p2, times(1)).disable();
1120     }
1121 
1122     @Test
1123     public void testThreeCycleDependencyWouldNotCauseInfiniteLoop() throws PluginException, IOException {
1124         Plugin p1 = mock(Plugin.class);
1125         when(p1.getKey()).thenReturn("p1");
1126         when(p1.isEnabledByDefault()).thenReturn(true);
1127         when(p1.getPluginState()).thenReturn(PluginState.ENABLED);
1128         when(p1.getDependencies()).thenReturn(new PluginDependencies(ImmutableSet.of("p2", "parent"), null, null));
1129         when(p1.compareTo(any(Plugin.class))).thenReturn(-1);
1130 
1131         // Create a circular dependency between p1, p2 and p3. This should not happen, but test anyway.
1132         Plugin p2 = mock(Plugin.class);
1133         when(p2.getKey()).thenReturn("p2");
1134         when(p2.isEnabledByDefault()).thenReturn(true);
1135         when(p2.getPluginState()).thenReturn(PluginState.ENABLED);
1136         when(p2.getDependencies()).thenReturn(new PluginDependencies(ImmutableSet.of("p3"), null, null));
1137         when(p2.compareTo(any(Plugin.class))).thenReturn(-1);
1138 
1139         Plugin p3 = mock(Plugin.class);
1140         when(p3.getKey()).thenReturn("p3");
1141         when(p3.isEnabledByDefault()).thenReturn(true);
1142         when(p3.getPluginState()).thenReturn(PluginState.ENABLED);
1143         when(p3.getDependencies()).thenReturn(new PluginDependencies(ImmutableSet.of("p1"), null, null));
1144         when(p3.compareTo(any(Plugin.class))).thenReturn(-1);
1145 
1146         Plugin parent = mock(Plugin.class);
1147         when(parent.getKey()).thenReturn("parent");
1148         when(parent.isEnabledByDefault()).thenReturn(true);
1149         when(parent.isDeleteable()).thenReturn(true);
1150         when(parent.isUninstallable()).thenReturn(true);
1151         when(parent.getPluginState()).thenReturn(PluginState.ENABLED);
1152         when(parent.compareTo(any(Plugin.class))).thenReturn(-1);
1153         when(parent.getDependencies()).thenReturn(new PluginDependencies());
1154 
1155         PluginLoader pluginLoader = mockPluginLoaderForPlugins(p1, p2, p3, parent);
1156         when(pluginLoader.supportsRemoval()).thenReturn(true);
1157 
1158         manager = initNewDefaultPluginManager(pluginLoader);
1159 
1160         manager.uninstall(parent);
1161         verify(parent, times(1)).enable();
1162         verify(parent, times(1)).disable();
1163         verify(pluginLoader).removePlugin(parent);
1164 
1165         verify(p1, times(1)).enable();
1166         verify(p1, times(1)).disable();
1167         verify(p2, times(1)).enable();
1168         verify(p2, times(1)).disable();
1169         verify(p3, times(1)).enable();
1170         verify(p3, times(1)).disable();
1171     }
1172 
1173     @Test
1174     public void addDynamicModule() {
1175         final PluginLoader pluginLoader = mock(PluginLoader.class);
1176         final PluginInternal plugin = mock(PluginInternal.class);
1177         final Element module = mock(Element.class);
1178         final ModuleDescriptor moduleDescriptor = mock(ModuleDescriptor.class, withSettings().extraInterfaces(StateAware.class));
1179 
1180         newDefaultPluginManager(pluginLoader)
1181                 .addPlugins(pluginLoader, ImmutableList.<Plugin>of(plugin));
1182 
1183         when(pluginLoader.createModule(plugin, module, moduleDescriptorFactory)).thenReturn(moduleDescriptor);
1184         when(moduleDescriptor.isEnabledByDefault()).thenReturn(true);
1185         when(plugin.addDynamicModuleDescriptor(moduleDescriptor)).thenReturn(true);
1186         when(plugin.getPluginState()).thenReturn(PluginState.ENABLED);
1187 
1188         assertThat(manager.addDynamicModule(plugin, module), is(moduleDescriptor));
1189 
1190         verify((StateAware) moduleDescriptor).enabled();
1191     }
1192 
1193     @Test
1194     public void addDynamicModulePluginDisabled() {
1195         final PluginLoader pluginLoader = mock(PluginLoader.class);
1196         final PluginInternal plugin = mock(PluginInternal.class);
1197         final Element module = mock(Element.class);
1198         final ModuleDescriptor moduleDescriptor = mock(ModuleDescriptor.class, withSettings().extraInterfaces(StateAware.class));
1199 
1200         newDefaultPluginManager(pluginLoader)
1201                 .addPlugins(pluginLoader, ImmutableList.<Plugin>of(plugin));
1202 
1203         when(pluginLoader.createModule(plugin, module, moduleDescriptorFactory)).thenReturn(moduleDescriptor);
1204         when(moduleDescriptor.isEnabledByDefault()).thenReturn(true);
1205         when(plugin.addDynamicModuleDescriptor(moduleDescriptor)).thenReturn(true);
1206         when(plugin.getPluginState()).thenReturn(PluginState.DISABLED);
1207 
1208         manager.addDynamicModule(plugin, module);
1209 
1210         verify((StateAware) moduleDescriptor, never()).enabled();
1211     }
1212 
1213     @Test
1214     public void addDynamicModuleModuleNotEnabledByDefault() {
1215         final PluginLoader pluginLoader = mock(PluginLoader.class);
1216         final PluginInternal plugin = mock(PluginInternal.class);
1217         final Element module = mock(Element.class);
1218         final ModuleDescriptor moduleDescriptor = mock(ModuleDescriptor.class, withSettings().extraInterfaces(StateAware.class));
1219 
1220         newDefaultPluginManager(pluginLoader)
1221                 .addPlugins(pluginLoader, ImmutableList.<Plugin>of(plugin));
1222 
1223         when(pluginLoader.createModule(plugin, module, moduleDescriptorFactory)).thenReturn(moduleDescriptor);
1224         when(moduleDescriptor.isEnabledByDefault()).thenReturn(false);
1225         when(plugin.addDynamicModuleDescriptor(moduleDescriptor)).thenReturn(true);
1226         when(plugin.getPluginState()).thenReturn(PluginState.ENABLED);
1227 
1228         manager.addDynamicModule(plugin, module);
1229 
1230         verify((StateAware) moduleDescriptor, never()).enabled();
1231     }
1232 
1233     @Test
1234     public void addDynamicModuleDuplicateKey() {
1235         final PluginLoader pluginLoader = mock(PluginLoader.class);
1236         final PluginInternal plugin = mock(PluginInternal.class);
1237         final Element module = mock(Element.class);
1238         final ModuleDescriptor moduleDescriptor = mock(ModuleDescriptor.class, withSettings().extraInterfaces(StateAware.class));
1239         final ModuleDescriptor existingModuleDescriptor = mock(ModuleDescriptor.class);
1240 
1241         newDefaultPluginManager(pluginLoader)
1242                 .addPlugins(pluginLoader, ImmutableList.<Plugin>of(plugin));
1243 
1244         when(pluginLoader.createModule(plugin, module, moduleDescriptorFactory)).thenReturn(moduleDescriptor);
1245         when(plugin.toString()).thenReturn("awesomePlugin");
1246         when(plugin.getModuleDescriptor("moduleKey")).thenReturn(existingModuleDescriptor);
1247         when(moduleDescriptor.getKey()).thenReturn("moduleKey");
1248 
1249         expectedException.expect(PluginException.class);
1250         expectedException.expectMessage(containsString("awesomePlugin"));
1251         expectedException.expectMessage(containsString("moduleKey"));
1252 
1253         manager.addDynamicModule(plugin, module);
1254 
1255         verify((StateAware) moduleDescriptor, never()).enabled();
1256     }
1257 
1258     @Test
1259     public void addDynamicModuleAlreadyPresentAsDynamicModule() {
1260         final PluginLoader pluginLoader = mock(PluginLoader.class);
1261         final PluginInternal plugin = mock(PluginInternal.class);
1262         final Element module = mock(Element.class);
1263         final ModuleDescriptor moduleDescriptor = mock(ModuleDescriptor.class, withSettings().extraInterfaces(StateAware.class));
1264 
1265         newDefaultPluginManager(pluginLoader)
1266                 .addPlugins(pluginLoader, ImmutableList.<Plugin>of(plugin));
1267 
1268         when(pluginLoader.createModule(plugin, module, moduleDescriptorFactory)).thenReturn(moduleDescriptor);
1269         when(plugin.addDynamicModuleDescriptor(moduleDescriptor)).thenReturn(false);
1270         when(plugin.toString()).thenReturn("lawsonPlugin");
1271         when(moduleDescriptor.getKey()).thenReturn("moduleKey");
1272 
1273         expectedException.expect(PluginException.class);
1274         expectedException.expectMessage(containsString("lawsonPlugin"));
1275         expectedException.expectMessage(containsString("moduleKey"));
1276 
1277         manager.addDynamicModule(plugin, module);
1278 
1279         verify((StateAware) moduleDescriptor, never()).enabled();
1280     }
1281 
1282     private static class CannotEnablePlugin extends StaticPlugin {
1283         public CannotEnablePlugin() {
1284             setKey("foo");
1285         }
1286 
1287         @Override
1288         protected PluginState enableInternal() {
1289             throw new RuntimeException("boo");
1290         }
1291 
1292         public void disabled() {
1293         }
1294     }
1295 
1296     @Test
1297     public void testAddPluginsThatThrowExceptionOnEnabled() throws Exception {
1298         final Plugin plugin = new CannotEnablePlugin();
1299 
1300         newDefaultPluginManager()
1301                 .addPlugins(null, Arrays.asList(plugin));
1302 
1303         assertFalse(plugin.getPluginState() == PluginState.ENABLED);
1304     }
1305 }