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