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.PluginDependentsChangedEvent;
21  import com.atlassian.plugin.event.events.PluginDisabledEvent;
22  import com.atlassian.plugin.event.events.PluginDisablingEvent;
23  import com.atlassian.plugin.event.events.PluginEnabledEvent;
24  import com.atlassian.plugin.event.events.PluginEnablingEvent;
25  import com.atlassian.plugin.event.events.PluginEvent;
26  import com.atlassian.plugin.event.events.PluginFrameworkShutdownEvent;
27  import com.atlassian.plugin.event.events.PluginFrameworkShuttingDownEvent;
28  import com.atlassian.plugin.event.events.PluginInstalledEvent;
29  import com.atlassian.plugin.event.events.PluginInstallingEvent;
30  import com.atlassian.plugin.event.events.PluginModuleDisabledEvent;
31  import com.atlassian.plugin.event.events.PluginModuleDisablingEvent;
32  import com.atlassian.plugin.event.events.PluginModuleEnabledEvent;
33  import com.atlassian.plugin.event.events.PluginModuleEnablingEvent;
34  import com.atlassian.plugin.event.events.PluginModuleEvent;
35  import com.atlassian.plugin.event.events.PluginUninstalledEvent;
36  import com.atlassian.plugin.event.events.PluginUninstallingEvent;
37  import com.atlassian.plugin.event.events.PluginUpgradedEvent;
38  import com.atlassian.plugin.event.events.PluginUpgradingEvent;
39  import com.atlassian.plugin.event.impl.DefaultPluginEventManager;
40  import com.atlassian.plugin.event.listeners.PassListener;
41  import com.atlassian.plugin.exception.PluginExceptionInterception;
42  import com.atlassian.plugin.hostcontainer.DefaultHostContainer;
43  import com.atlassian.plugin.impl.StaticPlugin;
44  import com.atlassian.plugin.loaders.DiscardablePluginLoader;
45  import com.atlassian.plugin.loaders.DynamicPluginLoader;
46  import com.atlassian.plugin.loaders.PluginLoader;
47  import com.atlassian.plugin.manager.store.MemoryPluginPersistentStateStore;
48  import com.atlassian.plugin.parsers.DescriptorParser;
49  import com.google.common.base.Predicates;
50  import com.google.common.collect.ImmutableList;
51  import com.google.common.collect.ImmutableSet;
52  import org.dom4j.Element;
53  import org.junit.Rule;
54  import org.junit.Test;
55  import org.junit.rules.ExpectedException;
56  import org.mockito.ArgumentCaptor;
57  
58  import java.io.IOException;
59  import java.util.ArrayList;
60  import java.util.Arrays;
61  import java.util.Collection;
62  import java.util.Collections;
63  import java.util.List;
64  import java.util.stream.Collectors;
65  
66  import static com.atlassian.plugin.manager.DefaultPluginManagerMocks.anyPluginEvent;
67  import static com.atlassian.plugin.manager.DefaultPluginManagerMocks.anyPluginStateChange;
68  import static com.atlassian.plugin.manager.DefaultPluginManagerMocks.mockPluginLoaderForPlugins;
69  import static com.atlassian.plugin.manager.DefaultPluginManagerMocks.mockPluginPersistentStateStore;
70  import static com.atlassian.plugin.manager.DefaultPluginManagerMocks.mockPluginsSortOrder;
71  import static com.atlassian.plugin.manager.DefaultPluginManagerMocks.mockStateChangePlugin;
72  import static com.atlassian.plugin.manager.DefaultPluginManagerMocks.mockStateChangePluginModule;
73  import static com.atlassian.plugin.manager.DefaultPluginManagerMocks.pluginEvent;
74  import static com.atlassian.plugin.manager.DefaultPluginManagerMocks.pluginModuleEvent;
75  import static com.atlassian.plugin.manager.DefaultPluginManagerMocks.pluginModuleStateChange;
76  import static com.atlassian.plugin.manager.DefaultPluginManagerMocks.pluginStateChange;
77  import static com.google.common.collect.ImmutableList.copyOf;
78  import static com.google.common.collect.Iterables.filter;
79  import static com.google.common.collect.Iterables.get;
80  import static com.google.common.collect.Iterables.getLast;
81  import static com.google.common.collect.Iterables.indexOf;
82  import static java.util.Collections.emptyList;
83  import static java.util.Collections.singletonList;
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.assertNotSame;
95  import static org.junit.Assert.assertTrue;
96  import static org.junit.Assert.fail;
97  import static org.mockito.ArgumentMatchers.any;
98  import static org.mockito.ArgumentMatchers.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 final 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 = newPluginMock();
149         Plugin pluginV2 = newPluginMock();
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                 singletonList(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 = newPluginMock();
177         Plugin pluginV2 = newPluginMock();
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(singletonList(pluginV2));
184         when(pluginLoader.supportsAddition()).thenReturn(true);
185         when(pluginLoader.loadFoundPlugins(isA(ModuleDescriptorFactory.class))).thenReturn(singletonList(pluginV1));
186 
187         final DefaultPluginManager defaultPluginManager = new DefaultPluginManager(
188                 pluginPersistentStateStore,
189                 singletonList(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(singletonList(pluginV2));
223 
224         final DefaultPluginManager defaultPluginManager = new DefaultPluginManager(
225                 pluginPersistentStateStore,
226                 singletonList(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 = newPluginMock();
264         when(pluginAlpha.getKey()).thenReturn("alpha");
265         when(pluginLoaderAlpha.loadFoundPlugins(isA(ModuleDescriptorFactory.class))).thenReturn(singletonList(pluginAlpha));
266         final Plugin pluginBeta = newPluginMock();
267         when(pluginBeta.getKey()).thenReturn("beta");
268         when(pluginLoaderBeta.loadFoundPlugins(isA(ModuleDescriptorFactory.class))).thenReturn(singletonList(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(singletonList(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(PluginDisablingEvent.class, dependentPlugin),
370                 pluginStateChange(dependentPlugin, PluginState.DISABLED),
371                 pluginEvent(PluginDisabledEvent.class, dependentPlugin),
372                 pluginEvent(PluginUpgradingEvent.class, pluginV1),
373                 pluginEvent(PluginDisablingEvent.class, pluginV1),
374                 pluginStateChange(pluginV1, PluginState.DISABLED),
375                 pluginEvent(PluginDisabledEvent.class, pluginV1),
376                 pluginStateChange(pluginV2, PluginState.INSTALLED),
377                 pluginEvent(PluginUpgradedEvent.class, pluginV2),
378                 pluginEvent(PluginEnablingEvent.class, pluginV2),
379                 pluginEvent(PluginEnablingEvent.class, dependentPlugin),
380                 pluginStateChange(pluginV2, PluginState.ENABLED),
381                 pluginStateChange(dependentPlugin, PluginState.ENABLED),
382                 pluginEvent(PluginEnabledEvent.class, pluginV2),
383                 pluginEvent(PluginEnabledEvent.class, dependentPlugin),
384                 pluginEvent(PluginDependentsChangedEvent.class, pluginV2)
385         ));
386     }
387 
388     @Test
389     public void uninstallEventSequencing() {
390         final PluginEventManager pluginEventManager = mock(PluginEventManager.class);
391 
392         final String pluginKey = "pluginKey";
393         final Plugin plugin = mockStateChangePlugin(pluginKey, pluginEventManager);
394 
395         final Plugin dependentPlugin = mockStateChangePlugin("dependentPluginKey", pluginEventManager);
396         when(dependentPlugin.getDependencies()).thenReturn(new PluginDependencies(ImmutableSet.of(pluginKey), null, null));
397 
398         mockPluginsSortOrder(dependentPlugin, plugin);
399 
400         final PluginPersistentStateStore pluginPersistentStateStore = mockPluginPersistentStateStore();
401 
402         final PluginLoader pluginLoader = mockPluginLoaderForPlugins(plugin, dependentPlugin);
403         when(pluginLoader.supportsRemoval()).thenReturn(true);
404 
405         final DefaultPluginManager defaultPluginManager = new DefaultPluginManager(
406                 pluginPersistentStateStore,
407                 ImmutableList.of(pluginLoader),
408                 mock(ModuleDescriptorFactory.class),
409                 pluginEventManager,
410                 mock(PluginExceptionInterception.class)
411         );
412 
413         defaultPluginManager.init();
414 
415         final ArgumentCaptor<Object> events = ArgumentCaptor.forClass(Object.class);
416         doNothing().when(pluginEventManager).broadcast(events.capture());
417 
418         defaultPluginManager.uninstall(defaultPluginManager.getPlugin(pluginKey));
419 
420         assertThat(filter(events.getAllValues(), PluginEvent.class), contains(
421                 pluginEvent(PluginDisablingEvent.class, dependentPlugin),
422                 pluginStateChange(dependentPlugin, PluginState.DISABLED),
423                 pluginEvent(PluginDisabledEvent.class, dependentPlugin),
424                 pluginEvent(PluginUninstallingEvent.class, plugin),
425                 pluginEvent(PluginDisablingEvent.class, plugin),
426                 pluginStateChange(plugin, PluginState.DISABLED),
427                 pluginEvent(PluginDisabledEvent.class, plugin),
428                 pluginEvent(PluginUninstalledEvent.class, plugin),
429                 pluginEvent(PluginDependentsChangedEvent.class, plugin)
430         ));
431     }
432 
433     @Test
434     public void uninstallPluginsEventSequencing() {
435         final PluginEventManager pluginEventManager = mock(PluginEventManager.class);
436 
437         final String pluginKey = "pluginKey";
438         final Plugin plugin = mockStateChangePlugin(pluginKey, pluginEventManager);
439 
440         final Plugin dependentPlugin = mockStateChangePlugin("dependentPluginKey", pluginEventManager);
441         when(dependentPlugin.getDependencies()).thenReturn(new PluginDependencies(ImmutableSet.of(pluginKey), null, null));
442 
443         final String dependent2PluginKey = "dependent2PluginKey";
444         final Plugin dependent2Plugin = mockStateChangePlugin(dependent2PluginKey, pluginEventManager);
445         when(dependent2Plugin.getDependencies()).thenReturn(new PluginDependencies(ImmutableSet.of(pluginKey), null, null));
446 
447         mockPluginsSortOrder(dependentPlugin, dependent2Plugin, plugin);
448 
449         final PluginPersistentStateStore pluginPersistentStateStore = mockPluginPersistentStateStore();
450 
451         final PluginLoader pluginLoader = mockPluginLoaderForPlugins(plugin, dependentPlugin, dependent2Plugin);
452         when(pluginLoader.supportsRemoval()).thenReturn(true);
453 
454         final DefaultPluginManager defaultPluginManager = new DefaultPluginManager(
455                 pluginPersistentStateStore,
456                 ImmutableList.of(pluginLoader),
457                 mock(ModuleDescriptorFactory.class),
458                 pluginEventManager,
459                 mock(PluginExceptionInterception.class)
460         );
461 
462         defaultPluginManager.init();
463 
464         final ArgumentCaptor<Object> events = ArgumentCaptor.forClass(Object.class);
465         doNothing().when(pluginEventManager).broadcast(events.capture());
466 
467         Plugin[] uninstalled = new Plugin[]{plugin, dependent2Plugin};
468 
469         defaultPluginManager.uninstallPlugins(Arrays.asList(uninstalled));
470 
471 
472         List<PluginEvent> pluginEvents = events.getAllValues().stream()
473                 .filter(PluginEvent.class::isInstance)
474                 .map(PluginEvent.class::cast)
475                 .collect(Collectors.toList());
476 
477         // Check event sequencing for each of the disabled plugins
478         Arrays.stream(uninstalled)
479                 .forEach(p -> assertThat(
480                         pluginEvents.stream()
481                                 .filter(e -> e.getPlugin() == p)
482                                 .collect(Collectors.toList()),
483                         contains(
484                                 pluginEvent(PluginUninstallingEvent.class, p),
485                                 pluginEvent(PluginDisablingEvent.class, p),
486                                 pluginStateChange(p, PluginState.DISABLED),
487                                 pluginEvent(PluginDisabledEvent.class, p),
488                                 pluginEvent(PluginUninstalledEvent.class, p),
489                                 pluginEvent(PluginDependentsChangedEvent.class, p)
490                         )));
491 
492         // check event sequencing for all plugins in relation to each other
493         assertThat(pluginEvents,
494                 contains(
495                         // dependent is disabled
496                         pluginEvent(PluginDisablingEvent.class, dependentPlugin),
497                         pluginStateChange(dependentPlugin, PluginState.DISABLED),
498                         pluginEvent(PluginDisabledEvent.class, dependentPlugin),
499 
500                         // uninstalling for both
501                         anyPluginEvent(PluginUninstallingEvent.class, uninstalled),
502                         anyPluginEvent(PluginUninstallingEvent.class, uninstalled),
503 
504                         // disabling sequence for one of them
505                         anyPluginEvent(PluginDisablingEvent.class, uninstalled),
506                         anyPluginStateChange(PluginState.DISABLED, uninstalled),
507                         anyPluginEvent(PluginDisabledEvent.class, uninstalled),
508 
509                         // disabling sequence for the second one
510                         anyPluginEvent(PluginDisablingEvent.class, uninstalled),
511                         anyPluginStateChange(PluginState.DISABLED, uninstalled),
512                         anyPluginEvent(PluginDisabledEvent.class, uninstalled),
513 
514                         //uninstalled for both
515                         anyPluginEvent(PluginUninstalledEvent.class, uninstalled),
516                         anyPluginEvent(PluginUninstalledEvent.class, uninstalled),
517 
518                         // dependent changed
519                         anyPluginEvent(PluginDependentsChangedEvent.class, uninstalled),
520                         anyPluginEvent(PluginDependentsChangedEvent.class, uninstalled)
521                 ));
522     }
523 
524     @Test
525     public void moduleEnableDisableEventSequencing() {
526         final PluginEventManager pluginEventManager = mock(PluginEventManager.class);
527 
528         final String pluginKey = "pluginKey";
529         final Plugin plugin = mockStateChangePlugin(pluginKey, pluginEventManager);
530 
531         final String moduleKeyAlpha = "alpha";
532         final ModuleDescriptor moduleAlpha = mockStateChangePluginModule(pluginKey, moduleKeyAlpha, pluginEventManager);
533         final String moduleKeyBeta = "beta";
534         final ModuleDescriptor moduleBeta = mockStateChangePluginModule(pluginKey, moduleKeyBeta, pluginEventManager);
535 
536         when(plugin.getModuleDescriptors()).thenReturn(ImmutableList.of(moduleAlpha, moduleBeta));
537         when(plugin.getModuleDescriptor(moduleKeyAlpha)).thenReturn(moduleAlpha);
538         when(plugin.getModuleDescriptor(moduleKeyBeta)).thenReturn(moduleBeta);
539 
540         final PluginPersistentStateStore pluginPersistentStateStore = mockPluginPersistentStateStore();
541 
542         final PluginLoader pluginLoader = mockPluginLoaderForPlugins(plugin);
543 
544         final DefaultPluginManager defaultPluginManager = new DefaultPluginManager(
545                 pluginPersistentStateStore,
546                 ImmutableList.of(pluginLoader),
547                 mock(ModuleDescriptorFactory.class),
548                 pluginEventManager,
549                 mock(PluginExceptionInterception.class)
550         );
551 
552         defaultPluginManager.init();
553 
554         final ArgumentCaptor<Object> disableEventsCaptor = ArgumentCaptor.forClass(Object.class);
555         doNothing().when(pluginEventManager).broadcast(disableEventsCaptor.capture());
556 
557         defaultPluginManager.disablePlugin(pluginKey);
558 
559         final List<Object> disableEvents = disableEventsCaptor.getAllValues();
560         final Iterable<PluginModuleEvent> pluginModuleDisableEvents = filter(disableEvents, PluginModuleEvent.class);
561         assertThat(pluginModuleDisableEvents, contains(
562                 pluginModuleEvent(PluginModuleDisablingEvent.class, moduleBeta),
563                 pluginModuleStateChange(moduleBeta, false),
564                 pluginModuleEvent(PluginModuleDisabledEvent.class, moduleBeta),
565                 pluginModuleEvent(PluginModuleDisablingEvent.class, moduleAlpha),
566                 pluginModuleStateChange(moduleAlpha, false),
567                 pluginModuleEvent(PluginModuleDisabledEvent.class, moduleAlpha)
568         ));
569 
570         // We now want to check that the plugin events surround the module events
571         final int pluginDisablingIndex = indexOf(disableEvents, Predicates.instanceOf(PluginDisablingEvent.class));
572         final int firstPluginModuleDisableEvent = disableEvents.indexOf(get(pluginModuleDisableEvents, 0));
573         assertThat(firstPluginModuleDisableEvent, greaterThan(pluginDisablingIndex));
574         final int lastPluginModuleDisableEventIndex = disableEvents.indexOf(getLast(pluginModuleDisableEvents));
575         final int pluginDisabledIndex = indexOf(disableEvents, Predicates.instanceOf(PluginDisabledEvent.class));
576         assertThat(lastPluginModuleDisableEventIndex, lessThan(pluginDisabledIndex));
577 
578         final ArgumentCaptor<Object> enableEventsCaptor = ArgumentCaptor.forClass(Object.class);
579         doNothing().when(pluginEventManager).broadcast(enableEventsCaptor.capture());
580 
581         defaultPluginManager.enablePlugins(pluginKey);
582 
583         final List<Object> enableEvents = enableEventsCaptor.getAllValues();
584         final Iterable<PluginModuleEvent> pluginModuleEnableEvents = filter(enableEvents, PluginModuleEvent.class);
585         assertThat(pluginModuleEnableEvents, contains(
586                 pluginModuleEvent(PluginModuleEnablingEvent.class, moduleAlpha),
587                 pluginModuleStateChange(moduleAlpha, true),
588                 pluginModuleEvent(PluginModuleEnabledEvent.class, moduleAlpha),
589                 pluginModuleEvent(PluginModuleEnablingEvent.class, moduleBeta),
590                 pluginModuleStateChange(moduleBeta, true),
591                 pluginModuleEvent(PluginModuleEnabledEvent.class, moduleBeta)
592         ));
593 
594         // We now want to check that the plugin events surround the module events
595         final int pluginEnablingIndex = indexOf(disableEvents, Predicates.instanceOf(PluginDisablingEvent.class));
596         final int firstPluginModuleEnableEvent = disableEvents.indexOf(get(pluginModuleDisableEvents, 0));
597         assertThat(firstPluginModuleEnableEvent, greaterThan(pluginEnablingIndex));
598         final int lastPluginModuleEnableEventIndex = disableEvents.indexOf(getLast(pluginModuleDisableEvents));
599         final int pluginEnabledIndex = indexOf(disableEvents, Predicates.instanceOf(PluginDisabledEvent.class));
600         assertThat(lastPluginModuleEnableEventIndex, lessThan(pluginEnabledIndex));
601     }
602 
603     @Test
604     public void shutdownEventsAreSent() {
605         final PluginPersistentStateStore pluginPersistentStateStore = mockPluginPersistentStateStore();
606         final List<PluginLoader> pluginLoaders = emptyList();
607         final ModuleDescriptorFactory moduleDescriptorFactory = mock(ModuleDescriptorFactory.class);
608         final PluginEventManager pluginEventManager = mock(PluginEventManager.class);
609 
610         final DefaultPluginManager defaultPluginManager = new DefaultPluginManager(
611                 pluginPersistentStateStore, pluginLoaders, moduleDescriptorFactory, pluginEventManager);
612 
613         defaultPluginManager.init();
614 
615         // Make PluginEventManager#broadcast throw during shutdown to validate the exception handling in DefaultPluginManager
616         final ArgumentCaptor<Object> shutdownEvents = ArgumentCaptor.forClass(Object.class);
617         final NotificationException notificationException = new NotificationException(new Throwable());
618         doThrow(notificationException).when(pluginEventManager).broadcast(shutdownEvents.capture());
619 
620         defaultPluginManager.shutdown();
621 
622         // Check that both broadcasts were attempted
623         assertThat(shutdownEvents.getAllValues(), contains(
624                 instanceOf(PluginFrameworkShuttingDownEvent.class), instanceOf(PluginFrameworkShutdownEvent.class)));
625     }
626 
627     @Test
628     public void enableAndDisableEventSequencing() {
629         final PluginEventManager pluginEventManager = mock(PluginEventManager.class);
630 
631         final String pluginKey = "pluginKey";
632         final Plugin plugin = mockStateChangePlugin(pluginKey, pluginEventManager);
633 
634         final PluginPersistentStateStore pluginPersistentStateStore = mockPluginPersistentStateStore();
635 
636         final PluginLoader pluginLoader = mockPluginLoaderForPlugins(plugin);
637 
638         final DefaultPluginManager defaultPluginManager = new DefaultPluginManager(
639                 pluginPersistentStateStore,
640                 ImmutableList.of(pluginLoader),
641                 mock(ModuleDescriptorFactory.class),
642                 pluginEventManager,
643                 mock(PluginExceptionInterception.class)
644         );
645 
646         defaultPluginManager.init();
647 
648         final ArgumentCaptor<Object> disableEvents = ArgumentCaptor.forClass(Object.class);
649         doNothing().when(pluginEventManager).broadcast(disableEvents.capture());
650 
651         defaultPluginManager.disablePlugin(pluginKey);
652 
653         assertThat(filter(disableEvents.getAllValues(), PluginEvent.class), contains(
654                 pluginEvent(PluginDisablingEvent.class, plugin),
655                 pluginStateChange(plugin, PluginState.DISABLED),
656                 pluginEvent(PluginDisabledEvent.class, plugin)
657         ));
658 
659         final ArgumentCaptor<Object> enableEvents = ArgumentCaptor.forClass(Object.class);
660         doNothing().when(pluginEventManager).broadcast(enableEvents.capture());
661 
662         defaultPluginManager.enablePlugins(pluginKey);
663 
664         assertThat(filter(enableEvents.getAllValues(), PluginEvent.class), contains(
665                 pluginEvent(PluginEnablingEvent.class, plugin),
666                 pluginStateChange(plugin, PluginState.ENABLED),
667                 pluginEvent(PluginEnabledEvent.class, plugin)
668         ));
669     }
670 
671     @Test
672     public void upgradePluginUpgradesPlugin() {
673         final String pluginKey = "pluginKey";
674         final PluginPersistentStateStore pluginPersistentStateStore = mock(
675                 PluginPersistentStateStore.class, RETURNS_DEEP_STUBS);
676         when(pluginPersistentStateStore.load().getPluginRestartState(pluginKey)).thenReturn(PluginRestartState.NONE);
677 
678         Plugin pluginV1 = newPluginMock();
679         Plugin pluginV2 = newPluginMock();
680         when(pluginV1.getKey()).thenReturn(pluginKey);
681         when(pluginV2.getKey()).thenReturn(pluginKey);
682         when(pluginV1.isDeleteable()).thenReturn(true);
683         when(pluginV1.isUninstallable()).thenReturn(true);
684         // Set up so that pluginV1 < pluginV2
685         when(pluginV1.compareTo(pluginV2)).thenReturn(-1);
686         when(pluginV2.compareTo(pluginV1)).thenReturn(1);
687 
688         DiscardablePluginLoader pluginLoader = mock(DiscardablePluginLoader.class);
689         when(pluginLoader.supportsAddition()).thenReturn(true);
690         when(pluginLoader.supportsRemoval()).thenReturn(true);
691         when(pluginLoader.loadAllPlugins(isA(ModuleDescriptorFactory.class))).thenReturn(singletonList(pluginV1));
692         when(pluginLoader.loadFoundPlugins(isA(ModuleDescriptorFactory.class))).thenReturn(singletonList(pluginV2));
693 
694         final DefaultPluginManager defaultPluginManager = new DefaultPluginManager(
695                 pluginPersistentStateStore,
696                 singletonList(pluginLoader),
697                 mock(ModuleDescriptorFactory.class),
698                 mock(PluginEventManager.class),
699                 mock(PluginExceptionInterception.class)
700         );
701         defaultPluginManager.init();
702 
703         assertThat(defaultPluginManager.getPlugin(pluginKey), is(pluginV1));
704 
705         final int found = defaultPluginManager.scanForNewPlugins();
706 
707         assertThat(found, is(1));
708         assertThat(defaultPluginManager.getPlugin(pluginKey), is(pluginV2));
709 
710         verify(pluginLoader).removePlugin(pluginV1);
711     }
712 
713     @Test
714     public void uninstallingNotDeletableUninstallablePluginRemovesItFromLoader() {
715         final Plugin plugin = newPluginMock();
716         final PluginLoader pluginLoader = mock(PluginLoader.class);
717         final DefaultPluginManager defaultPluginManager = setupUninstallTest(false, true, plugin, pluginLoader);
718         defaultPluginManager.uninstall(plugin);
719         verify(pluginLoader).removePlugin(plugin);
720     }
721 
722     @Test
723     public void uninstallingDeletableUninstallablePluginRemovesItFromLoader() {
724         final Plugin plugin = newPluginMock();
725         final PluginLoader pluginLoader = mock(PluginLoader.class);
726         final DefaultPluginManager defaultPluginManager = setupUninstallTest(true, true, plugin, pluginLoader);
727         defaultPluginManager.uninstall(plugin);
728         verify(pluginLoader).removePlugin(plugin);
729     }
730 
731     @Test
732     public void uninstallingDeletableNotUninstallablePluginDoesNotRemoveItFromLoader() {
733         final Plugin plugin = newPluginMock();
734         final PluginLoader pluginLoader = mock(PluginLoader.class);
735         final DefaultPluginManager defaultPluginManager = setupUninstallTest(true, false, plugin, pluginLoader);
736         // We want to ensure the removal is not attempted, so we make removal throw something unexpected
737         doThrow(new AssertionError("Unexpected PluginLoader.removePlugin call")).when(pluginLoader).removePlugin(plugin);
738         expectedException.expect(PluginException.class);
739         expectedException.expectMessage(plugin.getKey());
740         defaultPluginManager.uninstall(plugin);
741     }
742 
743     @Test
744     public void uninstallingNotDeletableNotUninstallablePluginDoesNotRemoveItFromLoader() {
745         final Plugin plugin = newPluginMock();
746         final PluginLoader pluginLoader = mock(PluginLoader.class);
747         final DefaultPluginManager defaultPluginManager = setupUninstallTest(false, 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     private DefaultPluginManager setupUninstallTest(
756             final boolean isDeleteable,
757             final boolean isUninstallable,
758             final Plugin plugin,
759             final PluginLoader pluginLoader) {
760         final String pluginKey = "uninstall-test-plugin-key";
761         when(plugin.getKey()).thenReturn(pluginKey);
762         when(plugin.toString()).thenReturn(pluginKey);
763         when(plugin.isDeleteable()).thenReturn(isDeleteable);
764         when(plugin.isUninstallable()).thenReturn(isUninstallable);
765         when(pluginLoader.loadAllPlugins(isA(ModuleDescriptorFactory.class))).thenReturn(singletonList(plugin));
766         when(pluginLoader.supportsRemoval()).thenReturn(true);
767 
768         final DefaultPluginManager defaultPluginManager = new DefaultPluginManager(
769                 mock(PluginPersistentStateStore.class, RETURNS_DEEP_STUBS),
770                 singletonList(pluginLoader),
771                 mock(ModuleDescriptorFactory.class),
772                 mock(PluginEventManager.class),
773                 mock(PluginExceptionInterception.class)
774         );
775         defaultPluginManager.init();
776         return defaultPluginManager;
777     }
778 
779     @Test
780     public void addDynamicModuleNoLoader() {
781         final PluginLoader pluginLoader = mock(PluginLoader.class);
782         final PluginInternal plugin = mock(PluginInternal.class);
783 
784         manager = newDefaultPluginManager(pluginLoader);
785 
786         when(plugin.toString()).thenReturn("bleh");
787 
788         expectedException.expect(PluginException.class);
789         expectedException.expectMessage(containsString("bleh"));
790 
791         manager.addDynamicModule(plugin, mock(Element.class));
792     }
793 
794 
795     @Test
796     public void removeDynamicModuleNotPresent() {
797         final PluginLoader pluginLoader = mock(PluginLoader.class);
798         final PluginInternal plugin = mock(PluginInternal.class);
799         final ModuleDescriptor moduleDescriptor = mock(ModuleDescriptor.class, withSettings().extraInterfaces(StateAware.class));
800 
801         manager = newDefaultPluginManager(pluginLoader);
802 
803         when(plugin.removeDynamicModuleDescriptor(moduleDescriptor)).thenReturn(false);
804         when(plugin.toString()).thenReturn("crapPlugin");
805         when(moduleDescriptor.getKey()).thenReturn("moduleKey");
806 
807         expectedException.expect(PluginException.class);
808         expectedException.expectMessage(containsString("crapPlugin"));
809         expectedException.expectMessage(containsString("moduleKey"));
810 
811         manager.removeDynamicModule(plugin, moduleDescriptor);
812 
813         verify((StateAware) moduleDescriptor, never()).enabled();
814     }
815 
816     @Test
817     public void removeDynamicModule() {
818         final PluginLoader pluginLoader = mock(PluginLoader.class);
819         final PluginInternal plugin = mock(PluginInternal.class);
820         final ModuleDescriptor<?> moduleDescriptor = mock(ModuleDescriptor.class, withSettings().extraInterfaces(StateAware.class));
821 
822         manager = newDefaultPluginManager(pluginLoader);
823 
824         when(plugin.removeDynamicModuleDescriptor(moduleDescriptor)).thenReturn(true);
825 
826         manager.removeDynamicModule(plugin, moduleDescriptor);
827 
828         verify(plugin).removeDynamicModuleDescriptor(moduleDescriptor);
829         verify(moduleDescriptor).destroy();
830         verify((StateAware) moduleDescriptor).disabled();
831     }
832 
833     @Test
834     public void testUninstallPluginWithMultiLevelDependencies() throws PluginException {
835 
836         Plugin child = mock(Plugin.class);
837         when(child.getKey()).thenReturn("child");
838         when(child.isEnabledByDefault()).thenReturn(true);
839         when(child.getPluginState()).thenReturn(PluginState.ENABLED);
840         when(child.getDependencies()).thenReturn(new PluginDependencies(ImmutableSet.of("parent"), null, null));
841         when(child.compareTo(any(Plugin.class))).thenReturn(-1);
842 
843         Plugin parent = mock(Plugin.class);
844         when(parent.getKey()).thenReturn("parent");
845         when(parent.isEnabledByDefault()).thenReturn(true);
846         when(parent.getPluginState()).thenReturn(PluginState.ENABLED);
847         when(parent.getDependencies()).thenReturn(new PluginDependencies(ImmutableSet.of("grandparent"), null, null));
848         when(parent.compareTo(any(Plugin.class))).thenReturn(-1);
849 
850         Plugin grandparent = mock(Plugin.class);
851         when(grandparent.getKey()).thenReturn("grandparent");
852         when(grandparent.isEnabledByDefault()).thenReturn(true);
853         when(grandparent.isDeleteable()).thenReturn(true);
854         when(grandparent.isUninstallable()).thenReturn(true);
855         when(grandparent.getPluginState()).thenReturn(PluginState.ENABLED);
856         when(grandparent.compareTo(any(Plugin.class))).thenReturn(-1);
857         when(grandparent.getDependencies()).thenReturn(new PluginDependencies());
858 
859         PluginLoader pluginLoader = mockPluginLoaderForPlugins(child, parent, grandparent);
860         when(pluginLoader.supportsRemoval()).thenReturn(true);
861 
862         manager = initNewDefaultPluginManager(pluginLoader);
863 
864         manager.uninstall(grandparent);
865         verify(grandparent).enable();
866         verify(grandparent).disable();
867         verify(pluginLoader).removePlugin(grandparent);
868 
869         verify(parent).enable();
870         verify(parent).disable();
871         verify(child).enable();
872         verify(child).disable();
873     }
874 
875     @Test
876     public void testUninstallPluginWithDependencies() throws PluginException {
877         Plugin child = mock(Plugin.class);
878         when(child.getKey()).thenReturn("child");
879         when(child.isEnabledByDefault()).thenReturn(true);
880         when(child.getPluginState()).thenReturn(PluginState.ENABLED);
881         when(child.getDependencies()).thenReturn(new PluginDependencies(ImmutableSet.of("parent"), null, null));
882         when(child.compareTo(any(Plugin.class))).thenReturn(-1);
883         Plugin parent = mock(Plugin.class);
884         when(parent.getKey()).thenReturn("parent");
885         when(parent.isEnabledByDefault()).thenReturn(true);
886         when(parent.isDeleteable()).thenReturn(true);
887         when(parent.isUninstallable()).thenReturn(true);
888         when(parent.getPluginState()).thenReturn(PluginState.ENABLED);
889         when(parent.compareTo(any(Plugin.class))).thenReturn(-1);
890         when(parent.getDependencies()).thenReturn(new PluginDependencies());
891 
892         PluginLoader pluginLoader = mockPluginLoaderForPlugins(child, parent);
893         when(pluginLoader.supportsRemoval()).thenReturn(true);
894 
895         manager = initNewDefaultPluginManager(pluginLoader);
896 
897         manager.uninstall(parent);
898         verify(parent).enable();
899         verify(parent).disable();
900         verify(pluginLoader).removePlugin(parent);
901 
902         verify(child).enable();
903         verify(child).disable();
904     }
905 
906     @Test
907     public void testInstallPluginsWithTwoButOneFailsValidationWithException() {
908         DynamicPluginLoader loader = mock(DynamicPluginLoader.class);
909         when(loader.isDynamicPluginLoader()).thenReturn(true);
910 
911         moduleDescriptorFactory = mock(ModuleDescriptorFactory.class);
912         PluginInstaller installer = mock(PluginInstaller.class);
913         DefaultPluginManager pm = newDefaultPluginManager(loader);
914         pm.setPluginInstaller(installer);
915         PluginArtifact artifactA = mock(PluginArtifact.class);
916         Plugin pluginA = mock(Plugin.class);
917         when(loader.canLoad(artifactA)).thenReturn("a");
918         PluginArtifact artifactB = mock(PluginArtifact.class);
919         Plugin pluginB = mock(Plugin.class);
920         doThrow(new PluginParseException()).when(loader).canLoad(artifactB);
921 
922         when(loader.loadFoundPlugins(moduleDescriptorFactory)).thenReturn(Arrays.asList(pluginA, pluginB));
923 
924         try {
925             manager.installPlugins(artifactA, artifactB);
926             fail("Should have not installed plugins");
927         } catch (PluginParseException ex) {
928             // this is good
929         }
930 
931         verify(loader).canLoad(artifactA);
932         verify(loader).canLoad(artifactB);
933         verify(installer, never()).installPlugin("a", artifactA);
934         verify(installer, never()).installPlugin("b", artifactB);
935     }
936 
937     @Test
938     public void testInstallPlugin() {
939         final PluginPersistentStateStore mockPluginStateStore = mock(PluginPersistentStateStore.class);
940         final ModuleDescriptorFactory moduleDescriptorFactory = mock(ModuleDescriptorFactory.class);
941         final DynamicPluginLoader mockPluginLoader = mock(DynamicPluginLoader.class);
942         when(mockPluginLoader.isDynamicPluginLoader()).thenReturn(true);
943 
944         final DescriptorParser mockDescriptorParser = mock(DescriptorParser.class);
945         final PluginArtifact pluginArtifact = mock(PluginArtifact.class);
946         final PluginInstaller mockRepository = mock(PluginInstaller.class);
947         final Plugin plugin = mock(Plugin.class);
948 
949 
950         final DefaultPluginManager pluginManager = new DefaultPluginManager(mockPluginStateStore,
951                 singletonList(mockPluginLoader), moduleDescriptorFactory, pluginEventManager);
952 
953         when(mockPluginStateStore.load()).thenReturn(PluginPersistentState.builder().toState());
954         when(mockPluginStateStore.load()).thenReturn(PluginPersistentState.builder().toState());
955         when(mockPluginStateStore.load()).thenReturn(PluginPersistentState.builder().toState());
956         when(mockDescriptorParser.getKey()).thenReturn("test");
957         when(mockPluginLoader.loadAllPlugins(moduleDescriptorFactory)).thenReturn(emptyList());
958         when(mockPluginLoader.supportsAddition()).thenReturn(true);
959         when(mockPluginLoader.loadFoundPlugins(moduleDescriptorFactory)).thenReturn(singletonList(plugin));
960         when(mockPluginLoader.canLoad(pluginArtifact)).thenReturn("test");
961         when(plugin.getKey()).thenReturn("test");
962         when(plugin.getModuleDescriptors()).thenReturn(new ArrayList<>());
963         when(plugin.getModuleDescriptors()).thenReturn(new ArrayList<>());
964         when(plugin.isEnabledByDefault()).thenReturn(true);
965 
966         when(plugin.isEnabledByDefault()).thenReturn(true);
967         when(plugin.getPluginState()).thenReturn(PluginState.ENABLED);
968         when(plugin.hasAllPermissions()).thenReturn(true);
969         when(plugin.getActivePermissions()).thenReturn(ImmutableSet.of(Permissions.ALL_PERMISSIONS));
970         when(plugin.getDependencies()).thenReturn(new PluginDependencies());
971 
972         pluginManager.setPluginInstaller(mockRepository);
973         pluginManager.init();
974         final PassListener enabledListener = new PassListener(PluginEnabledEvent.class);
975         pluginEventManager.register(enabledListener);
976         pluginManager.installPlugins(pluginArtifact);
977 
978         assertEquals(plugin, pluginManager.getPlugin("test"));
979         assertTrue(pluginManager.isPluginEnabled("test"));
980 
981         enabledListener.assertCalled();
982     }
983 
984     @Test
985     public void testInstallPluginsWithOne() {
986         DynamicPluginLoader loader = mock(DynamicPluginLoader.class);
987         when(loader.isDynamicPluginLoader()).thenReturn(true);
988 
989         moduleDescriptorFactory = mock(ModuleDescriptorFactory.class);
990         PluginInstaller installer = mock(PluginInstaller.class);
991         DefaultPluginManager pm = newDefaultPluginManager(loader);
992         pm.setPluginInstaller(installer);
993         when(loader.loadAllPlugins(moduleDescriptorFactory)).thenReturn(emptyList());
994         PluginArtifact artifact = mock(PluginArtifact.class);
995         Plugin plugin = mock(Plugin.class);
996         when(loader.canLoad(artifact)).thenReturn("foo");
997         when(loader.loadFoundPlugins(moduleDescriptorFactory)).thenReturn(singletonList(plugin));
998 
999         pm.init();
1000         manager.installPlugins(artifact);
1001 
1002         verify(loader).canLoad(artifact);
1003         verify(installer).installPlugin("foo", artifact);
1004     }
1005 
1006     @Test
1007     public void testInstallPluginsWithTwo() {
1008         DynamicPluginLoader loader = mock(DynamicPluginLoader.class);
1009         when(loader.isDynamicPluginLoader()).thenReturn(true);
1010 
1011         moduleDescriptorFactory = mock(ModuleDescriptorFactory.class);
1012         PluginInstaller installer = mock(PluginInstaller.class);
1013         DefaultPluginManager pm = newDefaultPluginManager(loader);
1014         pm.setPluginInstaller(installer);
1015         when(loader.loadAllPlugins(moduleDescriptorFactory)).thenReturn(emptyList());
1016         PluginArtifact artifactA = mock(PluginArtifact.class);
1017         Plugin pluginA = mock(Plugin.class);
1018         when(loader.canLoad(artifactA)).thenReturn("a");
1019         PluginArtifact artifactB = mock(PluginArtifact.class);
1020         Plugin pluginB = mock(Plugin.class);
1021         when(loader.canLoad(artifactB)).thenReturn("b");
1022 
1023         when(loader.loadFoundPlugins(moduleDescriptorFactory)).thenReturn(Arrays.asList(pluginA, pluginB));
1024 
1025         pm.init();
1026         manager.installPlugins(artifactA, artifactB);
1027 
1028         verify(loader).canLoad(artifactA);
1029         verify(loader).canLoad(artifactB);
1030         verify(installer).installPlugin("a", artifactA);
1031         verify(installer).installPlugin("b", artifactB);
1032     }
1033 
1034     @Test
1035     public void testInstallPluginsWithTwoButOneFailsValidation() {
1036         DynamicPluginLoader loader = mock(DynamicPluginLoader.class);
1037         when(loader.isDynamicPluginLoader()).thenReturn(true);
1038 
1039         ModuleDescriptorFactory descriptorFactory = mock(ModuleDescriptorFactory.class);
1040         PluginInstaller installer = mock(PluginInstaller.class);
1041         DefaultPluginManager pm = newDefaultPluginManager(loader);
1042         pm.setPluginInstaller(installer);
1043         PluginArtifact artifactA = mock(PluginArtifact.class);
1044         Plugin pluginA = mock(Plugin.class);
1045         when(loader.canLoad(artifactA)).thenReturn("a");
1046         PluginArtifact artifactB = mock(PluginArtifact.class);
1047         Plugin pluginB = mock(Plugin.class);
1048         when(loader.canLoad(artifactB)).thenReturn(null);
1049 
1050         when(loader.loadFoundPlugins(descriptorFactory)).thenReturn(Arrays.asList(pluginA, pluginB));
1051 
1052         try {
1053             manager.installPlugins(artifactA, artifactB);
1054             fail("Should have not installed plugins");
1055         } catch (PluginParseException ex) {
1056             // this is good
1057         }
1058 
1059         verify(loader).canLoad(artifactA);
1060         verify(loader).canLoad(artifactB);
1061         verify(installer, never()).installPlugin("a", artifactA);
1062         verify(installer, never()).installPlugin("b", artifactB);
1063     }
1064 
1065     @Test
1066     public void testCircularDependencyWouldNotCauseInfiniteLoop() throws PluginException {
1067 
1068         Plugin p1 = mock(Plugin.class);
1069         when(p1.getKey()).thenReturn("p1");
1070         when(p1.isEnabledByDefault()).thenReturn(true);
1071         when(p1.getPluginState()).thenReturn(PluginState.ENABLED);
1072         when(p1.getDependencies()).thenReturn(new PluginDependencies(ImmutableSet.of("p2", "parent"), null, null));
1073         when(p1.compareTo(any(Plugin.class))).thenReturn(-1);
1074 
1075         // Create a circular dependency between p1 and p2. This should not happen, but test anyway.
1076         Plugin p2 = mock(Plugin.class);
1077         when(p2.getKey()).thenReturn("p2");
1078         when(p2.isEnabledByDefault()).thenReturn(true);
1079         when(p2.getPluginState()).thenReturn(PluginState.ENABLED);
1080         when(p2.getDependencies()).thenReturn(new PluginDependencies(ImmutableSet.of("p1"), null, null));
1081         when(p2.compareTo(any(Plugin.class))).thenReturn(-1);
1082 
1083         Plugin parent = mock(Plugin.class);
1084         when(parent.getKey()).thenReturn("parent");
1085         when(parent.isEnabledByDefault()).thenReturn(true);
1086         when(parent.isDeleteable()).thenReturn(true);
1087         when(parent.isUninstallable()).thenReturn(true);
1088         when(parent.getPluginState()).thenReturn(PluginState.ENABLED);
1089         when(parent.compareTo(any(Plugin.class))).thenReturn(-1);
1090         when(parent.getDependencies()).thenReturn(new PluginDependencies());
1091 
1092         PluginLoader pluginLoader = mockPluginLoaderForPlugins(p1, p2, parent);
1093         when(pluginLoader.supportsRemoval()).thenReturn(true);
1094 
1095         manager = initNewDefaultPluginManager(pluginLoader);
1096 
1097         manager.uninstall(parent);
1098         verify(parent, times(1)).enable();
1099         verify(parent, times(1)).disable();
1100         verify(pluginLoader).removePlugin(parent);
1101 
1102         verify(p1, times(1)).enable();
1103         verify(p1, times(1)).disable();
1104         verify(p2, times(1)).enable();
1105         verify(p2, times(1)).disable();
1106     }
1107 
1108     @Test
1109     public void testThreeCycleDependencyWouldNotCauseInfiniteLoop() throws PluginException {
1110         Plugin p1 = mock(Plugin.class);
1111         when(p1.getKey()).thenReturn("p1");
1112         when(p1.isEnabledByDefault()).thenReturn(true);
1113         when(p1.getPluginState()).thenReturn(PluginState.ENABLED);
1114         when(p1.getDependencies()).thenReturn(new PluginDependencies(ImmutableSet.of("p2", "parent"), null, null));
1115         when(p1.compareTo(any(Plugin.class))).thenReturn(-1);
1116 
1117         // Create a circular dependency between p1, p2 and p3. This should not happen, but test anyway.
1118         Plugin p2 = mock(Plugin.class);
1119         when(p2.getKey()).thenReturn("p2");
1120         when(p2.isEnabledByDefault()).thenReturn(true);
1121         when(p2.getPluginState()).thenReturn(PluginState.ENABLED);
1122         when(p2.getDependencies()).thenReturn(new PluginDependencies(ImmutableSet.of("p3"), null, null));
1123         when(p2.compareTo(any(Plugin.class))).thenReturn(-1);
1124 
1125         Plugin p3 = mock(Plugin.class);
1126         when(p3.getKey()).thenReturn("p3");
1127         when(p3.isEnabledByDefault()).thenReturn(true);
1128         when(p3.getPluginState()).thenReturn(PluginState.ENABLED);
1129         when(p3.getDependencies()).thenReturn(new PluginDependencies(ImmutableSet.of("p1"), null, null));
1130         when(p3.compareTo(any(Plugin.class))).thenReturn(-1);
1131 
1132         Plugin parent = mock(Plugin.class);
1133         when(parent.getKey()).thenReturn("parent");
1134         when(parent.isEnabledByDefault()).thenReturn(true);
1135         when(parent.isDeleteable()).thenReturn(true);
1136         when(parent.isUninstallable()).thenReturn(true);
1137         when(parent.getPluginState()).thenReturn(PluginState.ENABLED);
1138         when(parent.compareTo(any(Plugin.class))).thenReturn(-1);
1139         when(parent.getDependencies()).thenReturn(new PluginDependencies());
1140 
1141         PluginLoader pluginLoader = mockPluginLoaderForPlugins(p1, p2, p3, parent);
1142         when(pluginLoader.supportsRemoval()).thenReturn(true);
1143 
1144         manager = initNewDefaultPluginManager(pluginLoader);
1145 
1146         manager.uninstall(parent);
1147         verify(parent, times(1)).enable();
1148         verify(parent, times(1)).disable();
1149         verify(pluginLoader).removePlugin(parent);
1150 
1151         verify(p1, times(1)).enable();
1152         verify(p1, times(1)).disable();
1153         verify(p2, times(1)).enable();
1154         verify(p2, times(1)).disable();
1155         verify(p3, times(1)).enable();
1156         verify(p3, times(1)).disable();
1157     }
1158 
1159     @Test
1160     public void addDynamicModule() {
1161         final PluginLoader pluginLoader = mock(PluginLoader.class);
1162         final PluginInternal plugin = mock(PluginInternal.class);
1163         final Element module = mock(Element.class);
1164         final ModuleDescriptor moduleDescriptor = mock(ModuleDescriptor.class, withSettings().extraInterfaces(StateAware.class));
1165 
1166         newDefaultPluginManager(pluginLoader)
1167                 .addPlugins(pluginLoader, ImmutableList.of(plugin));
1168 
1169         when(pluginLoader.createModule(plugin, module, moduleDescriptorFactory)).thenReturn(moduleDescriptor);
1170         when(moduleDescriptor.isEnabledByDefault()).thenReturn(true);
1171         when(plugin.addDynamicModuleDescriptor(moduleDescriptor)).thenReturn(true);
1172         when(plugin.getPluginState()).thenReturn(PluginState.ENABLED);
1173 
1174         assertThat(manager.addDynamicModule(plugin, module), is(moduleDescriptor));
1175 
1176         verify((StateAware) moduleDescriptor).enabled();
1177     }
1178 
1179     @Test
1180     public void addDynamicModulePluginDisabled() {
1181         final PluginLoader pluginLoader = mock(PluginLoader.class);
1182         final PluginInternal plugin = mock(PluginInternal.class);
1183         final Element module = mock(Element.class);
1184         final ModuleDescriptor moduleDescriptor = mock(ModuleDescriptor.class, withSettings().extraInterfaces(StateAware.class));
1185 
1186         newDefaultPluginManager(pluginLoader)
1187                 .addPlugins(pluginLoader, ImmutableList.of(plugin));
1188 
1189         when(pluginLoader.createModule(plugin, module, moduleDescriptorFactory)).thenReturn(moduleDescriptor);
1190         when(moduleDescriptor.isEnabledByDefault()).thenReturn(true);
1191         when(plugin.addDynamicModuleDescriptor(moduleDescriptor)).thenReturn(true);
1192         when(plugin.getPluginState()).thenReturn(PluginState.DISABLED);
1193 
1194         manager.addDynamicModule(plugin, module);
1195 
1196         verify((StateAware) moduleDescriptor, never()).enabled();
1197     }
1198 
1199     @Test
1200     public void addDynamicModuleModuleNotEnabledByDefault() {
1201         final PluginLoader pluginLoader = mock(PluginLoader.class);
1202         final PluginInternal plugin = mock(PluginInternal.class);
1203         final Element module = mock(Element.class);
1204         final ModuleDescriptor moduleDescriptor = mock(ModuleDescriptor.class, withSettings().extraInterfaces(StateAware.class));
1205 
1206         newDefaultPluginManager(pluginLoader)
1207                 .addPlugins(pluginLoader, ImmutableList.of(plugin));
1208 
1209         when(pluginLoader.createModule(plugin, module, moduleDescriptorFactory)).thenReturn(moduleDescriptor);
1210         when(moduleDescriptor.isEnabledByDefault()).thenReturn(false);
1211         when(plugin.addDynamicModuleDescriptor(moduleDescriptor)).thenReturn(true);
1212         when(plugin.getPluginState()).thenReturn(PluginState.ENABLED);
1213 
1214         manager.addDynamicModule(plugin, module);
1215 
1216         verify((StateAware) moduleDescriptor, never()).enabled();
1217     }
1218 
1219     @Test
1220     public void addDynamicModuleDuplicateKey() {
1221         final PluginLoader pluginLoader = mock(PluginLoader.class);
1222         final PluginInternal plugin = mock(PluginInternal.class);
1223         final Element module = mock(Element.class);
1224         final ModuleDescriptor moduleDescriptor = mock(ModuleDescriptor.class, withSettings().extraInterfaces(StateAware.class));
1225         final ModuleDescriptor existingModuleDescriptor = mock(ModuleDescriptor.class);
1226 
1227         newDefaultPluginManager(pluginLoader)
1228                 .addPlugins(pluginLoader, ImmutableList.of(plugin));
1229 
1230         when(pluginLoader.createModule(plugin, module, moduleDescriptorFactory)).thenReturn(moduleDescriptor);
1231         when(plugin.toString()).thenReturn("awesomePlugin");
1232         when(plugin.getModuleDescriptor("moduleKey")).thenReturn(existingModuleDescriptor);
1233         when(moduleDescriptor.getKey()).thenReturn("moduleKey");
1234 
1235         expectedException.expect(PluginException.class);
1236         expectedException.expectMessage(containsString("awesomePlugin"));
1237         expectedException.expectMessage(containsString("moduleKey"));
1238 
1239         manager.addDynamicModule(plugin, module);
1240 
1241         verify((StateAware) moduleDescriptor, never()).enabled();
1242     }
1243 
1244     @Test
1245     public void addDynamicModuleAlreadyPresentAsDynamicModule() {
1246         final PluginLoader pluginLoader = mock(PluginLoader.class);
1247         final PluginInternal plugin = mock(PluginInternal.class);
1248         final Element module = mock(Element.class);
1249         final ModuleDescriptor moduleDescriptor = mock(ModuleDescriptor.class, withSettings().extraInterfaces(StateAware.class));
1250 
1251         newDefaultPluginManager(pluginLoader)
1252                 .addPlugins(pluginLoader, ImmutableList.of(plugin));
1253 
1254         when(pluginLoader.createModule(plugin, module, moduleDescriptorFactory)).thenReturn(moduleDescriptor);
1255         when(plugin.addDynamicModuleDescriptor(moduleDescriptor)).thenReturn(false);
1256         when(plugin.toString()).thenReturn("lawsonPlugin");
1257         when(moduleDescriptor.getKey()).thenReturn("moduleKey");
1258 
1259         expectedException.expect(PluginException.class);
1260         expectedException.expectMessage(containsString("lawsonPlugin"));
1261         expectedException.expectMessage(containsString("moduleKey"));
1262 
1263         manager.addDynamicModule(plugin, module);
1264 
1265         verify((StateAware) moduleDescriptor, never()).enabled();
1266     }
1267 
1268     private static class CannotEnablePlugin extends StaticPlugin {
1269         public CannotEnablePlugin() {
1270             setKey("foo");
1271         }
1272 
1273         @Override
1274         protected PluginState enableInternal() {
1275             throw new RuntimeException("boo");
1276         }
1277 
1278         public void disabled() {
1279         }
1280     }
1281 
1282     @Test
1283     public void testAddPluginsThatThrowExceptionOnEnabled() {
1284         final Plugin plugin = new CannotEnablePlugin();
1285 
1286         newDefaultPluginManager()
1287                 .addPlugins(null, singletonList(plugin));
1288 
1289         assertNotSame(plugin.getPluginState(), PluginState.ENABLED);
1290     }
1291 
1292     private Plugin newPluginMock() {
1293         Plugin plugin = mock(Plugin.class, RETURNS_DEEP_STUBS);
1294         when(plugin.getModuleDescriptors()).thenReturn(emptyList());
1295         return plugin;
1296     }
1297 }