View Javadoc
1   
2   package com.atlassian.plugin.manager;
3   
4   import com.atlassian.plugin.DefaultModuleDescriptorFactory;
5   import com.atlassian.plugin.JarPluginArtifact;
6   import com.atlassian.plugin.MockModuleDescriptor;
7   import com.atlassian.plugin.ModuleDescriptor;
8   import com.atlassian.plugin.ModuleDescriptorFactory;
9   import com.atlassian.plugin.Plugin;
10  import com.atlassian.plugin.PluginAccessor;
11  import com.atlassian.plugin.PluginArtifact;
12  import com.atlassian.plugin.PluginDependencies;
13  import com.atlassian.plugin.PluginException;
14  import com.atlassian.plugin.PluginInformation;
15  import com.atlassian.plugin.PluginInternal;
16  import com.atlassian.plugin.PluginParseException;
17  import com.atlassian.plugin.PluginRestartState;
18  import com.atlassian.plugin.PluginState;
19  import com.atlassian.plugin.descriptors.AbstractModuleDescriptor;
20  import com.atlassian.plugin.descriptors.MockUnusedModuleDescriptor;
21  import com.atlassian.plugin.descriptors.RequiresRestart;
22  import com.atlassian.plugin.event.PluginEventListener;
23  import com.atlassian.plugin.event.PluginEventManager;
24  import com.atlassian.plugin.event.events.PluginContainerUnavailableEvent;
25  import com.atlassian.plugin.event.events.PluginDisabledEvent;
26  import com.atlassian.plugin.event.events.PluginEnabledEvent;
27  import com.atlassian.plugin.event.events.PluginModuleAvailableEvent;
28  import com.atlassian.plugin.event.events.PluginModuleDisabledEvent;
29  import com.atlassian.plugin.event.events.PluginModuleEnabledEvent;
30  import com.atlassian.plugin.event.events.PluginModuleUnavailableEvent;
31  import com.atlassian.plugin.event.impl.DefaultPluginEventManager;
32  import com.atlassian.plugin.event.listeners.FailListener;
33  import com.atlassian.plugin.event.listeners.PassListener;
34  import com.atlassian.plugin.factories.LegacyDynamicPluginFactory;
35  import com.atlassian.plugin.hostcontainer.DefaultHostContainer;
36  import com.atlassian.plugin.impl.StaticPlugin;
37  import com.atlassian.plugin.impl.UnloadablePlugin;
38  import com.atlassian.plugin.loaders.DirectoryPluginLoader;
39  import com.atlassian.plugin.loaders.DynamicPluginLoader;
40  import com.atlassian.plugin.loaders.PluginLoader;
41  import com.atlassian.plugin.loaders.SinglePluginLoader;
42  import com.atlassian.plugin.loaders.classloading.DirectoryPluginLoaderUtils;
43  import com.atlassian.plugin.manager.store.MemoryPluginPersistentStateStore;
44  import com.atlassian.plugin.metadata.PluginMetadataManager;
45  import com.atlassian.plugin.mock.MockAnimal;
46  import com.atlassian.plugin.mock.MockAnimalModuleDescriptor;
47  import com.atlassian.plugin.mock.MockBear;
48  import com.atlassian.plugin.mock.MockMineral;
49  import com.atlassian.plugin.mock.MockMineralModuleDescriptor;
50  import com.atlassian.plugin.mock.MockThing;
51  import com.atlassian.plugin.mock.MockVegetableModuleDescriptor;
52  import com.atlassian.plugin.mock.MockVegetableSubclassModuleDescriptor;
53  import com.atlassian.plugin.parsers.SafeModeCommandLineArguments;
54  import com.atlassian.plugin.parsers.SafeModeCommandLineArgumentsFactory;
55  import com.atlassian.plugin.repositories.FilePluginInstaller;
56  import com.atlassian.plugin.test.CapturedLogging;
57  import com.atlassian.plugin.test.PluginJarBuilder;
58  import com.google.common.collect.ImmutableList;
59  import com.google.common.collect.Iterables;
60  import com.google.common.collect.Lists;
61  import org.apache.commons.io.FileUtils;
62  import org.apache.commons.io.IOUtils;
63  import org.dom4j.Element;
64  import org.junit.After;
65  import org.junit.Before;
66  import org.junit.Rule;
67  import org.junit.Test;
68  import org.junit.contrib.java.lang.system.RestoreSystemProperties;
69  import org.junit.rules.ExpectedException;
70  import org.mockito.ArgumentMatchers;
71  import org.mockito.Mock;
72  import org.mockito.MockitoAnnotations;
73  
74  import java.io.File;
75  import java.io.IOException;
76  import java.io.InputStream;
77  import java.util.ArrayList;
78  import java.util.Arrays;
79  import java.util.Collection;
80  import java.util.Collections;
81  import java.util.HashSet;
82  import java.util.List;
83  import java.util.concurrent.atomic.AtomicBoolean;
84  import java.util.concurrent.atomic.AtomicInteger;
85  import java.util.function.Predicate;
86  
87  import static com.atlassian.plugin.loaders.classloading.DirectoryPluginLoaderUtils.PADDINGTON_JAR;
88  import static com.atlassian.plugin.manager.DefaultPluginManagerMocks.FailureMode.FAIL_TO_DISABLE;
89  import static com.atlassian.plugin.manager.DefaultPluginManagerMocks.FailureMode.FAIL_TO_ENABLE;
90  import static com.atlassian.plugin.manager.DefaultPluginManagerMocks.mockFailingModuleDescriptor;
91  import static com.atlassian.plugin.manager.DefaultPluginManagerMocks.mockPluginLoaderForPlugins;
92  import static com.atlassian.plugin.manager.DefaultPluginManagerMocks.mockStaticPlugin;
93  import static com.atlassian.plugin.manager.DefaultPluginManagerMocks.mockTestPlugin;
94  import static com.atlassian.plugin.test.PluginTestUtils.createTempDirectory;
95  import static com.google.common.collect.ImmutableList.copyOf;
96  import static java.util.Collections.emptyMap;
97  import static java.util.Collections.emptySet;
98  import static org.apache.commons.io.FileUtils.deleteQuietly;
99  import static org.hamcrest.MatcherAssert.assertThat;
100 import static org.hamcrest.Matchers.empty;
101 import static org.hamcrest.Matchers.equalTo;
102 import static org.hamcrest.Matchers.hasProperty;
103 import static org.hamcrest.Matchers.hasSize;
104 import static org.hamcrest.Matchers.is;
105 import static org.junit.Assert.assertEquals;
106 import static org.junit.Assert.assertFalse;
107 import static org.junit.Assert.assertNotNull;
108 import static org.junit.Assert.assertNull;
109 import static org.junit.Assert.assertTrue;
110 import static org.junit.Assert.fail;
111 import static org.mockito.ArgumentMatchers.any;
112 import static org.mockito.ArgumentMatchers.anyString;
113 import static org.mockito.ArgumentMatchers.eq;
114 import static org.mockito.ArgumentMatchers.isA;
115 import static org.mockito.Mockito.mock;
116 import static org.mockito.Mockito.spy;
117 import static org.mockito.Mockito.verify;
118 import static org.mockito.Mockito.when;
119 import static org.mockito.hamcrest.MockitoHamcrest.argThat;
120 
121 /**
122  * Unit and low level integration tests for DefaultPluginManager, and base for tests of wrapping/delegating variants.
123  * <p>
124  * If you're tempted to try to push mocks further through this, be aware that there are subclasses
125  * which need to wrap components and which react to events sent during processing. That is, these
126  * tests are also testing to some extent integration between DefaultPluginManager and
127  * DefaultPluginEventManager and other related classes.
128  */
129 public class TestDefaultPluginManager {
130     @Rule
131     public final CapturedLogging capturedLogging = new CapturedLogging(DefaultPluginManager.class);
132     @Rule
133     public final ExpectedException expectedException = ExpectedException.none();
134     @Rule
135     public final RestoreSystemProperties restoreSystemProperties = new RestoreSystemProperties();
136 
137     /**
138      * the object being tested
139      */
140     protected DefaultPluginManager manager;
141 
142     protected PluginPersistentStateStore pluginStateStore;
143     protected DefaultModuleDescriptorFactory moduleDescriptorFactory; // we should be able to use the interface here?
144 
145     private DirectoryPluginLoader directoryPluginLoader;
146     protected PluginEventManager pluginEventManager;
147     private SafeModeManager safeModeManager;
148     private ClusterEnvironmentProvider clusterEnvironmentProvider;
149     private SafeModeCommandLineArguments safeModeCommandLineArgs;
150     private SafeModeCommandLineArgumentsFactory safeModeCommandLineArgsFactory;
151     private PluginMetadataManager pluginMetadataManager;
152     private ApplicationDefinedPluginsProvider appDefinedPluginsProvider;
153     @Mock
154     private PluginPersistentStateStore mockPluginPersistentStateStore;
155     private File pluginsDirectory;
156     private File pluginsTestDir;
157     private File temporaryDirectory;
158 
159     private PluginPersistentState mockPluginPersistentState;
160 
161     private void createFillAndCleanTempPluginDirectory() throws IOException {
162         final DirectoryPluginLoaderUtils.ScannerDirectories directories = DirectoryPluginLoaderUtils.createFillAndCleanTempPluginDirectory();
163         pluginsDirectory = directories.pluginsDirectory;
164         pluginsTestDir = directories.pluginsTestDir;
165     }
166 
167     @Before
168     public void setUp() throws Exception {
169         MockitoAnnotations.initMocks(this);
170 
171         pluginEventManager = new DefaultPluginEventManager();
172         safeModeCommandLineArgsFactory = mock(SafeModeCommandLineArgumentsFactory.class);
173         pluginStateStore = new MemoryPluginPersistentStateStore();
174         moduleDescriptorFactory = new DefaultModuleDescriptorFactory(new DefaultHostContainer());
175         temporaryDirectory = createTempDirectory(TestDefaultPluginManager.class);
176         pluginMetadataManager = mock(PluginMetadataManager.class);
177         appDefinedPluginsProvider = mock(ApplicationDefinedPluginsProvider.class);
178         clusterEnvironmentProvider = mock(ClusterEnvironmentProvider.class);
179         when(pluginMetadataManager.isSystemProvided(any())).thenReturn(false);
180         when(pluginMetadataManager.isOptional(any(Plugin.class))).thenReturn(true);
181         safeModeCommandLineArgs = mock(SafeModeCommandLineArguments.class);
182         when(safeModeCommandLineArgsFactory.get()).thenReturn(safeModeCommandLineArgs);
183         when(safeModeCommandLineArgs.isSafeMode()).thenReturn(false);
184         safeModeManager = SafeModeManager.START_ALL_PLUGINS;
185     }
186 
187     @After
188     public void tearDown() throws Exception {
189         manager = null;
190         moduleDescriptorFactory = null;
191         pluginStateStore = null;
192 
193         if (directoryPluginLoader != null) {
194             directoryPluginLoader = null;
195         }
196         deleteQuietly(temporaryDirectory);
197     }
198 
199     /**
200      * Overridden in test subclasses which wrap manager or plugin accessor
201      */
202     protected DefaultPluginManager newDefaultPluginManager(DefaultPluginManager.Builder builder) {
203         return builder
204                 .withModuleDescriptorFactory(moduleDescriptorFactory)
205                 .withPluginEventManager(pluginEventManager)
206                 .withStore(pluginStateStore)
207                 .withSafeModeManager(safeModeManager)
208                 .withVerifyRequiredPlugins(true)
209                 .build();
210     }
211 
212     private SafeModeManager newDefaultSafeModeManager(){
213 
214         mockPluginPersistentStateStore = mock(PluginPersistentStateStore.class);
215         mockPluginPersistentState = mock(PluginPersistentState.class);
216         when(mockPluginPersistentState.getStatesMap()).thenReturn(emptyMap());
217         when(mockPluginPersistentStateStore.load()).thenReturn(mockPluginPersistentState);
218         return spy(new DefaultSafeModeManager(pluginMetadataManager,appDefinedPluginsProvider, clusterEnvironmentProvider, safeModeCommandLineArgsFactory, mockPluginPersistentStateStore));
219     }
220 
221     private DefaultPluginManager newDefaultPluginManager(PluginLoader... pluginLoaders) {
222         return newDefaultPluginManager(DefaultPluginManager.newBuilder().withPluginLoaders(copyOf(pluginLoaders)));
223     }
224 
225     /**
226      * Overridden in test subclasses which wrap plugin accessor
227      */
228     protected PluginAccessor getPluginAccessor() {
229         return manager;
230     }
231 
232     @Test
233     public void testRetrievePlugins() throws PluginParseException {
234         manager = newDefaultPluginManager(
235                 new SinglePluginLoader("test-atlassian-plugin.xml"),
236                 new SinglePluginLoader("test-disabled-plugin.xml"));
237 
238         moduleDescriptorFactory.addModuleDescriptor("animal", MockAnimalModuleDescriptor.class);
239         moduleDescriptorFactory.addModuleDescriptor("mineral", MockMineralModuleDescriptor.class);
240         manager.init();
241 
242         assertThat(getPluginAccessor().getPlugins(), hasSize(2));
243         assertThat(getPluginAccessor().getEnabledPlugins(), hasSize(1));
244         manager.enablePlugins("test.disabled.plugin");
245         assertThat(getPluginAccessor().getEnabledPlugins(), hasSize(2));
246     }
247 
248     @Test
249     public void testEnableModuleFailed() throws PluginParseException {
250         final PluginLoader mockPluginLoader = mock(PluginLoader.class);
251         final ModuleDescriptor<Object> badModuleDescriptor = mockFailingModuleDescriptor("foo:bar", FAIL_TO_ENABLE);
252 
253         final AbstractModuleDescriptor goodModuleDescriptor = mock(AbstractModuleDescriptor.class);
254         when(goodModuleDescriptor.getKey()).thenReturn("baz");
255         when(goodModuleDescriptor.getPluginKey()).thenReturn("foo");
256         when(goodModuleDescriptor.isEnabledByDefault()).thenReturn(true);
257 
258         Plugin plugin = mockStaticPlugin("foo", goodModuleDescriptor, badModuleDescriptor);
259 
260         when(mockPluginLoader.loadAllPlugins(isA(ModuleDescriptorFactory.class))).thenReturn(Collections.singletonList(plugin));
261 
262         pluginEventManager.register(new FailListener(PluginEnabledEvent.class));
263 
264         MyModuleDisabledListener listener = new MyModuleDisabledListener(goodModuleDescriptor);
265         pluginEventManager.register(listener);
266 
267         manager = newDefaultPluginManager(mockPluginLoader);
268         manager.init();
269 
270         assertThat(getPluginAccessor().getPlugins(), hasSize(1));
271         assertThat(getPluginAccessor().getEnabledPlugins(), hasSize(0));
272         plugin = getPluginAccessor().getPlugin("foo");
273         assertFalse(plugin.getPluginState() == PluginState.ENABLED);
274         assertTrue(plugin instanceof UnloadablePlugin);
275         assertTrue(listener.isCalled());
276     }
277 
278     public static class MyModuleDisabledListener {
279         private final ModuleDescriptor goodModuleDescriptor;
280         private volatile boolean disableCalled = false;
281 
282         public MyModuleDisabledListener(ModuleDescriptor goodModuleDescriptor) {
283             this.goodModuleDescriptor = goodModuleDescriptor;
284         }
285 
286         @PluginEventListener
287         public void onDisable(PluginModuleDisabledEvent evt) {
288             if (evt.getModule().equals(goodModuleDescriptor)) {
289                 disableCalled = true;
290             }
291         }
292 
293         public boolean isCalled() {
294             return disableCalled;
295         }
296     }
297 
298     @Test
299     public void testEnabledModuleOutOfSyncWithPlugin() throws PluginParseException {
300         final PluginLoader mockPluginLoader = mock(PluginLoader.class);
301         Plugin plugin = new StaticPlugin();
302         plugin.setKey("foo");
303         plugin.setEnabledByDefault(true);
304         plugin.setPluginInformation(new PluginInformation());
305 
306         when(mockPluginLoader.loadAllPlugins(isA(ModuleDescriptorFactory.class))).thenReturn(Collections.singletonList(plugin));
307 
308         manager = newDefaultPluginManager(mockPluginLoader);
309         manager.init();
310 
311         assertThat(getPluginAccessor().getPlugins(), hasSize(1));
312         assertThat(getPluginAccessor().getEnabledPlugins(), hasSize(1));
313         plugin = getPluginAccessor().getPlugin("foo");
314         assertTrue(plugin.getPluginState() == PluginState.ENABLED);
315         assertTrue(getPluginAccessor().isPluginEnabled("foo"));
316         plugin.disable();
317         assertFalse(plugin.getPluginState() == PluginState.ENABLED);
318         assertFalse(getPluginAccessor().isPluginEnabled("foo"));
319     }
320 
321     @Test
322     public void disablePluginsBySafeModeShouldNotWorkInClusterEnvironment() throws Exception {
323         safeModeManager = newDefaultSafeModeManager();
324         when(safeModeCommandLineArgs.isDisabledByParam(anyString())).thenReturn(true);
325         when(safeModeCommandLineArgs.isSafeMode()).thenReturn(true);
326         when(clusterEnvironmentProvider.isInCluster()).thenReturn(true);
327         manager = newDefaultPluginManager(new SinglePluginLoader("test-another-plugin.xml"));
328         manager.init();
329         assertNotNull(getPluginAccessor().getEnabledPlugin("test.another.plugin"));
330     }
331 
332     @Test
333     public void disablePluginsBySafeModeManagerShouldNotAffectNewPlugins() {
334         safeModeManager = newDefaultSafeModeManager();
335         when(safeModeCommandLineArgs.isDisabledByParam(any())).thenReturn(true);
336         final SinglePluginLoaderWithAddition loader = new SinglePluginLoaderWithAddition("test-atlassian-plugin.xml");
337         loader.setAddPluginLoader(new SinglePluginLoader("test-another-plugin.xml"));
338         manager = newDefaultPluginManager(loader);
339         manager.init();
340         manager.scanForNewPlugins();
341         assertNull(getPluginAccessor().getEnabledPlugin("test.atlassian.plugin"));
342         assertNotNull(getPluginAccessor().getEnabledPlugin("test.another.plugin"));
343     }
344 
345     @Test
346     public void disableParticularPluginBySafeModeManagerShouldDisableAnyPlugin() {
347         safeModeManager = newDefaultSafeModeManager();
348         final PluginLoader[] loaders = {new SinglePluginLoader("test-another-plugin.xml"), new SinglePluginLoader("test-system-plugin.xml")};
349         when(pluginMetadataManager.isSystemProvided(argThat(hasProperty("key", equalTo("test.atlassian.plugin"))))).thenReturn(true);
350 
351         // disable system plugin
352         when(safeModeCommandLineArgs.isDisabledByParam(any())).thenReturn(false);
353         when(safeModeCommandLineArgs.isDisabledByParam(eq("test.atlassian.plugin"))).thenReturn(true);
354         manager = newDefaultPluginManager(loaders);
355         manager.init();
356         assertNull(getPluginAccessor().getEnabledPlugin("test.atlassian.plugin"));
357         assertNotNull(getPluginAccessor().getEnabledPlugin("test.another.plugin"));
358 
359         // disable both - user installed and system plugins
360         when(safeModeCommandLineArgs.isDisabledByParam(eq("test.atlassian.plugin"))).thenReturn(true);
361         when(safeModeCommandLineArgs.isDisabledByParam(eq("test.another.plugin"))).thenReturn(true);
362         manager = newDefaultPluginManager(loaders);
363         manager.init();
364         assertNull(getPluginAccessor().getEnabledPlugin("test.another.plugin"));
365         assertNull(getPluginAccessor().getEnabledPlugin("test.atlassian.plugin"));
366     }
367 
368     @Test
369     public void disableAllPluginsBySafeModeShouldNotDisableApplicationPlugins() {
370         safeModeManager = newDefaultSafeModeManager();
371         when(safeModeCommandLineArgs.isDisabledByParam(eq("test.atlassian.plugin.app"))).thenReturn(false);
372         when(safeModeCommandLineArgs.isSafeMode()).thenReturn(true);
373         when(appDefinedPluginsProvider.getPluginKeys(any(Iterable.class))).thenReturn(new HashSet(Collections.singletonList("test.atlassian.plugin.app")));
374         // reinit manager
375         manager = newDefaultPluginManager(new SinglePluginLoader("test-plugin-dynamic-modules.xml"), new SinglePluginLoader("test-atlassian-plugin-with-app.xml"));
376         manager.init();
377 
378         assertNull(getPluginAccessor().getEnabledPlugin("pluginKey"));
379         assertNotNull(getPluginAccessor().getEnabledPlugin("test.atlassian.plugin.app"));
380     }
381 
382     @Test
383     public void disableAllPluginsBySafeModeManagerShouldNotDisableRequiredPlugins() {
384         safeModeManager = newDefaultSafeModeManager();
385         final PluginLoader[] loaders = {
386                 new SinglePluginLoader("test-another-plugin.xml"), new SinglePluginLoader("test-system-plugin.xml")};
387         when(pluginMetadataManager.isOptional(ArgumentMatchers.argThat(
388                 (Plugin item) -> item.getKey().equals("test.another.plugin")))).thenReturn(false);
389         when(safeModeCommandLineArgs.isSafeMode()).thenReturn(true);
390         when(safeModeCommandLineArgs.isDisabledByParam(eq("test.another.plugin"))).thenReturn(false);
391         // reinit manager
392         manager = newDefaultPluginManager(loaders);
393         manager.init();
394         assertNotNull(getPluginAccessor().getEnabledPlugin("test.another.plugin"));
395         assertNull(getPluginAccessor().getEnabledPlugin("test.atlassian.plugin"));
396     }
397 
398     @Test
399     public void disableAllPluginsBySafeModeManagerShouldNotDisableSystemPlugins() {
400         safeModeManager = newDefaultSafeModeManager();
401         final PluginLoader[] loaders = {new SinglePluginLoader("test-another-plugin.xml"), new SinglePluginLoader("test-system-plugin.xml")};
402         when(pluginMetadataManager.isSystemProvided(argThat(hasProperty("key", equalTo("test.atlassian.plugin"))))).thenReturn(true);
403         // disable all plugins, but not particular one
404         when(safeModeCommandLineArgs.isDisabledByParam(any())).thenReturn(true);
405         when(safeModeCommandLineArgs.isDisabledByParam(eq("test.atlassian.plugin"))).thenReturn(false);
406         // reinit manager
407         manager = newDefaultPluginManager(loaders);
408         manager.init();
409         assertNull(getPluginAccessor().getEnabledPlugin("test.another.plugin"));
410         assertNotNull(getPluginAccessor().getEnabledPlugin("test.atlassian.plugin"));
411     }
412 
413     @Test
414     public void testDisablePluginModuleWithCannotDisableAnnotation() {
415         moduleDescriptorFactory.addModuleDescriptor("animal", MockAnimalModuleDescriptor.class);
416         moduleDescriptorFactory.addModuleDescriptor("mineral", MockMineralModuleDescriptor.class);
417         moduleDescriptorFactory.addModuleDescriptor("bullshit", MockUnusedModuleDescriptor.class);
418         moduleDescriptorFactory.addModuleDescriptor("vegetable", MockVegetableModuleDescriptor.class);
419 
420         manager = newDefaultPluginManager(new SinglePluginLoader("test-atlassian-plugin.xml"));
421         manager.init();
422 
423         final String pluginKey = "test.atlassian.plugin";
424         final String disablableModuleKey = pluginKey + ":bear";
425         final String moduleKey = pluginKey + ":veg";
426 
427         // First, make sure we can disable the bear module
428         manager.disablePluginModule(disablableModuleKey);
429         assertNull(getPluginAccessor().getEnabledPluginModule(disablableModuleKey));
430 
431         // Now, make sure we can't disable the veg module
432         manager.disablePluginModule(moduleKey);
433         assertNotNull(getPluginAccessor().getEnabledPluginModule(moduleKey));
434     }
435 
436     @Test
437     public void testDisablePluginModuleWithCannotDisableAnnotationInSuperclass() {
438         moduleDescriptorFactory.addModuleDescriptor("animal", MockAnimalModuleDescriptor.class);
439         moduleDescriptorFactory.addModuleDescriptor("mineral", MockMineralModuleDescriptor.class);
440         moduleDescriptorFactory.addModuleDescriptor("bullshit", MockUnusedModuleDescriptor.class);
441         moduleDescriptorFactory.addModuleDescriptor("vegetable", MockVegetableModuleDescriptor.class);
442         moduleDescriptorFactory.addModuleDescriptor("vegetableSubclass", MockVegetableSubclassModuleDescriptor.class);
443 
444         manager = newDefaultPluginManager(new SinglePluginLoader("test-atlassian-plugin.xml"));
445         manager.init();
446 
447         final String pluginKey = "test.atlassian.plugin";
448         final String disablableModuleKey = pluginKey + ":bear";
449         final String moduleKey = pluginKey + ":vegSubclass";
450 
451         // First, make sure we can disable the bear module
452         manager.disablePluginModule(disablableModuleKey);
453         assertNull(getPluginAccessor().getEnabledPluginModule(disablableModuleKey));
454 
455         // Now, make sure we can't disable the vegSubclass module
456         manager.disablePluginModule(moduleKey);
457         assertNotNull(getPluginAccessor().getEnabledPluginModule(moduleKey));
458     }
459 
460     @Test
461     public void testEnabledDisabledRetrieval() throws PluginParseException {
462         moduleDescriptorFactory.addModuleDescriptor("animal", MockAnimalModuleDescriptor.class);
463         moduleDescriptorFactory.addModuleDescriptor("mineral", MockMineralModuleDescriptor.class);
464         moduleDescriptorFactory.addModuleDescriptor("bullshit", MockUnusedModuleDescriptor.class);
465         moduleDescriptorFactory.addModuleDescriptor("vegetable", MockVegetableModuleDescriptor.class);
466 
467         final PassListener enabledListener = new PassListener(PluginEnabledEvent.class);
468         final PassListener disabledListener = new PassListener(PluginDisabledEvent.class);
469         pluginEventManager.register(enabledListener);
470         pluginEventManager.register(disabledListener);
471 
472         manager = newDefaultPluginManager(new SinglePluginLoader("test-atlassian-plugin.xml"));
473         manager.init();
474 
475         // check non existent plugins don't show
476         assertNull(getPluginAccessor().getPlugin("bull:shit"));
477         assertNull(getPluginAccessor().getEnabledPlugin("bull:shit"));
478         assertNull(getPluginAccessor().getPluginModule("bull:shit"));
479         assertNull(getPluginAccessor().getEnabledPluginModule("bull:shit"));
480         assertTrue(getPluginAccessor().getEnabledModuleDescriptorsByClass(NothingModuleDescriptor.class).isEmpty());
481 
482         final String pluginKey = "test.atlassian.plugin";
483         final String moduleKey = pluginKey + ":bear";
484 
485         // retrieve everything when enabled
486         assertNotNull(getPluginAccessor().getPlugin(pluginKey));
487         assertNotNull(getPluginAccessor().getEnabledPlugin(pluginKey));
488         assertNotNull(getPluginAccessor().getPluginModule(moduleKey));
489         assertNotNull(getPluginAccessor().getEnabledPluginModule(moduleKey));
490         assertNull(getPluginAccessor().getEnabledPluginModule(pluginKey + ":shit"));
491         assertFalse(getPluginAccessor().getEnabledModuleDescriptorsByClass(MockAnimalModuleDescriptor.class).isEmpty());
492         assertFalse(getPluginAccessor().getEnabledModulesByClass(MockBear.class).isEmpty());
493         assertEquals(new MockBear(), getPluginAccessor().getEnabledModulesByClass(MockBear.class).get(0));
494         enabledListener.assertCalled();
495 
496         // now only retrieve via always retrieve methods
497         manager.disablePlugin(pluginKey);
498         assertNotNull(getPluginAccessor().getPlugin(pluginKey));
499         assertNull(getPluginAccessor().getEnabledPlugin(pluginKey));
500         assertNotNull(getPluginAccessor().getPluginModule(moduleKey));
501         assertNull(getPluginAccessor().getEnabledPluginModule(moduleKey));
502         assertTrue(getPluginAccessor().getEnabledModulesByClass(com.atlassian.plugin.mock.MockBear.class).isEmpty());
503         assertTrue(getPluginAccessor().getEnabledModuleDescriptorsByClass(MockAnimalModuleDescriptor.class).isEmpty());
504         disabledListener.assertCalled();
505 
506         // now enable again and check back to start
507         manager.enablePlugins(pluginKey);
508         assertNotNull(getPluginAccessor().getPlugin(pluginKey));
509         assertNotNull(getPluginAccessor().getEnabledPlugin(pluginKey));
510         assertNotNull(getPluginAccessor().getPluginModule(moduleKey));
511         assertNotNull(getPluginAccessor().getEnabledPluginModule(moduleKey));
512         assertFalse(getPluginAccessor().getEnabledModulesByClass(com.atlassian.plugin.mock.MockBear.class).isEmpty());
513         assertFalse(getPluginAccessor().getEnabledModuleDescriptorsByClass(MockAnimalModuleDescriptor.class).isEmpty());
514         enabledListener.assertCalled();
515 
516         // now let's disable the module, but not the plugin
517         pluginEventManager.register(new FailListener(PluginEnabledEvent.class));
518         manager.disablePluginModule(moduleKey);
519         assertNotNull(getPluginAccessor().getPlugin(pluginKey));
520         assertNotNull(getPluginAccessor().getEnabledPlugin(pluginKey));
521         assertNotNull(getPluginAccessor().getPluginModule(moduleKey));
522         assertNull(getPluginAccessor().getEnabledPluginModule(moduleKey));
523         assertTrue(getPluginAccessor().getEnabledModulesByClass(com.atlassian.plugin.mock.MockBear.class).isEmpty());
524         assertTrue(getPluginAccessor().getEnabledModuleDescriptorsByClass(MockAnimalModuleDescriptor.class).isEmpty());
525 
526         // now enable the module again
527         pluginEventManager.register(new FailListener(PluginDisabledEvent.class));
528         manager.enablePluginModule(moduleKey);
529         assertNotNull(getPluginAccessor().getPlugin(pluginKey));
530         assertNotNull(getPluginAccessor().getEnabledPlugin(pluginKey));
531         assertNotNull(getPluginAccessor().getPluginModule(moduleKey));
532         assertNotNull(getPluginAccessor().getEnabledPluginModule(moduleKey));
533         assertFalse(getPluginAccessor().getEnabledModulesByClass(com.atlassian.plugin.mock.MockBear.class).isEmpty());
534         assertFalse(getPluginAccessor().getEnabledModuleDescriptorsByClass(MockAnimalModuleDescriptor.class).isEmpty());
535     }
536 
537     @Test
538     public void testDuplicatePluginKeysAreIgnored() throws PluginParseException {
539         moduleDescriptorFactory.addModuleDescriptor("mineral", MockMineralModuleDescriptor.class);
540 
541         manager = newDefaultPluginManager(new SinglePluginLoader("test-atlassian-plugin.xml"), new SinglePluginLoader("test-atlassian-plugin.xml"));
542         manager.init();
543         assertThat(getPluginAccessor().getEnabledPlugins(), hasSize(1));
544     }
545 
546     @Test
547     public void testDuplicateSnapshotVersionsAreNotLoaded() throws PluginParseException {
548         moduleDescriptorFactory.addModuleDescriptor("mineral", MockMineralModuleDescriptor.class);
549 
550         manager = newDefaultPluginManager(new SinglePluginLoader("test-atlassian-snapshot-plugin.xml"), new SinglePluginLoader("test-atlassian-snapshot-plugin.xml"));
551         manager.init();
552         assertThat(getPluginAccessor().getEnabledPlugins(), hasSize(1));
553     }
554 
555     @Test
556     public void testChangedSnapshotVersionIsLoaded() throws PluginParseException {
557         moduleDescriptorFactory.addModuleDescriptor("mineral", MockMineralModuleDescriptor.class);
558 
559         manager = newDefaultPluginManager(
560                 new SinglePluginLoader("test-atlassian-snapshot-plugin.xml"),
561                 new DelayedSinglePluginLoader("test-atlassian-snapshot-plugin-changed-same-version.xml")
562         );
563         manager.init();
564         assertThat(getPluginAccessor().getEnabledPlugins(), hasSize(1));
565         final Plugin plugin = getPluginAccessor().getPlugin("test.atlassian.plugin");
566         assertEquals("1.1-SNAPSHOT", plugin.getPluginInformation().getVersion());
567         assertEquals("This plugin descriptor has been changed!", plugin.getPluginInformation().getDescription());
568     }
569 
570     @Test
571     public void testLoadOlderDuplicatePlugin() {
572         moduleDescriptorFactory.addModuleDescriptor("mineral", MockMineralModuleDescriptor.class);
573         moduleDescriptorFactory.addModuleDescriptor("animal", MockAnimalModuleDescriptor.class);
574 
575         manager = newDefaultPluginManager(new MultiplePluginLoader("test-atlassian-plugin-newer.xml"), new MultiplePluginLoader("test-atlassian-plugin.xml", "test-another-plugin.xml"));
576         manager.init();
577         assertThat(getPluginAccessor().getEnabledPlugins(), hasSize(2));
578     }
579 
580     @Test
581     public void testLoadOlderDuplicatePluginDoesNotTryToEnableIt() {
582         moduleDescriptorFactory.addModuleDescriptor("mineral", MockMineralModuleDescriptor.class);
583         moduleDescriptorFactory.addModuleDescriptor("animal", MockAnimalModuleDescriptor.class);
584 
585         final Plugin plugin = new StaticPlugin() {
586             @Override
587             protected PluginState enableInternal() {
588                 fail("enable() must never be called on a earlier version of plugin when later version is installed");
589                 return null;
590             }
591 
592             @Override
593             public void disableInternal() {
594                 fail("disable() must never be called on a earlier version of plugin when later version is installed");
595             }
596         };
597         plugin.setKey("test.atlassian.plugin");
598         plugin.getPluginInformation().setVersion("1.0");
599 
600         PluginLoader pluginLoader = new MultiplePluginLoader("test-atlassian-plugin-newer.xml");
601         manager = newDefaultPluginManager(pluginLoader);
602         manager.init();
603         manager.addPlugins(pluginLoader, Collections.singletonList(plugin));
604     }
605 
606     @Test
607     public void testLoadNewerDuplicatePlugin() {
608         moduleDescriptorFactory.addModuleDescriptor("mineral", MockMineralModuleDescriptor.class);
609         moduleDescriptorFactory.addModuleDescriptor("animal", MockAnimalModuleDescriptor.class);
610 
611         manager = newDefaultPluginManager(
612                 new SinglePluginLoader("test-atlassian-plugin.xml"),
613                 new SinglePluginLoader("test-atlassian-plugin-newer.xml"));
614         manager.init();
615         assertThat(getPluginAccessor().getEnabledPlugins(), hasSize(1));
616         final Plugin plugin = getPluginAccessor().getPlugin("test.atlassian.plugin");
617         assertEquals("1.1", plugin.getPluginInformation().getVersion());
618     }
619 
620     @Test
621     public void testLoadNewerDuplicateDynamicPluginPreservesPluginState() throws PluginParseException {
622         moduleDescriptorFactory.addModuleDescriptor("mineral", MockMineralModuleDescriptor.class);
623         moduleDescriptorFactory.addModuleDescriptor("animal", MockAnimalModuleDescriptor.class);
624 
625         manager = newDefaultPluginManager(new SinglePluginLoaderWithRemoval("test-atlassian-plugin.xml"));
626         manager.init();
627 
628         pluginStateStore.save(PluginPersistentState.Builder.create(pluginStateStore.load()).setEnabled(getPluginAccessor().getPlugin("test.atlassian.plugin"),
629                 false).toState());
630 
631         manager.shutdown();
632 
633         manager = newDefaultPluginManager(new SinglePluginLoaderWithRemoval("test-atlassian-plugin-newer.xml"));
634         manager.init();
635 
636         final Plugin plugin = getPluginAccessor().getPlugin("test.atlassian.plugin");
637         assertEquals("1.1", plugin.getPluginInformation().getVersion());
638         assertFalse(getPluginAccessor().isPluginEnabled("test.atlassian.plugin"));
639     }
640 
641     @Test
642     public void testLoadNewerDuplicateDynamicPluginPreservesModuleState() throws PluginParseException {
643         moduleDescriptorFactory.addModuleDescriptor("mineral", MockMineralModuleDescriptor.class);
644         moduleDescriptorFactory.addModuleDescriptor("animal", MockAnimalModuleDescriptor.class);
645 
646         manager = newDefaultPluginManager(new SinglePluginLoaderWithRemoval("test-atlassian-plugin.xml"));
647         manager.init();
648 
649         pluginStateStore.save(PluginPersistentState.Builder.create(pluginStateStore.load()).setEnabled(
650                 getPluginAccessor().getPluginModule("test.atlassian.plugin:bear"), false).toState());
651 
652         manager.shutdown();
653 
654         manager = newDefaultPluginManager(new SinglePluginLoaderWithRemoval("test-atlassian-plugin-newer.xml"));
655         manager.init();
656 
657         final Plugin plugin = getPluginAccessor().getPlugin("test.atlassian.plugin");
658         assertEquals("1.1", plugin.getPluginInformation().getVersion());
659         assertFalse(getPluginAccessor().isPluginModuleEnabled("test.atlassian.plugin:bear"));
660         assertTrue(getPluginAccessor().isPluginModuleEnabled("test.atlassian.plugin:gold"));
661     }
662 
663     @Test
664     public void testLoadChangedDynamicPluginWithSameVersionNumberDoesNotReplaceExisting() throws PluginParseException {
665         moduleDescriptorFactory.addModuleDescriptor("mineral", MockMineralModuleDescriptor.class);
666         moduleDescriptorFactory.addModuleDescriptor("animal", MockAnimalModuleDescriptor.class);
667 
668         manager = newDefaultPluginManager(
669                 new SinglePluginLoaderWithRemoval("test-atlassian-plugin.xml"),
670                 new SinglePluginLoaderWithRemoval("test-atlassian-plugin-changed-same-version.xml"));
671         manager.init();
672 
673         final Plugin plugin = getPluginAccessor().getPlugin("test.atlassian.plugin");
674         assertEquals("Test Plugin", plugin.getName());
675     }
676 
677     @Test
678     public void testGetPluginsWithPluginMatchingPluginPredicate() throws Exception {
679         final Plugin plugin = mockTestPlugin(Collections.emptyList());
680 
681         final Predicate<Plugin> mockPluginPredicate = mock(Predicate.class);
682         when(mockPluginPredicate.test(plugin)).thenReturn(true);
683 
684         manager = newDefaultPluginManager();
685         manager.addPlugins(null, Collections.singletonList(plugin));
686         final Collection<Plugin> plugins = getPluginAccessor().getPlugins(mockPluginPredicate);
687 
688         assertThat(plugins, hasSize(1));
689         assertTrue(plugins.contains(plugin));
690         verify(mockPluginPredicate).test(any(Plugin.class));
691     }
692 
693     @Test
694     public void testGetPluginsWithPluginNotMatchingPluginPredicate() {
695         final Plugin plugin = mockTestPlugin(Collections.emptyList());
696 
697         manager = newDefaultPluginManager();
698         manager.addPlugins(null, Collections.singletonList(plugin));
699         final Collection<Plugin> plugins = getPluginAccessor().getPlugins((Predicate<Plugin>) p -> false);
700 
701         assertThat(plugins, hasSize(0));
702     }
703 
704     @Test
705     public void testGetPluginModulesWithModuleMatchingPredicate() throws Exception {
706         final MockThing module = new MockThing() {
707         };
708         @SuppressWarnings("unchecked")
709         final ModuleDescriptor<MockThing> moduleDescriptor = mock(ModuleDescriptor.class);
710         when(moduleDescriptor.getModule()).thenReturn(module);
711         when(moduleDescriptor.getPluginKey()).thenReturn("some-plugin-key");
712         when(moduleDescriptor.isEnabledByDefault()).thenReturn(true);
713 
714         final Plugin plugin = mockTestPlugin(Collections.singleton(moduleDescriptor));
715         when(plugin.getModuleDescriptor("module")).thenReturn((ModuleDescriptor) moduleDescriptor);
716 
717         final Predicate<ModuleDescriptor<MockThing>> predicate = mock(Predicate.class);
718         when(predicate.test(moduleDescriptor)).thenReturn(true);
719 
720         manager = newDefaultPluginManager();
721         manager.addPlugins(null, Collections.singletonList(plugin));
722         @SuppressWarnings("unchecked")
723         final Collection<MockThing> modules = getPluginAccessor().getModules(predicate);
724 
725         assertThat(modules, hasSize(1));
726         assertTrue(modules.contains(module));
727 
728         verify(predicate).test(moduleDescriptor);
729     }
730 
731     @Test
732     public void testGetPluginModulesWithGetModuleThrowingException() throws Exception {
733         final Plugin badPlugin = new StaticPlugin();
734         badPlugin.setKey("bad");
735         final AtomicInteger getModuleCallCount = new AtomicInteger();
736 
737         final MockModuleDescriptor<Object> badDescriptor = new MockModuleDescriptor<Object>(badPlugin, "bad", new Object()) {
738             @Override
739             public Object getModule() {
740                 getModuleCallCount.incrementAndGet();
741                 throw new RuntimeException();
742             }
743         };
744         badPlugin.addModuleDescriptor(badDescriptor);
745 
746         final Plugin goodPlugin = new StaticPlugin();
747         goodPlugin.setKey("good");
748         final MockModuleDescriptor<Object> goodDescriptor = new MockModuleDescriptor<Object>(goodPlugin, "good", new Object());
749         goodPlugin.addModuleDescriptor(goodDescriptor);
750 
751         manager = newDefaultPluginManager();
752         manager.addPlugins(null, Arrays.asList(goodPlugin, badPlugin));
753         manager.enablePlugins("bad", "good");
754 
755         assertTrue(getPluginAccessor().isPluginEnabled("bad"));
756         assertTrue(getPluginAccessor().isPluginEnabled("good"));
757         final Collection<Object> modules = getPluginAccessor().getEnabledModulesByClass(Object.class);
758 
759         assertThat(modules, hasSize(1));
760 
761         getPluginAccessor().getEnabledModulesByClass(Object.class);
762         assertThat(getModuleCallCount.get(), is(1));
763     }
764 
765     @Test
766     public void testGetPluginModulesWith2GetModulesThrowingExceptionOnlyNotifiesOnce() throws Exception {
767         final Plugin badPlugin = new StaticPlugin();
768         badPlugin.setKey("bad");
769         final MockModuleDescriptor<Object> badDescriptor = new MockModuleDescriptor<Object>(badPlugin, "bad", new Object()) {
770             @Override
771             public Object getModule() {
772                 throw new RuntimeException();
773             }
774         };
775         badPlugin.addModuleDescriptor(badDescriptor);
776         final MockModuleDescriptor<Object> badDescriptor2 = new MockModuleDescriptor<Object>(badPlugin, "bad2", new Object()) {
777             @Override
778             public Object getModule() {
779                 throw new RuntimeException();
780             }
781         };
782         badPlugin.addModuleDescriptor(badDescriptor2);
783 
784         final Plugin goodPlugin = new StaticPlugin();
785         goodPlugin.setKey("good");
786         final MockModuleDescriptor<Object> goodDescriptor = new MockModuleDescriptor<Object>(goodPlugin, "good", new Object());
787         goodPlugin.addModuleDescriptor(goodDescriptor);
788         DisabledPluginCounter counter = new DisabledPluginCounter();
789         pluginEventManager.register(counter);
790 
791         manager = newDefaultPluginManager();
792         manager.addPlugins(null, Arrays.asList(goodPlugin, badPlugin));
793         manager.enablePlugins("bad", "good");
794 
795         assertTrue(getPluginAccessor().isPluginEnabled("bad"));
796         assertTrue(getPluginAccessor().isPluginEnabled("good"));
797         final Collection<Object> modules = getPluginAccessor().getEnabledModulesByClass(Object.class);
798 
799         assertThat(modules, hasSize(1));
800     }
801 
802     public static class DisabledPluginCounter {
803         int disableCount = 0;
804 
805         @PluginEventListener
806         public void consume(PluginDisabledEvent element) {
807             disableCount++;
808         }
809     }
810 
811     @Test
812     public void testGetPluginModulesWithModuleNotMatchingPredicate() throws Exception {
813         @SuppressWarnings("unchecked")
814         final ModuleDescriptor<MockThing> moduleDescriptor = mock(ModuleDescriptor.class);
815         when(moduleDescriptor.getPluginKey()).thenReturn("some-plugin-key");
816         when(moduleDescriptor.isEnabledByDefault()).thenReturn(true);
817 
818         final Plugin plugin = mockTestPlugin(Collections.singleton(moduleDescriptor));
819         when(plugin.getModuleDescriptor("module")).thenReturn((ModuleDescriptor) moduleDescriptor);
820 
821         @SuppressWarnings("unchecked")
822         final Predicate<ModuleDescriptor<MockThing>> predicate = mock(Predicate.class);
823 
824         manager = newDefaultPluginManager();
825         manager.addPlugins(null, Collections.singletonList(plugin));
826         final Collection<MockThing> modules = getPluginAccessor().getModules(predicate);
827 
828         assertThat(modules, hasSize(0));
829 
830         verify(predicate).test(moduleDescriptor);
831     }
832 
833     @Test
834     public void testGetPluginModuleDescriptorWithModuleMatchingPredicate() throws Exception {
835         @SuppressWarnings("unchecked")
836         final ModuleDescriptor<MockThing> moduleDescriptor = mock(ModuleDescriptor.class);
837         when(moduleDescriptor.getPluginKey()).thenReturn("some-plugin-key");
838         when(moduleDescriptor.isEnabledByDefault()).thenReturn(true);
839 
840         final Plugin plugin = mockTestPlugin(Collections.singleton(moduleDescriptor));
841         when(plugin.getModuleDescriptor("module")).thenReturn((ModuleDescriptor) moduleDescriptor);
842 
843         @SuppressWarnings("unchecked")
844         final Predicate<ModuleDescriptor<MockThing>> predicate = mock(Predicate.class);
845         when(predicate.test(moduleDescriptor)).thenReturn(true);
846 
847         manager = newDefaultPluginManager();
848         manager.addPlugins(null, Collections.singletonList(plugin));
849         final Collection<ModuleDescriptor<MockThing>> modules = getPluginAccessor().getModuleDescriptors(predicate);
850 
851         assertThat(modules, hasSize(1));
852         assertTrue(modules.contains(moduleDescriptor));
853 
854         verify(predicate).test(moduleDescriptor);
855     }
856 
857     @Test
858     public void testGetPluginModuleDescriptorsWithModuleNotMatchingPredicate() throws Exception {
859         @SuppressWarnings("unchecked")
860         final ModuleDescriptor<MockThing> moduleDescriptor = mock(ModuleDescriptor.class);
861         when(moduleDescriptor.getPluginKey()).thenReturn("some-plugin-key");
862         when(moduleDescriptor.isEnabledByDefault()).thenReturn(true);
863 
864         final Plugin plugin = mockTestPlugin(Collections.singleton(moduleDescriptor));
865         when(plugin.getModuleDescriptor("module")).thenReturn((ModuleDescriptor) moduleDescriptor);
866 
867         @SuppressWarnings("unchecked")
868         final Predicate<ModuleDescriptor<MockThing>> predicate = mock(Predicate.class);
869 
870         manager = newDefaultPluginManager();
871         manager.addPlugins(null, Collections.singletonList(plugin));
872         final Collection<MockThing> modules = getPluginAccessor().getModules(predicate);
873 
874         assertThat(modules, hasSize(0));
875 
876         verify(predicate).test(moduleDescriptor);
877     }
878 
879     @Test
880     public void testGetPluginAndModules() throws PluginParseException {
881         moduleDescriptorFactory.addModuleDescriptor("animal", MockAnimalModuleDescriptor.class);
882         moduleDescriptorFactory.addModuleDescriptor("mineral", MockMineralModuleDescriptor.class);
883 
884         manager = newDefaultPluginManager(new SinglePluginLoader("test-atlassian-plugin.xml"));
885         manager.init();
886 
887         final Plugin plugin = getPluginAccessor().getPlugin("test.atlassian.plugin");
888         assertNotNull(plugin);
889         assertEquals("Test Plugin", plugin.getName());
890 
891         final ModuleDescriptor<?> bear = plugin.getModuleDescriptor("bear");
892         assertEquals(bear, getPluginAccessor().getPluginModule("test.atlassian.plugin:bear"));
893     }
894 
895     @Test
896     public void testGetModuleByModuleClassOneFound() throws PluginParseException {
897         moduleDescriptorFactory.addModuleDescriptor("animal", MockAnimalModuleDescriptor.class);
898         moduleDescriptorFactory.addModuleDescriptor("mineral", MockMineralModuleDescriptor.class);
899 
900         manager = newDefaultPluginManager(new SinglePluginLoader("test-atlassian-plugin.xml"));
901         manager.init();
902 
903         final List<MockAnimalModuleDescriptor> animalDescriptors = getPluginAccessor().getEnabledModuleDescriptorsByClass(MockAnimalModuleDescriptor.class);
904         assertNotNull(animalDescriptors);
905         assertThat(animalDescriptors, hasSize(1));
906         final ModuleDescriptor<MockAnimal> moduleDescriptor = animalDescriptors.iterator().next();
907         assertEquals("Bear Animal", moduleDescriptor.getName());
908 
909         final List<MockMineralModuleDescriptor> mineralDescriptors = getPluginAccessor().getEnabledModuleDescriptorsByClass(MockMineralModuleDescriptor.class);
910         assertNotNull(mineralDescriptors);
911         assertThat(mineralDescriptors, hasSize(1));
912         final ModuleDescriptor<MockMineral> mineralDescriptor = mineralDescriptors.iterator().next();
913         assertEquals("Bar", mineralDescriptor.getName());
914     }
915 
916     @Test
917     public void testGetModuleByModuleClassNoneFound() throws PluginParseException {
918         moduleDescriptorFactory.addModuleDescriptor("animal", MockAnimalModuleDescriptor.class);
919         moduleDescriptorFactory.addModuleDescriptor("mineral", MockMineralModuleDescriptor.class);
920 
921         manager = newDefaultPluginManager(new SinglePluginLoader("test-atlassian-plugin.xml"));
922         manager.init();
923 
924         class MockSilver implements MockMineral {
925             public int getWeight() {
926                 return 3;
927             }
928         }
929 
930         final Collection<MockSilver> descriptors = getPluginAccessor().getEnabledModulesByClass(MockSilver.class);
931         assertNotNull(descriptors);
932         assertTrue(descriptors.isEmpty());
933     }
934 
935     @Test
936     public void testGetEnabledPluginsDoesNotReturnEnablingPlugins() throws Exception {
937         final PluginLoader mockPluginLoader = mock(PluginLoader.class);
938 
939         final Plugin firstPlugin = new StaticPlugin();
940         firstPlugin.setKey("first");
941         firstPlugin.setEnabledByDefault(false);
942         firstPlugin.setPluginInformation(new PluginInformation());
943 
944         manager = newDefaultPluginManager();
945 
946         final Plugin secondPlugin = new StaticPlugin() {
947             public PluginState enableInternal() {
948                 try {
949                     // Assert here when the first plugin has been started but this plugin still has not been loaded
950                     assertThat(getPluginAccessor().getPlugins(), hasSize(2));
951                     assertThat("First plugin should not be enabled", manager.getEnabledPlugins(), hasSize(0));
952                 } catch (Exception e) {
953                     throw new RuntimeException(e);
954                 }
955                 return PluginState.ENABLED;
956             }
957 
958             public void disableInternal() {
959                 // do nothing
960             }
961         };
962         PluginPersistentStateModifier stateModifier = new PluginPersistentStateModifier(pluginStateStore);
963         stateModifier.enable(firstPlugin);
964         secondPlugin.setKey("second");
965         secondPlugin.setEnabledByDefault(false);
966         secondPlugin.setPluginInformation(new PluginInformation());
967         stateModifier.enable(secondPlugin);
968 
969         when(mockPluginLoader.loadAllPlugins(any(ModuleDescriptorFactory.class))).thenReturn(Arrays.asList(firstPlugin, secondPlugin));
970 
971         manager = newDefaultPluginManager(mockPluginLoader);
972         manager.init();
973     }
974 
975     @Test
976     public void testFindingNewPlugins() throws PluginParseException, IOException {
977         createFillAndCleanTempPluginDirectory();
978 
979         //delete paddington for the timebeing
980         final File paddington = new File(pluginsTestDir, PADDINGTON_JAR);
981         paddington.delete();
982 
983         manager = makeClassLoadingPluginManager();
984 
985         assertThat(getPluginAccessor().getPlugins(), hasSize(1));
986         assertNotNull(getPluginAccessor().getPlugin("test.atlassian.plugin.classloaded2"));
987 
988         //restore paddington to test plugins dir
989         FileUtils.copyDirectory(pluginsDirectory, pluginsTestDir);
990 
991         final int foundFirstScan = manager.scanForNewPlugins();
992         assertThat(foundFirstScan, is(1));
993         assertThat(getPluginAccessor().getPlugins(), hasSize(2));
994         assertNotNull(getPluginAccessor().getPlugin("test.atlassian.plugin.classloaded2"));
995         assertNotNull(getPluginAccessor().getPlugin("test.atlassian.plugin.classloaded"));
996 
997         final int foundSecondScan = manager.scanForNewPlugins();
998         assertThat(foundSecondScan, is(0));
999         assertThat(getPluginAccessor().getPlugins(), hasSize(2));
1000         assertNotNull(getPluginAccessor().getPlugin("test.atlassian.plugin.classloaded2"));
1001         assertNotNull(getPluginAccessor().getPlugin("test.atlassian.plugin.classloaded"));
1002     }
1003 
1004     @Test
1005     public void testFindingNewPluginsNotLoadingRestartRequiredDescriptors() throws PluginParseException, IOException {
1006         createFillAndCleanTempPluginDirectory();
1007 
1008         final DynamicSinglePluginLoader dynamicSinglePluginLoader = new DynamicSinglePluginLoader("test.atlassian.plugin", "test-requiresRestart-plugin.xml");
1009         manager = makeClassLoadingPluginManager(dynamicSinglePluginLoader);
1010 
1011         moduleDescriptorFactory.addModuleDescriptor("requiresRestart", RequiresRestartModuleDescriptor.class);
1012 
1013         assertThat(getPluginAccessor().getPlugins(), hasSize(2));
1014         assertNotNull(getPluginAccessor().getPlugin("test.atlassian.plugin.classloaded2"));
1015 
1016         // enable the dynamic plugin loader
1017         dynamicSinglePluginLoader.canLoad.set(true);
1018 
1019         manager.scanForNewPlugins();
1020         assertThat(getPluginAccessor().getPlugins(), hasSize(3));
1021         assertNotNull(getPluginAccessor().getPlugin("test.atlassian.plugin.classloaded2"));
1022         assertNotNull(getPluginAccessor().getPlugin("test.atlassian.plugin"));
1023 
1024         final Plugin plugin = getPluginAccessor().getPlugin("test.atlassian.plugin");
1025         assertTrue(plugin instanceof UnloadablePlugin);
1026         assertTrue(((UnloadablePlugin) plugin).getErrorText().contains("foo"));
1027 
1028         assertEquals(PluginRestartState.INSTALL, getPluginAccessor().getPluginRestartState("test.atlassian.plugin"));
1029     }
1030 
1031     /**
1032      * Tests upgrade of plugin where the old version didn't have any restart required module descriptors, but the new one does
1033      */
1034     @Test
1035     public void testFindingUpgradePluginsNotLoadingRestartRequiredDescriptors() throws PluginParseException, IOException {
1036         createFillAndCleanTempPluginDirectory();
1037 
1038         moduleDescriptorFactory.addModuleDescriptor("requiresRestart", RequiresRestartModuleDescriptor.class);
1039 
1040         final DynamicSinglePluginLoader dynamicSinglePluginLoader = new DynamicSinglePluginLoader("test.atlassian.plugin.classloaded2", "test-requiresRestartWithUpgrade-plugin.xml");
1041         manager = makeClassLoadingPluginManager(dynamicSinglePluginLoader);
1042 
1043         assertThat(getPluginAccessor().getPlugins(), hasSize(2));
1044         assertNotNull(getPluginAccessor().getPlugin("test.atlassian.plugin.classloaded2"));
1045         assertThat(getPluginAccessor().getEnabledModuleDescriptorsByClass(RequiresRestartModuleDescriptor.class), hasSize(0));
1046 
1047 
1048         dynamicSinglePluginLoader.canLoad.set(true);
1049 
1050         manager.scanForNewPlugins();
1051         assertThat(getPluginAccessor().getPlugins(), hasSize(2));
1052         assertNotNull(getPluginAccessor().getPlugin("test.atlassian.plugin.classloaded2"));
1053         assertEquals(PluginRestartState.UPGRADE, getPluginAccessor().getPluginRestartState("test.atlassian.plugin.classloaded2"));
1054         assertThat(getPluginAccessor().getEnabledModuleDescriptorsByClass(RequiresRestartModuleDescriptor.class), hasSize(0));
1055     }
1056 
1057     @Test
1058     public void testInstallPluginThatRequiresRestart() throws PluginParseException, IOException, InterruptedException {
1059         createFillAndCleanTempPluginDirectory();
1060         moduleDescriptorFactory.addModuleDescriptor("requiresRestart", RequiresRestartModuleDescriptor.class);
1061         manager = makeClassLoadingPluginManager();
1062         assertThat(getPluginAccessor().getPlugins(), hasSize(2));
1063 
1064         new PluginJarBuilder().addFormattedResource("atlassian-plugin.xml",
1065                 "<atlassian-plugin name='Test 2' i18n-name-key='test.name' key='test.restartrequired' pluginsVersion='1'>", "    <plugin-info>", "        <version>1.0</version>",
1066                 "    </plugin-info>", "    <requiresRestart key='foo' />", "</atlassian-plugin>").build(pluginsTestDir);
1067         manager.scanForNewPlugins();
1068 
1069         assertThat(getPluginAccessor().getPlugins(), hasSize(3));
1070         Plugin plugin = getPluginAccessor().getPlugin("test.restartrequired");
1071         assertNotNull(plugin);
1072         assertEquals("Test 2", plugin.getName());
1073         assertEquals("test.name", plugin.getI18nNameKey());
1074         assertEquals(1, plugin.getPluginsVersion());
1075         assertEquals("1.0", plugin.getPluginInformation().getVersion());
1076         assertFalse(getPluginAccessor().isPluginEnabled("test.restartrequired"));
1077         assertThat(getPluginAccessor().getEnabledModuleDescriptorsByClass(RequiresRestartModuleDescriptor.class), hasSize(0));
1078         assertEquals(PluginRestartState.INSTALL, getPluginAccessor().getPluginRestartState("test.restartrequired"));
1079 
1080         manager.shutdown();
1081         manager.init();
1082 
1083         assertThat(getPluginAccessor().getPlugins(), hasSize(3));
1084         assertNotNull(plugin);
1085         assertTrue(getPluginAccessor().isPluginEnabled("test.restartrequired"));
1086         assertThat(getPluginAccessor().getEnabledModuleDescriptorsByClass(RequiresRestartModuleDescriptor.class), hasSize(1));
1087         assertEquals(PluginRestartState.NONE, getPluginAccessor().getPluginRestartState("test.restartrequired"));
1088     }
1089 
1090     @Test
1091     public void testInstallPluginThatRequiresRestartThenRevert() throws PluginParseException, IOException, InterruptedException {
1092         createFillAndCleanTempPluginDirectory();
1093         moduleDescriptorFactory.addModuleDescriptor("requiresRestart", RequiresRestartModuleDescriptor.class);
1094         manager = makeClassLoadingPluginManager();
1095         manager.setPluginInstaller(new FilePluginInstaller(pluginsTestDir));
1096         assertThat(getPluginAccessor().getPlugins(), hasSize(2));
1097 
1098         File pluginJar = new PluginJarBuilder().addFormattedResource("atlassian-plugin.xml",
1099                 "<atlassian-plugin name='Test 2' i18n-name-key='test.name' key='test.restartrequired' pluginsVersion='1'>", "    <plugin-info>", "        <version>1.0</version>",
1100                 "    </plugin-info>", "    <requiresRestart key='foo' />", "</atlassian-plugin>").build();
1101         manager.installPlugins(new JarPluginArtifact(pluginJar));
1102 
1103         assertThat(getPluginAccessor().getPlugins(), hasSize(3));
1104         Plugin plugin = getPluginAccessor().getPlugin("test.restartrequired");
1105         assertNotNull(plugin);
1106         assertEquals("Test 2", plugin.getName());
1107         assertEquals("test.name", plugin.getI18nNameKey());
1108         assertEquals(1, plugin.getPluginsVersion());
1109         assertEquals("1.0", plugin.getPluginInformation().getVersion());
1110         assertFalse(getPluginAccessor().isPluginEnabled("test.restartrequired"));
1111         assertThat(getPluginAccessor().getEnabledModuleDescriptorsByClass(RequiresRestartModuleDescriptor.class), hasSize(0));
1112         assertEquals(PluginRestartState.INSTALL, getPluginAccessor().getPluginRestartState("test.restartrequired"));
1113 
1114         manager.revertRestartRequiredChange("test.restartrequired");
1115         assertEquals(PluginRestartState.NONE, getPluginAccessor().getPluginRestartState("test.restartrequired"));
1116 
1117         manager.shutdown();
1118         manager.init();
1119 
1120         assertThat(getPluginAccessor().getPlugins(), hasSize(2));
1121         assertFalse(getPluginAccessor().isPluginEnabled("test.restartrequired"));
1122         assertEquals(PluginRestartState.NONE, getPluginAccessor().getPluginRestartState("test.restartrequired"));
1123     }
1124 
1125     @Test
1126     public void testUpgradePluginThatRequiresRestart() throws PluginParseException, IOException, InterruptedException {
1127         createFillAndCleanTempPluginDirectory();
1128         moduleDescriptorFactory.addModuleDescriptor("requiresRestart", RequiresRestartModuleDescriptor.class);
1129 
1130         final File origFile = new PluginJarBuilder().addFormattedResource("atlassian-plugin.xml",
1131                 "<atlassian-plugin name='Test 2' key='test.restartrequired' pluginsVersion='1'>", "    <plugin-info>", "        <version>1.0</version>",
1132                 "    </plugin-info>", "    <requiresRestart key='foo' />", "</atlassian-plugin>").build(pluginsTestDir);
1133 
1134         manager = makeClassLoadingPluginManager();
1135 
1136         assertThat(getPluginAccessor().getPlugins(), hasSize(3));
1137         assertNotNull(getPluginAccessor().getPlugin("test.restartrequired"));
1138         assertTrue(getPluginAccessor().isPluginEnabled("test.restartrequired"));
1139         assertThat(getPluginAccessor().getEnabledModuleDescriptorsByClass(RequiresRestartModuleDescriptor.class), hasSize(1));
1140         assertEquals(PluginRestartState.NONE, getPluginAccessor().getPluginRestartState("test.restartrequired"));
1141 
1142         // Some filesystems only record last modified in seconds
1143         Thread.sleep(1000);
1144         final File updateFile = new PluginJarBuilder().addFormattedResource("atlassian-plugin.xml",
1145                 "<atlassian-plugin name='Test 2' key='test.restartrequired' pluginsVersion='1'>", "    <plugin-info>", "        <version>2.0</version>",
1146                 "    </plugin-info>", "    <requiresRestart key='foo' />", "    <requiresRestart key='bar' />", "</atlassian-plugin>").build();
1147 
1148         origFile.delete();
1149         FileUtils.moveFile(updateFile, origFile);
1150 
1151         manager.scanForNewPlugins();
1152         assertThat(getPluginAccessor().getPlugins(), hasSize(3));
1153         assertNotNull(getPluginAccessor().getPlugin("test.restartrequired"));
1154         assertTrue(getPluginAccessor().isPluginEnabled("test.restartrequired"));
1155         assertEquals(PluginRestartState.UPGRADE, getPluginAccessor().getPluginRestartState("test.restartrequired"));
1156         assertThat(getPluginAccessor().getEnabledModuleDescriptorsByClass(RequiresRestartModuleDescriptor.class), hasSize(1));
1157 
1158         manager.shutdown();
1159         manager.init();
1160 
1161         assertThat(getPluginAccessor().getPlugins(), hasSize(3));
1162         assertNotNull(getPluginAccessor().getPlugin("test.restartrequired"));
1163         assertTrue(getPluginAccessor().isPluginEnabled("test.restartrequired"));
1164         assertThat(getPluginAccessor().getEnabledModuleDescriptorsByClass(RequiresRestartModuleDescriptor.class), hasSize(2));
1165         assertEquals(PluginRestartState.NONE, getPluginAccessor().getPluginRestartState("test.restartrequired"));
1166     }
1167 
1168     @Test
1169     public void testUpgradePluginThatRequiresRestartThenReverted() throws PluginParseException, IOException, InterruptedException {
1170         createFillAndCleanTempPluginDirectory();
1171         moduleDescriptorFactory.addModuleDescriptor("requiresRestart", RequiresRestartModuleDescriptor.class);
1172 
1173         new PluginJarBuilder().addFormattedResource("atlassian-plugin.xml",
1174                 "<atlassian-plugin name='Test 2' key='test.restartrequired' pluginsVersion='1'>",
1175                 "    <plugin-info>",
1176                 "        <version>1.0</version>",
1177                 "    </plugin-info>",
1178                 "    <requiresRestart key='foo' />",
1179                 "</atlassian-plugin>")
1180                 .build(pluginsTestDir);
1181 
1182         manager = makeClassLoadingPluginManager();
1183         manager.setPluginInstaller(new FilePluginInstaller(pluginsTestDir));
1184 
1185         assertThat(getPluginAccessor().getPlugins(), hasSize(3));
1186         assertNotNull(getPluginAccessor().getPlugin("test.restartrequired"));
1187         assertTrue(getPluginAccessor().isPluginEnabled("test.restartrequired"));
1188         assertThat(getPluginAccessor().getEnabledModuleDescriptorsByClass(RequiresRestartModuleDescriptor.class), hasSize(1));
1189         assertEquals(PluginRestartState.NONE, getPluginAccessor().getPluginRestartState("test.restartrequired"));
1190 
1191         // Some filesystems only record last modified in seconds
1192         Thread.sleep(1000);
1193         final File updateFile = new PluginJarBuilder().addFormattedResource("atlassian-plugin.xml",
1194                 "<atlassian-plugin name='Test 2' key='test.restartrequired' pluginsVersion='1'>", "    <plugin-info>", "        <version>2.0</version>",
1195                 "    </plugin-info>", "    <requiresRestart key='foo' />", "    <requiresRestart key='bar' />", "</atlassian-plugin>").build();
1196 
1197         manager.installPlugins(new JarPluginArtifact(updateFile));
1198 
1199         assertThat(getPluginAccessor().getPlugins(), hasSize(3));
1200         assertNotNull(getPluginAccessor().getPlugin("test.restartrequired"));
1201         assertTrue(getPluginAccessor().isPluginEnabled("test.restartrequired"));
1202         assertEquals(PluginRestartState.UPGRADE, getPluginAccessor().getPluginRestartState("test.restartrequired"));
1203         assertThat(getPluginAccessor().getEnabledModuleDescriptorsByClass(RequiresRestartModuleDescriptor.class), hasSize(1));
1204 
1205         manager.revertRestartRequiredChange("test.restartrequired");
1206         assertEquals(PluginRestartState.NONE, manager.getPluginRestartState("test.restartrequired"));
1207 
1208         manager.shutdown();
1209         manager.init();
1210 
1211         assertThat(getPluginAccessor().getPlugins(), hasSize(3));
1212         assertNotNull(getPluginAccessor().getPlugin("test.restartrequired"));
1213         assertTrue(getPluginAccessor().isPluginEnabled("test.restartrequired"));
1214         assertEquals("1.0", getPluginAccessor().getPlugin("test.restartrequired").getPluginInformation().getVersion());
1215         assertThat(getPluginAccessor().getEnabledModuleDescriptorsByClass(RequiresRestartModuleDescriptor.class), hasSize(1));
1216         assertEquals(PluginRestartState.NONE, getPluginAccessor().getPluginRestartState("test.restartrequired"));
1217     }
1218 
1219     @Test
1220     public void testUpgradePluginThatRequiresRestartThenRevertedRevertsToOriginalPlugin() throws PluginParseException, IOException, InterruptedException {
1221         createFillAndCleanTempPluginDirectory();
1222         moduleDescriptorFactory.addModuleDescriptor("requiresRestart", RequiresRestartModuleDescriptor.class);
1223 
1224         // Add the plugin to the plugins test directory so it is included when we first start up
1225         new PluginJarBuilder().addFormattedResource("atlassian-plugin.xml",
1226                 "<atlassian-plugin name='Test 2' key='test.restartrequired' pluginsVersion='1'>", "    <plugin-info>", "        <version>1.0</version>",
1227                 "    </plugin-info>", "    <requiresRestart key='foo' />", "</atlassian-plugin>").build(pluginsTestDir);
1228 
1229         manager = makeClassLoadingPluginManager();
1230         manager.setPluginInstaller(new FilePluginInstaller(pluginsTestDir));
1231 
1232         assertThat(getPluginAccessor().getPlugins(), hasSize(3));
1233         assertNotNull(getPluginAccessor().getPlugin("test.restartrequired"));
1234         assertTrue(getPluginAccessor().isPluginEnabled("test.restartrequired"));
1235         assertThat(getPluginAccessor().getEnabledModuleDescriptorsByClass(RequiresRestartModuleDescriptor.class), hasSize(1));
1236         assertEquals(PluginRestartState.NONE, getPluginAccessor().getPluginRestartState("test.restartrequired"));
1237 
1238         // Some filesystems only record last modified in seconds
1239         Thread.sleep(1000);
1240         final File updateFile = new PluginJarBuilder().addFormattedResource("atlassian-plugin.xml",
1241                 "<atlassian-plugin name='Test 2' key='test.restartrequired' pluginsVersion='1'>", "    <plugin-info>", "        <version>2.0</version>",
1242                 "    </plugin-info>", "    <requiresRestart key='foo' />", "    <requiresRestart key='bar' />", "</atlassian-plugin>").build();
1243 
1244         // Install version 2 of the plugin
1245         manager.installPlugins(new JarPluginArtifact(updateFile));
1246 
1247         assertThat(getPluginAccessor().getPlugins(), hasSize(3));
1248         assertNotNull(getPluginAccessor().getPlugin("test.restartrequired"));
1249         assertTrue(getPluginAccessor().isPluginEnabled("test.restartrequired"));
1250         assertEquals(PluginRestartState.UPGRADE, getPluginAccessor().getPluginRestartState("test.restartrequired"));
1251         assertThat(getPluginAccessor().getEnabledModuleDescriptorsByClass(RequiresRestartModuleDescriptor.class), hasSize(1));
1252 
1253         Thread.sleep(1000);
1254         final File updateFile2 = new PluginJarBuilder().addFormattedResource("atlassian-plugin.xml",
1255                 "<atlassian-plugin name='Test 2' key='test.restartrequired' pluginsVersion='1'>", "    <plugin-info>", "        <version>3.0</version>",
1256                 "    </plugin-info>", "    <requiresRestart key='foo' />", "    <requiresRestart key='bar' />", "</atlassian-plugin>").build();
1257 
1258         // Install version 3 of the plugin
1259         manager.installPlugins(new JarPluginArtifact(updateFile2));
1260 
1261         assertThat(getPluginAccessor().getPlugins(), hasSize(3));
1262         assertNotNull(getPluginAccessor().getPlugin("test.restartrequired"));
1263         assertTrue(getPluginAccessor().isPluginEnabled("test.restartrequired"));
1264         assertEquals(PluginRestartState.UPGRADE, getPluginAccessor().getPluginRestartState("test.restartrequired"));
1265         assertThat(getPluginAccessor().getEnabledModuleDescriptorsByClass(RequiresRestartModuleDescriptor.class), hasSize(1));
1266 
1267         // Lets revert the whole upgrade so that the original plugin is restored
1268         manager.revertRestartRequiredChange("test.restartrequired");
1269         assertEquals(PluginRestartState.NONE, manager.getPluginRestartState("test.restartrequired"));
1270         assertEquals("1.0", getPluginAccessor().getPlugin("test.restartrequired").getPluginInformation().getVersion());
1271 
1272         manager.shutdown();
1273         manager.init();
1274 
1275         assertThat(getPluginAccessor().getPlugins(), hasSize(3));
1276         assertNotNull(getPluginAccessor().getPlugin("test.restartrequired"));
1277         assertTrue(getPluginAccessor().isPluginEnabled("test.restartrequired"));
1278         assertEquals("1.0", getPluginAccessor().getPlugin("test.restartrequired").getPluginInformation().getVersion());
1279         assertThat(getPluginAccessor().getEnabledModuleDescriptorsByClass(RequiresRestartModuleDescriptor.class), hasSize(1));
1280         assertEquals(PluginRestartState.NONE, getPluginAccessor().getPluginRestartState("test.restartrequired"));
1281     }
1282 
1283     @Test
1284     public void testUpgradePluginThatRequiresRestartMultipleTimeStaysUpgraded() throws PluginParseException, IOException, InterruptedException {
1285         createFillAndCleanTempPluginDirectory();
1286         moduleDescriptorFactory.addModuleDescriptor("requiresRestart", RequiresRestartModuleDescriptor.class);
1287 
1288         final File origFile = new PluginJarBuilder().addFormattedResource("atlassian-plugin.xml",
1289                 "<atlassian-plugin name='Test 2' key='test.restartrequired' pluginsVersion='1'>", "    <plugin-info>", "        <version>1.0</version>",
1290                 "    </plugin-info>", "    <requiresRestart key='foo' />", "</atlassian-plugin>").build(pluginsTestDir);
1291 
1292         manager = makeClassLoadingPluginManager();
1293         manager.setPluginInstaller(new FilePluginInstaller(pluginsTestDir));
1294 
1295         assertThat(getPluginAccessor().getPlugins(), hasSize(3));
1296         assertNotNull(getPluginAccessor().getPlugin("test.restartrequired"));
1297         assertTrue(getPluginAccessor().isPluginEnabled("test.restartrequired"));
1298         assertThat(getPluginAccessor().getEnabledModuleDescriptorsByClass(RequiresRestartModuleDescriptor.class), hasSize(1));
1299         assertEquals(PluginRestartState.NONE, getPluginAccessor().getPluginRestartState("test.restartrequired"));
1300 
1301         // Some filesystems only record last modified in seconds
1302         Thread.sleep(1000);
1303         final File updateFile = new PluginJarBuilder().addFormattedResource("atlassian-plugin.xml",
1304                 "<atlassian-plugin name='Test 2' key='test.restartrequired' pluginsVersion='1'>", "    <plugin-info>", "        <version>2.0</version>",
1305                 "    </plugin-info>", "    <requiresRestart key='foo' />", "</atlassian-plugin>").build();
1306 
1307         manager.installPlugins(new JarPluginArtifact(updateFile));
1308 
1309         assertThat(getPluginAccessor().getPlugins(), hasSize(3));
1310         assertNotNull(getPluginAccessor().getPlugin("test.restartrequired"));
1311         assertTrue(getPluginAccessor().isPluginEnabled("test.restartrequired"));
1312         assertEquals(PluginRestartState.UPGRADE, getPluginAccessor().getPluginRestartState("test.restartrequired"));
1313         assertThat(getPluginAccessor().getEnabledModuleDescriptorsByClass(RequiresRestartModuleDescriptor.class), hasSize(1));
1314 
1315         Thread.sleep(1000);
1316         final File updateFile2 = new PluginJarBuilder().addFormattedResource("atlassian-plugin.xml",
1317                 "<atlassian-plugin name='Test 2' key='test.restartrequired' pluginsVersion='1'>", "    <plugin-info>", "        <version>3.0</version>",
1318                 "    </plugin-info>", "    <requiresRestart key='foo' />", "</atlassian-plugin>").build();
1319 
1320         manager.installPlugins(new JarPluginArtifact(updateFile2));
1321 
1322         assertThat(getPluginAccessor().getPlugins(), hasSize(3));
1323         assertNotNull(getPluginAccessor().getPlugin("test.restartrequired"));
1324         assertTrue(getPluginAccessor().isPluginEnabled("test.restartrequired"));
1325         assertEquals(PluginRestartState.UPGRADE, getPluginAccessor().getPluginRestartState("test.restartrequired"));
1326         assertThat(getPluginAccessor().getEnabledModuleDescriptorsByClass(RequiresRestartModuleDescriptor.class), hasSize(1));
1327 
1328         manager.shutdown();
1329         manager.init();
1330 
1331         assertThat(getPluginAccessor().getPlugins(), hasSize(3));
1332         assertNotNull(getPluginAccessor().getPlugin("test.restartrequired"));
1333         assertTrue(getPluginAccessor().isPluginEnabled("test.restartrequired"));
1334         assertThat(getPluginAccessor().getEnabledModuleDescriptorsByClass(RequiresRestartModuleDescriptor.class), hasSize(1));
1335         assertEquals(PluginRestartState.NONE, getPluginAccessor().getPluginRestartState("test.restartrequired"));
1336     }
1337 
1338     @Test
1339     public void testUpgradePluginThatPreviouslyRequiredRestart() throws PluginParseException, IOException, InterruptedException {
1340         createFillAndCleanTempPluginDirectory();
1341         moduleDescriptorFactory.addModuleDescriptor("requiresRestart", RequiresRestartModuleDescriptor.class);
1342 
1343         final File origFile = new PluginJarBuilder().addFormattedResource("atlassian-plugin.xml",
1344                 "<atlassian-plugin name='Test 2' key='test.restartrequired' pluginsVersion='1'>", "    <plugin-info>", "        <version>1.0</version>",
1345                 "    </plugin-info>", "    <requiresRestart key='foo' />", "</atlassian-plugin>").build(pluginsTestDir);
1346 
1347         manager = makeClassLoadingPluginManager();
1348 
1349         assertThat(getPluginAccessor().getPlugins(), hasSize(3));
1350         assertNotNull(getPluginAccessor().getPlugin("test.restartrequired"));
1351         assertTrue(getPluginAccessor().isPluginEnabled("test.restartrequired"));
1352         assertThat(getPluginAccessor().getEnabledModuleDescriptorsByClass(RequiresRestartModuleDescriptor.class), hasSize(1));
1353         assertEquals(PluginRestartState.NONE, getPluginAccessor().getPluginRestartState("test.restartrequired"));
1354 
1355         // Some filesystems only record last modified in seconds
1356         Thread.sleep(1000);
1357         final File updateFile = new PluginJarBuilder().addFormattedResource("atlassian-plugin.xml",
1358                 "<atlassian-plugin name='Test 2' key='test.restartrequired' pluginsVersion='1'>", "    <plugin-info>", "        <version>2.0</version>",
1359                 "    </plugin-info>", "</atlassian-plugin>").build(pluginsTestDir);
1360 
1361         origFile.delete();
1362         FileUtils.moveFile(updateFile, origFile);
1363 
1364         manager.scanForNewPlugins();
1365         assertThat(getPluginAccessor().getPlugins(), hasSize(3));
1366         assertNotNull(getPluginAccessor().getPlugin("test.restartrequired"));
1367         assertTrue(getPluginAccessor().isPluginEnabled("test.restartrequired"));
1368         assertEquals(PluginRestartState.UPGRADE, getPluginAccessor().getPluginRestartState("test.restartrequired"));
1369         assertThat(getPluginAccessor().getEnabledModuleDescriptorsByClass(RequiresRestartModuleDescriptor.class), hasSize(1));
1370 
1371         manager.shutdown();
1372         manager.init();
1373 
1374         assertThat(getPluginAccessor().getEnabledModuleDescriptorsByClass(RequiresRestartModuleDescriptor.class), hasSize(0));
1375         assertEquals(PluginRestartState.NONE, getPluginAccessor().getPluginRestartState("test.restartrequired"));
1376     }
1377 
1378     @Test
1379     public void testInstallPluginThatPreviouslyRequiredRestart() throws PluginParseException, IOException, InterruptedException {
1380         createFillAndCleanTempPluginDirectory();
1381         moduleDescriptorFactory.addModuleDescriptor("requiresRestart", RequiresRestartModuleDescriptor.class);
1382 
1383         manager = makeClassLoadingPluginManager();
1384 
1385         assertThat(getPluginAccessor().getPlugins(), hasSize(2));
1386         assertNull(getPluginAccessor().getPlugin("test.restartrequired"));
1387 
1388         final File origFile = new PluginJarBuilder().addFormattedResource("atlassian-plugin.xml",
1389                 "<atlassian-plugin name='Test 2' key='test.restartrequired' pluginsVersion='1'>", "    <plugin-info>", "        <version>1.0</version>",
1390                 "    </plugin-info>", "    <requiresRestart key='foo' />", "</atlassian-plugin>").build(pluginsTestDir);
1391 
1392         manager.scanForNewPlugins();
1393         assertThat(getPluginAccessor().getPlugins(), hasSize(3));
1394         assertNotNull(getPluginAccessor().getPlugin("test.restartrequired"));
1395         assertFalse(getPluginAccessor().isPluginEnabled("test.restartrequired"));
1396         assertEquals(PluginRestartState.INSTALL, getPluginAccessor().getPluginRestartState("test.restartrequired"));
1397 
1398         // Some filesystems only record last modified in seconds
1399         Thread.sleep(1000);
1400         final File updateFile = new PluginJarBuilder().addFormattedResource("atlassian-plugin.xml",
1401                 "<atlassian-plugin name='Test 2' key='test.restartrequired' pluginsVersion='1'>", "    <plugin-info>", "        <version>2.0</version>",
1402                 "    </plugin-info>", "</atlassian-plugin>").build(pluginsTestDir);
1403 
1404         origFile.delete();
1405         FileUtils.moveFile(updateFile, origFile);
1406 
1407         manager.scanForNewPlugins();
1408         assertThat(getPluginAccessor().getPlugins(), hasSize(3));
1409         assertNotNull(getPluginAccessor().getPlugin("test.restartrequired"));
1410         assertTrue(getPluginAccessor().isPluginEnabled("test.restartrequired"));
1411         assertEquals(PluginRestartState.NONE, getPluginAccessor().getPluginRestartState("test.restartrequired"));
1412         assertThat(getPluginAccessor().getEnabledModuleDescriptorsByClass(RequiresRestartModuleDescriptor.class), hasSize(0));
1413 
1414         manager.shutdown();
1415         manager.init();
1416 
1417         assertThat(getPluginAccessor().getEnabledModuleDescriptorsByClass(RequiresRestartModuleDescriptor.class), hasSize(0));
1418         assertEquals(PluginRestartState.NONE, getPluginAccessor().getPluginRestartState("test.restartrequired"));
1419     }
1420 
1421     @Test
1422     public void testInstallPluginMoreThanOnceStaysAsInstall() throws PluginParseException, IOException, InterruptedException {
1423         createFillAndCleanTempPluginDirectory();
1424         moduleDescriptorFactory.addModuleDescriptor("requiresRestart", RequiresRestartModuleDescriptor.class);
1425 
1426         manager = makeClassLoadingPluginManager();
1427 
1428         assertThat(getPluginAccessor().getPlugins(), hasSize(2));
1429         assertNull(getPluginAccessor().getPlugin("test.restartrequired"));
1430         assertThat(getPluginAccessor().getEnabledModuleDescriptorsByClass(RequiresRestartModuleDescriptor.class), hasSize(0));
1431 
1432         final File origFile = new PluginJarBuilder().addFormattedResource("atlassian-plugin.xml",
1433                 "<atlassian-plugin name='Test 2' key='test.restartrequired' pluginsVersion='1'>", "    <plugin-info>", "        <version>1.0</version>",
1434                 "    </plugin-info>", "    <requiresRestart key='foo' />", "</atlassian-plugin>").build(pluginsTestDir);
1435 
1436         manager.scanForNewPlugins();
1437         assertThat(getPluginAccessor().getPlugins(), hasSize(3));
1438         assertNotNull(getPluginAccessor().getPlugin("test.restartrequired"));
1439         assertFalse(getPluginAccessor().isPluginEnabled("test.restartrequired"));
1440         assertEquals(PluginRestartState.INSTALL, getPluginAccessor().getPluginRestartState("test.restartrequired"));
1441 
1442         // Some filesystems only record last modified in seconds
1443         Thread.sleep(1000);
1444         final File updateFile = new PluginJarBuilder().addFormattedResource("atlassian-plugin.xml",
1445                 "<atlassian-plugin name='Test 2' key='test.restartrequired' pluginsVersion='1'>", "    <plugin-info>", "        <version>2.0</version>",
1446                 "    </plugin-info>", "    <requiresRestart key='foo' />", "</atlassian-plugin>").build(pluginsTestDir);
1447 
1448         origFile.delete();
1449         FileUtils.moveFile(updateFile, origFile);
1450 
1451         manager.scanForNewPlugins();
1452         assertThat(getPluginAccessor().getPlugins(), hasSize(3));
1453         assertNotNull(getPluginAccessor().getPlugin("test.restartrequired"));
1454         assertFalse(getPluginAccessor().isPluginEnabled("test.restartrequired"));
1455         assertEquals(PluginRestartState.INSTALL, getPluginAccessor().getPluginRestartState("test.restartrequired"));
1456 
1457         manager.shutdown();
1458         manager.init();
1459 
1460         assertThat(getPluginAccessor().getEnabledModuleDescriptorsByClass(RequiresRestartModuleDescriptor.class), hasSize(1));
1461         assertEquals(PluginRestartState.NONE, getPluginAccessor().getPluginRestartState("test.restartrequired"));
1462     }
1463 
1464     @Test
1465     public void testRemovePluginThatRequiresRestart() throws PluginParseException, IOException {
1466         createFillAndCleanTempPluginDirectory();
1467         moduleDescriptorFactory.addModuleDescriptor("requiresRestart", RequiresRestartModuleDescriptor.class);
1468 
1469         final File pluginFile = new PluginJarBuilder().addFormattedResource("atlassian-plugin.xml",
1470                 "<atlassian-plugin name='Test 2' key='test.restartrequired' pluginsVersion='1'>", "    <plugin-info>", "        <version>1.0</version>",
1471                 "    </plugin-info>", "    <requiresRestart key='foo' />", "</atlassian-plugin>").build(pluginsTestDir);
1472 
1473         manager = makeClassLoadingPluginManager();
1474 
1475         assertThat(getPluginAccessor().getPlugins(), hasSize(3));
1476         assertNotNull(getPluginAccessor().getPlugin("test.restartrequired"));
1477         assertTrue(getPluginAccessor().isPluginEnabled("test.restartrequired"));
1478         assertThat(getPluginAccessor().getEnabledModuleDescriptorsByClass(RequiresRestartModuleDescriptor.class), hasSize(1));
1479         assertEquals(PluginRestartState.NONE, getPluginAccessor().getPluginRestartState("test.restartrequired"));
1480 
1481         manager.uninstall(getPluginAccessor().getPlugin("test.restartrequired"));
1482 
1483         assertThat(getPluginAccessor().getPlugins(), hasSize(3));
1484         assertNotNull(getPluginAccessor().getPlugin("test.restartrequired"));
1485         assertTrue(getPluginAccessor().isPluginEnabled("test.restartrequired"));
1486         assertEquals(PluginRestartState.REMOVE, getPluginAccessor().getPluginRestartState("test.restartrequired"));
1487         assertThat(getPluginAccessor().getEnabledModuleDescriptorsByClass(RequiresRestartModuleDescriptor.class), hasSize(1));
1488 
1489         manager.shutdown();
1490         manager.init();
1491 
1492         assertFalse(pluginFile.exists());
1493         assertThat(getPluginAccessor().getEnabledModuleDescriptorsByClass(RequiresRestartModuleDescriptor.class), hasSize(0));
1494         assertThat(getPluginAccessor().getPlugins(), hasSize(2));
1495     }
1496 
1497     @Test
1498     public void testRemovePluginThatRequiresRestartThenReverted() throws PluginParseException, IOException {
1499         createFillAndCleanTempPluginDirectory();
1500         moduleDescriptorFactory.addModuleDescriptor("requiresRestart", RequiresRestartModuleDescriptor.class);
1501 
1502         final File pluginFile = new PluginJarBuilder().addFormattedResource("atlassian-plugin.xml",
1503                 "<atlassian-plugin name='Test 2' key='test.restartrequired' pluginsVersion='1'>", "    <plugin-info>", "        <version>1.0</version>",
1504                 "    </plugin-info>", "    <requiresRestart key='foo' />", "</atlassian-plugin>").build(pluginsTestDir);
1505 
1506         manager = makeClassLoadingPluginManager();
1507 
1508         assertThat(getPluginAccessor().getPlugins(), hasSize(3));
1509         assertNotNull(getPluginAccessor().getPlugin("test.restartrequired"));
1510         assertTrue(getPluginAccessor().isPluginEnabled("test.restartrequired"));
1511         assertThat(getPluginAccessor().getEnabledModuleDescriptorsByClass(RequiresRestartModuleDescriptor.class), hasSize(1));
1512         assertEquals(PluginRestartState.NONE, getPluginAccessor().getPluginRestartState("test.restartrequired"));
1513 
1514         manager.uninstall(getPluginAccessor().getPlugin("test.restartrequired"));
1515 
1516         assertThat(getPluginAccessor().getPlugins(), hasSize(3));
1517         assertNotNull(getPluginAccessor().getPlugin("test.restartrequired"));
1518         assertTrue(getPluginAccessor().isPluginEnabled("test.restartrequired"));
1519         assertEquals(PluginRestartState.REMOVE, getPluginAccessor().getPluginRestartState("test.restartrequired"));
1520         assertThat(getPluginAccessor().getEnabledModuleDescriptorsByClass(RequiresRestartModuleDescriptor.class), hasSize(1));
1521 
1522         manager.revertRestartRequiredChange("test.restartrequired");
1523         assertEquals(PluginRestartState.NONE, getPluginAccessor().getPluginRestartState("test.restartrequired"));
1524 
1525         manager.shutdown();
1526         manager.init();
1527 
1528         assertThat(getPluginAccessor().getPlugins(), hasSize(3));
1529         assertNotNull(getPluginAccessor().getPlugin("test.restartrequired"));
1530         assertTrue(getPluginAccessor().isPluginEnabled("test.restartrequired"));
1531         assertThat(getPluginAccessor().getEnabledModuleDescriptorsByClass(RequiresRestartModuleDescriptor.class), hasSize(1));
1532         assertEquals(PluginRestartState.NONE, getPluginAccessor().getPluginRestartState("test.restartrequired"));
1533     }
1534 
1535     @Test
1536     public void testRemovePluginThatRequiresRestartViaSubclass() throws PluginParseException, IOException {
1537         createFillAndCleanTempPluginDirectory();
1538         moduleDescriptorFactory.addModuleDescriptor("requiresRestartSubclass", RequiresRestartSubclassModuleDescriptor.class);
1539 
1540         final File pluginFile = new PluginJarBuilder().addFormattedResource("atlassian-plugin.xml",
1541                 "<atlassian-plugin name='Test 2' key='test.restartrequired' pluginsVersion='1'>", "    <plugin-info>", "        <version>1.0</version>",
1542                 "    </plugin-info>", "    <requiresRestartSubclass key='foo' />", "</atlassian-plugin>").build(pluginsTestDir);
1543 
1544         manager = makeClassLoadingPluginManager();
1545 
1546         assertThat(getPluginAccessor().getPlugins(), hasSize(3));
1547         assertNotNull(getPluginAccessor().getPlugin("test.restartrequired"));
1548         assertTrue(getPluginAccessor().isPluginEnabled("test.restartrequired"));
1549         assertThat(getPluginAccessor().getEnabledModuleDescriptorsByClass(RequiresRestartSubclassModuleDescriptor.class), hasSize(1));
1550         assertEquals(PluginRestartState.NONE, getPluginAccessor().getPluginRestartState("test.restartrequired"));
1551 
1552         manager.uninstall(getPluginAccessor().getPlugin("test.restartrequired"));
1553 
1554         assertThat(getPluginAccessor().getPlugins(), hasSize(3));
1555         assertNotNull(getPluginAccessor().getPlugin("test.restartrequired"));
1556         assertTrue(getPluginAccessor().isPluginEnabled("test.restartrequired"));
1557         assertEquals(PluginRestartState.REMOVE, getPluginAccessor().getPluginRestartState("test.restartrequired"));
1558         assertThat(getPluginAccessor().getEnabledModuleDescriptorsByClass(RequiresRestartSubclassModuleDescriptor.class), hasSize(1));
1559 
1560         manager.shutdown();
1561         manager.init();
1562 
1563         assertFalse(pluginFile.exists());
1564         assertThat(getPluginAccessor().getEnabledModuleDescriptorsByClass(RequiresRestartSubclassModuleDescriptor.class), hasSize(0));
1565         assertThat(getPluginAccessor().getPlugins(), hasSize(2));
1566     }
1567 
1568     @Test
1569     public void testDisableEnableOfPluginThatRequiresRestart() throws PluginParseException, IOException {
1570         createFillAndCleanTempPluginDirectory();
1571         moduleDescriptorFactory.addModuleDescriptor("requiresRestart", RequiresRestartModuleDescriptor.class);
1572 
1573         new PluginJarBuilder().addFormattedResource("atlassian-plugin.xml",
1574                 "<atlassian-plugin name='Test 2' key='test.restartrequired' pluginsVersion='1'>", "    <plugin-info>", "        <version>1.0</version>",
1575                 "    </plugin-info>", "    <requiresRestart key='foo' />", "</atlassian-plugin>").build(pluginsTestDir);
1576 
1577         manager = makeClassLoadingPluginManager();
1578 
1579         assertThat(getPluginAccessor().getPlugins(), hasSize(3));
1580         assertNotNull(getPluginAccessor().getPlugin("test.restartrequired"));
1581         assertTrue(getPluginAccessor().isPluginEnabled("test.restartrequired"));
1582         assertThat(getPluginAccessor().getEnabledModuleDescriptorsByClass(RequiresRestartModuleDescriptor.class), hasSize(1));
1583         assertEquals(PluginRestartState.NONE, getPluginAccessor().getPluginRestartState("test.restartrequired"));
1584 
1585         manager.disablePlugin("test.restartrequired");
1586         assertFalse(getPluginAccessor().isPluginEnabled("test.restartrequired"));
1587         manager.enablePlugins("test.restartrequired");
1588 
1589         assertThat(getPluginAccessor().getPlugins(), hasSize(3));
1590         assertNotNull(getPluginAccessor().getPlugin("test.restartrequired"));
1591         assertTrue(getPluginAccessor().isPluginEnabled("test.restartrequired"));
1592         assertEquals(PluginRestartState.NONE, getPluginAccessor().getPluginRestartState("test.restartrequired"));
1593         assertThat(getPluginAccessor().getEnabledModuleDescriptorsByClass(RequiresRestartModuleDescriptor.class), hasSize(1));
1594     }
1595 
1596     @Test
1597     public void testCannotRemovePluginFromStaticLoader() throws PluginParseException, IOException {
1598         createFillAndCleanTempPluginDirectory();
1599         moduleDescriptorFactory.addModuleDescriptor("requiresRestart", RequiresRestartModuleDescriptor.class);
1600 
1601         directoryPluginLoader = new DirectoryPluginLoader(
1602                 pluginsTestDir,
1603                 ImmutableList.of(new LegacyDynamicPluginFactory(PluginAccessor.Descriptor.FILENAME)),
1604                 pluginEventManager);
1605 
1606         manager = newDefaultPluginManager(directoryPluginLoader, new SinglePluginLoader("test-requiresRestart-plugin.xml"));
1607         manager.init();
1608 
1609         assertThat(getPluginAccessor().getPlugins(), hasSize(3));
1610         assertNotNull(getPluginAccessor().getPlugin("test.atlassian.plugin"));
1611         assertTrue(getPluginAccessor().isPluginEnabled("test.atlassian.plugin"));
1612         assertThat(getPluginAccessor().getEnabledModuleDescriptorsByClass(RequiresRestartModuleDescriptor.class), hasSize(1));
1613         assertEquals(PluginRestartState.NONE, getPluginAccessor().getPluginRestartState("test.atlassian.plugin"));
1614 
1615         try {
1616             manager.uninstall(getPluginAccessor().getPlugin("test.atlassian.plugin"));
1617             fail();
1618         } catch (final PluginException ex) {
1619             // test passed
1620         }
1621 
1622         assertThat(getPluginAccessor().getPlugins(), hasSize(3));
1623         assertNotNull(getPluginAccessor().getPlugin("test.atlassian.plugin"));
1624         assertTrue(getPluginAccessor().isPluginEnabled("test.atlassian.plugin"));
1625         assertEquals(PluginRestartState.NONE, getPluginAccessor().getPluginRestartState("test.atlassian.plugin"));
1626         assertThat(getPluginAccessor().getEnabledModuleDescriptorsByClass(RequiresRestartModuleDescriptor.class), hasSize(1));
1627     }
1628 
1629     private DefaultPluginManager makeClassLoadingPluginManager(PluginLoader... pluginLoaders) throws PluginParseException {
1630         moduleDescriptorFactory.addModuleDescriptor("animal", MockAnimalModuleDescriptor.class);
1631 
1632         directoryPluginLoader = new DirectoryPluginLoader(pluginsTestDir, ImmutableList.of(
1633                 new LegacyDynamicPluginFactory(PluginAccessor.Descriptor.FILENAME)), pluginEventManager);
1634 
1635         final DefaultPluginManager manager = newDefaultPluginManager(Iterables.toArray(ImmutableList.<PluginLoader>builder().add(directoryPluginLoader).addAll(copyOf(pluginLoaders)).build(), PluginLoader.class));
1636 
1637         manager.init();
1638         return manager;
1639     }
1640 
1641     @Test
1642     public void testRemovingPlugins() throws PluginException, IOException {
1643         createFillAndCleanTempPluginDirectory();
1644 
1645         manager = makeClassLoadingPluginManager();
1646         assertThat(getPluginAccessor().getPlugins(), hasSize(2));
1647         final MockAnimalModuleDescriptor moduleDescriptor = (MockAnimalModuleDescriptor) manager.getPluginModule("test.atlassian.plugin.classloaded:paddington");
1648         assertTrue(moduleDescriptor.isEnabled());
1649         final PassListener disabledListener = new PassListener(PluginDisabledEvent.class);
1650         pluginEventManager.register(disabledListener);
1651         final Plugin plugin = getPluginAccessor().getPlugin("test.atlassian.plugin.classloaded");
1652         manager.uninstall(plugin);
1653         assertTrue("Module must have had disable() called before being removed", !moduleDescriptor.isEnabled());
1654 
1655         // uninstalling a plugin should remove it's state completely from the state store - PLUG-13
1656         assertTrue(pluginStateStore.load().getPluginEnabledStateMap(plugin).isEmpty());
1657         // test the deprecated method too
1658         assertTrue(pluginStateStore.load().getPluginStateMap(plugin).isEmpty());
1659 
1660         assertThat(getPluginAccessor().getPlugins(), hasSize(1));
1661         // plugin is no longer available though the plugin manager
1662         assertNull(getPluginAccessor().getPlugin("test.atlassian.plugin.classloaded"));
1663         assertEquals(1, pluginsTestDir.listFiles().length);
1664         disabledListener.assertCalled();
1665     }
1666 
1667     @Test
1668     public void testPluginModuleAvailableAfterInstallation() {
1669         Plugin plugin = mock(Plugin.class);
1670         when(plugin.getKey()).thenReturn("dynPlugin");
1671         when(plugin.isEnabledByDefault()).thenReturn(true);
1672         when(plugin.isDeleteable()).thenReturn(true);
1673         when(plugin.isUninstallable()).thenReturn(true);
1674         when(plugin.getPluginState()).thenReturn(PluginState.ENABLED);
1675         when(plugin.compareTo(any(Plugin.class))).thenReturn(-1);
1676         when(plugin.getDependencies()).thenReturn(new PluginDependencies());
1677 
1678         PluginLoader pluginLoader = mockPluginLoaderForPlugins(plugin);
1679         when(pluginLoader.supportsRemoval()).thenReturn(true);
1680 
1681         manager = newDefaultPluginManager(pluginLoader);
1682         manager.init();
1683 
1684         PluginModuleEnabledListener listener = new PluginModuleEnabledListener();
1685         pluginEventManager.register(listener);
1686         Collection<ModuleDescriptor<?>> mods = new ArrayList<>();
1687         MockModuleDescriptor<String> moduleDescriptor = new MockModuleDescriptor<>(plugin, "foo", "foo");
1688         mods.add(moduleDescriptor);
1689         when(plugin.getModuleDescriptors()).thenReturn(mods);
1690         when(plugin.getModuleDescriptor("foo")).thenReturn((ModuleDescriptor) moduleDescriptor);
1691         pluginEventManager.broadcast(new PluginModuleAvailableEvent(moduleDescriptor));
1692 
1693         assertTrue(getPluginAccessor().isPluginModuleEnabled("dynPlugin:foo"));
1694         assertTrue(listener.called);
1695     }
1696 
1697     @Test
1698     public void testPluginModuleAvailableAfterInstallationButConfiguredToBeDisabled() {
1699         Plugin plugin = mock(Plugin.class);
1700         when(plugin.getKey()).thenReturn("dynPlugin");
1701         when(plugin.isEnabledByDefault()).thenReturn(true);
1702         when(plugin.isDeleteable()).thenReturn(true);
1703         when(plugin.isUninstallable()).thenReturn(true);
1704         when(plugin.getPluginState()).thenReturn(PluginState.ENABLED);
1705         when(plugin.compareTo(any(Plugin.class))).thenReturn(-1);
1706         when(plugin.getDependencies()).thenReturn(new PluginDependencies());
1707 
1708         PluginLoader pluginLoader = mockPluginLoaderForPlugins(plugin);
1709         when(pluginLoader.supportsRemoval()).thenReturn(true);
1710 
1711         manager = newDefaultPluginManager(pluginLoader);
1712         manager.init();
1713 
1714         MockModuleDescriptor<String> moduleDescriptor = new MockModuleDescriptor<>(plugin, "foo", "foo");
1715 
1716         new PluginPersistentStateModifier(pluginStateStore).disable(moduleDescriptor);
1717 
1718         PluginModuleEnabledListener listener = new PluginModuleEnabledListener();
1719         pluginEventManager.register(listener);
1720         Collection<ModuleDescriptor<?>> mods = new ArrayList<>();
1721         mods.add(moduleDescriptor);
1722 
1723         when(plugin.getModuleDescriptors()).thenReturn(mods);
1724         when(plugin.getModuleDescriptor("foo")).thenReturn((ModuleDescriptor) moduleDescriptor);
1725         pluginEventManager.broadcast(new PluginModuleAvailableEvent(moduleDescriptor));
1726 
1727         assertFalse(getPluginAccessor().isPluginModuleEnabled("dynPlugin:foo"));
1728         assertFalse(listener.called);
1729     }
1730 
1731     @Test
1732     public void testPluginModuleUnavailableAfterInstallation() {
1733         Plugin plugin = mock(Plugin.class);
1734         when(plugin.getKey()).thenReturn("dynPlugin");
1735         when(plugin.isEnabledByDefault()).thenReturn(true);
1736         when(plugin.isDeleteable()).thenReturn(true);
1737         when(plugin.isUninstallable()).thenReturn(true);
1738         when(plugin.getPluginState()).thenReturn(PluginState.ENABLED);
1739         when(plugin.compareTo(any(Plugin.class))).thenReturn(-1);
1740         when(plugin.getDependencies()).thenReturn(new PluginDependencies());
1741 
1742         PluginLoader pluginLoader = mockPluginLoaderForPlugins(plugin);
1743         when(pluginLoader.supportsRemoval()).thenReturn(true);
1744         when(pluginLoader.loadAllPlugins(any(ModuleDescriptorFactory.class))).thenReturn(Arrays.asList(plugin));
1745 
1746         manager = newDefaultPluginManager(pluginLoader);
1747         manager.init();
1748 
1749         PluginModuleDisabledListener listener = new PluginModuleDisabledListener();
1750         pluginEventManager.register(listener);
1751         Collection<ModuleDescriptor<?>> mods = new ArrayList<>();
1752         MockModuleDescriptor<String> moduleDescriptor = new MockModuleDescriptor<>(plugin, "foo", "foo");
1753         mods.add(moduleDescriptor);
1754         when(plugin.getModuleDescriptors()).thenReturn(mods);
1755         when(plugin.getModuleDescriptor("foo")).thenReturn((ModuleDescriptor) moduleDescriptor);
1756         pluginEventManager.broadcast(new PluginModuleAvailableEvent(moduleDescriptor));
1757         assertTrue(getPluginAccessor().isPluginModuleEnabled("dynPlugin:foo"));
1758         assertFalse(listener.called);
1759         pluginEventManager.broadcast(new PluginModuleUnavailableEvent(moduleDescriptor));
1760         assertTrue(listener.called);
1761     }
1762 
1763     @Test
1764     public void testPluginModuleDisabledOnStartupUnavailableAfterInstallation() {
1765         PluginLoader pluginLoader = mock(PluginLoader.class);
1766         when(pluginLoader.supportsRemoval()).thenReturn(true);
1767         Plugin plugin = mock(Plugin.class);
1768         when(plugin.getKey()).thenReturn("dynPlugin");
1769         when(plugin.isEnabledByDefault()).thenReturn(true);
1770         when(plugin.isDeleteable()).thenReturn(true);
1771         when(plugin.isUninstallable()).thenReturn(true);
1772         when(plugin.getPluginState()).thenReturn(PluginState.ENABLED);
1773         when(plugin.compareTo(any(Plugin.class))).thenReturn(-1);
1774         when(plugin.getDependencies()).thenReturn(new PluginDependencies());
1775         when(pluginLoader.loadAllPlugins(any(ModuleDescriptorFactory.class))).thenReturn(Arrays.asList(plugin));
1776 
1777         manager = newDefaultPluginManager(pluginLoader);
1778         manager.init();
1779 
1780         PluginModuleDisabledListener listener = new PluginModuleDisabledListener();
1781         pluginEventManager.register(listener);
1782         Collection<ModuleDescriptor<?>> mods = new ArrayList<>();
1783         MockModuleDescriptor<String> moduleDescriptor = new MockModuleDescriptor<>(plugin, "foo", "foo");
1784         mods.add(moduleDescriptor);
1785         when(plugin.getModuleDescriptors()).thenReturn(mods);
1786         when(plugin.getModuleDescriptor("foo")).thenReturn((ModuleDescriptor) moduleDescriptor);
1787 
1788         // ideally, we'd set enabledByDefault to false, but that requires calling init with a valid Element.
1789         // Luckily, setting the persistent state of the module to disabled also does what we need.
1790         pluginStateStore.save(PluginPersistentState.Builder.create(pluginStateStore.load()).setEnabled(
1791                 moduleDescriptor, false).toState());
1792 
1793         pluginEventManager.broadcast(new PluginModuleAvailableEvent(moduleDescriptor));
1794         assertFalse(getPluginAccessor().isPluginModuleEnabled("dynPlugin:foo"));
1795         assertFalse(listener.called);
1796         pluginEventManager.broadcast(new PluginModuleUnavailableEvent(moduleDescriptor));
1797         assertFalse(listener.called);
1798     }
1799 
1800     @Test
1801     public void testPluginContainerUnavailable() {
1802         Plugin plugin = mock(Plugin.class);
1803         when(plugin.getKey()).thenReturn("dynPlugin");
1804         when(plugin.isEnabledByDefault()).thenReturn(true);
1805         when(plugin.isDeleteable()).thenReturn(true);
1806         when(plugin.isUninstallable()).thenReturn(true);
1807         when(plugin.getPluginState()).thenReturn(PluginState.ENABLED);
1808         when(plugin.compareTo(any(Plugin.class))).thenReturn(-1);
1809         Collection<ModuleDescriptor<?>> mods = new ArrayList<>();
1810         MockModuleDescriptor<String> moduleDescriptor = new MockModuleDescriptor<>(plugin, "foo", "foo");
1811         mods.add(moduleDescriptor);
1812         when(plugin.getModuleDescriptors()).thenReturn(mods);
1813         when(plugin.getModuleDescriptor("foo")).thenReturn((ModuleDescriptor) moduleDescriptor);
1814         when(plugin.getDependencies()).thenReturn(new PluginDependencies());
1815 
1816         PluginLoader pluginLoader = mockPluginLoaderForPlugins(plugin);
1817         when(pluginLoader.supportsRemoval()).thenReturn(true);
1818 
1819         manager = newDefaultPluginManager(pluginLoader);
1820         manager.init();
1821 
1822         PluginDisabledListener listener = new PluginDisabledListener();
1823         PluginModuleDisabledListener moduleDisabledListener = new PluginModuleDisabledListener();
1824         pluginEventManager.register(listener);
1825         pluginEventManager.register(moduleDisabledListener);
1826         when(plugin.getPluginState()).thenReturn(PluginState.DISABLED);
1827         pluginEventManager.broadcast(new PluginContainerUnavailableEvent("dynPlugin"));
1828         //Fix in behaviour, if the plugin is already disabled and you try to disable it there should be no event called
1829         assertFalse(getPluginAccessor().isPluginEnabled("dynPlugin"));
1830         assertFalse(listener.called);
1831         assertFalse(moduleDisabledListener.called);
1832     }
1833 
1834     @Test
1835     public void testNonRemovablePlugins() throws PluginParseException {
1836         moduleDescriptorFactory.addModuleDescriptor("animal", MockAnimalModuleDescriptor.class);
1837         moduleDescriptorFactory.addModuleDescriptor("mineral", MockMineralModuleDescriptor.class);
1838 
1839         manager = newDefaultPluginManager(new SinglePluginLoader("test-atlassian-plugin.xml"));
1840         manager.init();
1841 
1842         final Plugin plugin = getPluginAccessor().getPlugin("test.atlassian.plugin");
1843         assertFalse(plugin.isUninstallable());
1844         assertNotNull(plugin.getResourceAsStream("test-atlassian-plugin.xml"));
1845 
1846         try {
1847             manager.uninstall(plugin);
1848             fail("Where was the exception?");
1849         } catch (final PluginException p) {
1850         }
1851     }
1852 
1853     @Test
1854     public void testNonDeletablePlugins() throws PluginException, IOException {
1855         createFillAndCleanTempPluginDirectory();
1856 
1857         manager = makeClassLoadingPluginManager();
1858         assertThat(getPluginAccessor().getPlugins(), hasSize(2));
1859 
1860         // Set plugin file can't be deleted.
1861         final Plugin pluginToRemove = spy(getPluginAccessor().getPlugin("test.atlassian.plugin.classloaded"));
1862         when(pluginToRemove.isDeleteable()).thenReturn(false);
1863 
1864         // Disable plugin module before uninstall
1865         final MockAnimalModuleDescriptor moduleDescriptor = (MockAnimalModuleDescriptor) getPluginAccessor().getPluginModule("test.atlassian.plugin.classloaded:paddington");
1866         assertTrue(moduleDescriptor.isEnabled());
1867 
1868         manager.uninstall(pluginToRemove);
1869 
1870         assertFalse("Module must have had disable() called before being removed", moduleDescriptor.isEnabled());
1871         assertThat(getPluginAccessor().getPlugins(), hasSize(1));
1872         assertNull(getPluginAccessor().getPlugin("test.atlassian.plugin.classloaded"));
1873         assertEquals(2, pluginsTestDir.listFiles().length);
1874     }
1875 
1876     @Test
1877     public void testInvalidationOfDynamicResourceCache() throws IOException, PluginException {
1878         createFillAndCleanTempPluginDirectory();
1879 
1880         manager = makeClassLoadingPluginManager();
1881 
1882         checkResources(manager, true, true);
1883         manager.disablePlugin("test.atlassian.plugin.classloaded");
1884         checkResources(manager, false, false);
1885         manager.enablePlugins("test.atlassian.plugin.classloaded");
1886         checkResources(manager, true, true);
1887         manager.uninstall(getPluginAccessor().getPlugin("test.atlassian.plugin.classloaded"));
1888         checkResources(manager, false, false);
1889         //restore paddington to test plugins dir
1890         FileUtils.copyDirectory(pluginsDirectory, pluginsTestDir);
1891         manager.scanForNewPlugins();
1892         checkResources(manager, true, true);
1893     }
1894 
1895     @Test
1896     public void testValidatePlugin() throws PluginParseException {
1897         final DynamicPluginLoader mockLoader = mock(DynamicPluginLoader.class);
1898         when(mockLoader.isDynamicPluginLoader()).thenReturn(true);
1899 
1900         manager = new DefaultPluginManager(pluginStateStore, ImmutableList.of(mockLoader), moduleDescriptorFactory, new DefaultPluginEventManager());
1901 
1902         final PluginArtifact mockPluginJar = mock(PluginArtifact.class);
1903         final PluginArtifact pluginArtifact = mockPluginJar;
1904         when(mockLoader.canLoad(pluginArtifact)).thenReturn("foo");
1905 
1906         final String key = manager.validatePlugin(pluginArtifact);
1907         assertEquals("foo", key);
1908         verify(mockLoader).canLoad(pluginArtifact);
1909     }
1910 
1911     @Test
1912     public void testValidatePluginWithNoDynamicLoaders() throws PluginParseException {
1913         final PluginLoader loader = mock(PluginLoader.class);
1914         final DefaultPluginManager manager = new DefaultPluginManager(pluginStateStore, ImmutableList.of(loader), moduleDescriptorFactory, new DefaultPluginEventManager());
1915 
1916         final PluginArtifact pluginArtifact = mock(PluginArtifact.class);
1917         try {
1918             manager.validatePlugin(pluginArtifact);
1919             fail("Should have thrown exception");
1920         } catch (final IllegalStateException ex) {
1921             // test passed
1922         }
1923     }
1924 
1925     @Test
1926     public void testInvalidationOfDynamicClassCache() throws IOException, PluginException {
1927         createFillAndCleanTempPluginDirectory();
1928 
1929         manager = makeClassLoadingPluginManager();
1930 
1931         checkClasses(manager, true);
1932         manager.disablePlugin("test.atlassian.plugin.classloaded");
1933         checkClasses(manager, false);
1934         manager.enablePlugins("test.atlassian.plugin.classloaded");
1935         checkClasses(manager, true);
1936         manager.uninstall(getPluginAccessor().getPlugin("test.atlassian.plugin.classloaded"));
1937         checkClasses(manager, false);
1938         //restore paddington to test plugins dir
1939         FileUtils.copyDirectory(pluginsDirectory, pluginsTestDir);
1940         manager.scanForNewPlugins();
1941         checkClasses(manager, true);
1942     }
1943 
1944     @Test
1945     public void testInstallTwoPluginsButOneFailsToEnableAModuleAndThenFailsToDisableAModule() {
1946         final PluginLoader mockPluginLoader = mock(PluginLoader.class);
1947         final ModuleDescriptor<Object> failEnableModuleDescriptor = mockFailingModuleDescriptor("foo:bar", FAIL_TO_ENABLE);
1948         final ModuleDescriptor<Object> failDisableModuleDescriptor = mockFailingModuleDescriptor("foo:buzz", FAIL_TO_DISABLE);
1949 
1950         Plugin badPlugin = mockStaticPlugin("foo", failDisableModuleDescriptor, failEnableModuleDescriptor);
1951 
1952         final AbstractModuleDescriptor<?> goodModuleDescriptor = mock(AbstractModuleDescriptor.class);
1953         when(goodModuleDescriptor.getKey()).thenReturn("baz");
1954         when(goodModuleDescriptor.getPluginKey()).thenReturn("good");
1955         when(goodModuleDescriptor.isEnabledByDefault()).thenReturn(true);
1956         Plugin goodPlugin = mockStaticPlugin("good", goodModuleDescriptor);
1957 
1958         when(mockPluginLoader.loadAllPlugins(isA(ModuleDescriptorFactory.class))).thenReturn(Lists.newArrayList(badPlugin, goodPlugin));
1959 
1960         manager = newDefaultPluginManager(mockPluginLoader);
1961         manager.init();
1962 
1963         assertThat(getPluginAccessor().getPlugins(), hasSize(2));
1964         assertThat(getPluginAccessor().getEnabledPlugins(), hasSize(1));
1965         verify(goodModuleDescriptor).enabled();
1966     }
1967 
1968     private <T> void checkResources(final PluginAccessor manager, final boolean canGetGlobal, final boolean canGetModule) throws IOException {
1969         InputStream is = manager.getDynamicResourceAsStream("icon.gif");
1970         assertEquals(canGetGlobal, is != null);
1971         IOUtils.closeQuietly(is);
1972         is = manager.getDynamicResourceAsStream("bear/paddington.vm");
1973         assertEquals(canGetModule, is != null);
1974         IOUtils.closeQuietly(is);
1975     }
1976 
1977     private void checkClasses(final PluginAccessor manager, final boolean canGet) {
1978         try {
1979             manager.getClassLoader().loadClass("com.atlassian.plugin.mock.MockPaddington");
1980             if (!canGet) {
1981                 fail("Class in plugin was successfully loaded");
1982             }
1983         } catch (final ClassNotFoundException e) {
1984             if (canGet) {
1985                 fail(e.getMessage());
1986             }
1987         }
1988     }
1989 
1990     @Test
1991     public void testUninstallPluginClearsState() throws IOException {
1992         createFillAndCleanTempPluginDirectory();
1993 
1994         manager = makeClassLoadingPluginManager();
1995 
1996         checkClasses(manager, true);
1997         final Plugin plugin = getPluginAccessor().getPlugin("test.atlassian.plugin.classloaded");
1998 
1999         final ModuleDescriptor<?> module = plugin.getModuleDescriptor("paddington");
2000         assertTrue(getPluginAccessor().isPluginModuleEnabled(module.getCompleteKey()));
2001         manager.disablePluginModule(module.getCompleteKey());
2002         assertFalse(getPluginAccessor().isPluginModuleEnabled(module.getCompleteKey()));
2003         manager.uninstall(plugin);
2004         assertFalse(getPluginAccessor().isPluginModuleEnabled(module.getCompleteKey()));
2005         assertTrue(pluginStateStore.load().getPluginEnabledStateMap(plugin).isEmpty());
2006         // test the deprecated method too
2007         assertTrue(pluginStateStore.load().getPluginStateMap(plugin).isEmpty());
2008     }
2009 
2010     @Test
2011     public void testUninstallDisabledPlugin() throws IOException {
2012         createFillAndCleanTempPluginDirectory();
2013 
2014         manager = makeClassLoadingPluginManager();
2015 
2016         final Plugin plugin = getPluginAccessor().getPlugin("test.atlassian.plugin.classloaded");
2017 
2018         manager.disablePlugin(plugin.getKey());
2019         assertEquals(PluginState.DISABLED, plugin.getPluginState());
2020         manager.uninstall(plugin);
2021         assertEquals(PluginState.UNINSTALLED, plugin.getPluginState());
2022     }
2023 
2024     /**
2025      * Also relevant to {@link TestDefaultPluginManagerWithCachingPluginAccessor}
2026      */
2027     @Test
2028     public void testDisabledPluginClearsModulesByClass() throws IOException {
2029         final Class<MockAnimalModuleDescriptor> descriptorClass = MockAnimalModuleDescriptor.class;
2030         moduleDescriptorFactory.addModuleDescriptor("animal", descriptorClass);
2031         moduleDescriptorFactory.addModuleDescriptor("mineral", MockMineralModuleDescriptor.class);
2032 
2033         manager = newDefaultPluginManager(new SinglePluginLoaderWithRemoval("test-atlassian-plugin.xml"));
2034         manager.init();
2035 
2036         final List<MockAnimalModuleDescriptor> animalDescriptors =
2037                 getPluginAccessor().getEnabledModuleDescriptorsByClass(descriptorClass);
2038         assertThat(animalDescriptors, hasSize(1));
2039         final MockAnimalModuleDescriptor descriptor = animalDescriptors.get(0);
2040         final Class<?> moduleClass = descriptor.getModule().getClass();
2041         assertThat(getPluginAccessor().getEnabledModulesByClass(moduleClass), hasSize(1));
2042 
2043         manager.disablePlugin(descriptor.getPlugin().getKey());
2044 
2045         assertThat(getPluginAccessor().getEnabledModuleDescriptorsByClass(descriptorClass), empty());
2046         assertThat(getPluginAccessor().getEnabledModulesByClass(moduleClass), empty());
2047     }
2048 
2049     /**
2050      * Also relevant to {@link TestDefaultPluginManagerWithCachingPluginAccessor}: prove the cache doesn't need to listen to
2051      * pluginUninstalled: pluginDisabled is good enough
2052      */
2053     @Test
2054     public void testUninstallPluginClearsModulesByClass() throws IOException {
2055         final Class<MockAnimalModuleDescriptor> descriptorClass = MockAnimalModuleDescriptor.class;
2056         moduleDescriptorFactory.addModuleDescriptor("animal", descriptorClass);
2057         moduleDescriptorFactory.addModuleDescriptor("mineral", MockMineralModuleDescriptor.class);
2058 
2059         manager = newDefaultPluginManager(new SinglePluginLoaderWithRemoval("test-atlassian-plugin.xml"));
2060         manager.init();
2061 
2062         final List<MockAnimalModuleDescriptor> animalDescriptors =
2063                 getPluginAccessor().getEnabledModuleDescriptorsByClass(descriptorClass);
2064         assertThat(animalDescriptors, hasSize(1));
2065         final MockAnimalModuleDescriptor descriptor = animalDescriptors.get(0);
2066         final Class<?> moduleClass = descriptor.getModule().getClass();
2067         assertThat(getPluginAccessor().getEnabledModulesByClass(moduleClass), hasSize(1));
2068 
2069         manager.uninstall(descriptor.getPlugin());
2070 
2071         assertThat(getPluginAccessor().getEnabledModuleDescriptorsByClass(descriptorClass), empty());
2072         assertThat(getPluginAccessor().getEnabledModulesByClass(moduleClass), empty());
2073     }
2074 
2075     @Test
2076     public void testGetPluginWithNullKey() {
2077         manager = newDefaultPluginManager();
2078         manager.init();
2079         try {
2080             getPluginAccessor().getPlugin(null);
2081             fail();
2082         } catch (IllegalArgumentException ex) {
2083             // test passed
2084         }
2085     }
2086 
2087     @Test
2088     public void checkPluginInternal() {
2089         final PluginLoader pluginLoader = mock(PluginLoader.class);
2090         final PluginInternal plugin = mock(PluginInternal.class);
2091 
2092         manager = newDefaultPluginManager(pluginLoader);
2093 
2094         assertThat(manager.checkPluginInternal(plugin), is(plugin));
2095     }
2096 
2097     @Test
2098     public void checkPluginInternalNotSo() {
2099         final PluginLoader pluginLoader = mock(PluginLoader.class);
2100         final Plugin plugin = mock(Plugin.class);
2101 
2102         manager = newDefaultPluginManager(pluginLoader);
2103 
2104         when(plugin.toString()).thenReturn("shitPlugin");
2105 
2106         expectedException.expect(IllegalArgumentException.class);
2107         expectedException.expectMessage("shitPlugin");
2108 
2109         manager.checkPluginInternal(plugin);
2110     }
2111 
2112     @Test
2113     public void getDynamicModules() {
2114         final PluginLoader pluginLoader = mock(PluginLoader.class);
2115         final PluginInternal plugin = mock(PluginInternal.class);
2116         final Iterable<ModuleDescriptor<?>> dynamicModules = mock(Iterable.class);
2117 
2118         manager = newDefaultPluginManager(pluginLoader);
2119 
2120         when(plugin.getDynamicModuleDescriptors()).thenReturn(dynamicModules);
2121 
2122         assertThat(getPluginAccessor().getDynamicModules(plugin), is(dynamicModules));
2123     }
2124 
2125     /**
2126      * Dummy plugin loader that reports that removal is supported and returns plugins that report that they can
2127      * be uninstalled.
2128      */
2129     private static class SinglePluginLoaderWithRemoval extends SinglePluginLoader {
2130         public SinglePluginLoaderWithRemoval(final String resource) {
2131             super(resource);
2132         }
2133 
2134         public boolean supportsRemoval() {
2135 
2136             return true;
2137         }
2138 
2139         public void removePlugin(final Plugin plugin) throws PluginException {
2140             plugins = Collections.emptyList();
2141         }
2142 
2143         protected StaticPlugin getNewPlugin() {
2144             return new StaticPlugin() {
2145                 public boolean isUninstallable() {
2146                     return true;
2147                 }
2148             };
2149         }
2150     }
2151 
2152     private static class SinglePluginLoaderWithAddition extends SinglePluginLoader {
2153         PluginLoader addPluginLoader;
2154 
2155         public SinglePluginLoaderWithAddition(String resource) {
2156             super(resource);
2157         }
2158 
2159         @Override
2160         public boolean supportsAddition() {
2161             return true;
2162         }
2163 
2164         public void setAddPluginLoader(PluginLoader addPluginLoader) {
2165             this.addPluginLoader = addPluginLoader;
2166         }
2167 
2168         @Override
2169         public Iterable<Plugin> loadFoundPlugins(ModuleDescriptorFactory moduleDescriptorFactory) {
2170             return addPluginLoader == null ? emptySet() : addPluginLoader.loadAllPlugins(moduleDescriptorFactory);
2171         }
2172     }
2173 
2174     class NothingModuleDescriptor extends MockUnusedModuleDescriptor {
2175     }
2176 
2177     @RequiresRestart
2178     public static class RequiresRestartModuleDescriptor extends MockUnusedModuleDescriptor {
2179     }
2180 
2181     // A subclass of a module descriptor that @RequiresRestart; should inherit the annotation
2182     public static class RequiresRestartSubclassModuleDescriptor extends RequiresRestartModuleDescriptor {
2183     }
2184 
2185     private class MultiplePluginLoader implements PluginLoader {
2186         private final String[] descriptorPaths;
2187 
2188         public MultiplePluginLoader(final String... descriptorPaths) {
2189             this.descriptorPaths = descriptorPaths;
2190         }
2191 
2192         public Iterable<Plugin> loadAllPlugins(final ModuleDescriptorFactory moduleDescriptorFactory) throws PluginParseException {
2193             final ImmutableList.Builder<Plugin> result = ImmutableList.builder();
2194             for (final String path : descriptorPaths) {
2195                 final SinglePluginLoader loader = new SinglePluginLoader(path);
2196                 result.addAll(loader.loadAllPlugins(moduleDescriptorFactory));
2197             }
2198             return result.build();
2199         }
2200 
2201         public boolean supportsAddition() {
2202             return false;
2203         }
2204 
2205         public boolean supportsRemoval() {
2206             return false;
2207         }
2208 
2209         public Iterable<Plugin> loadFoundPlugins(final ModuleDescriptorFactory moduleDescriptorFactory) throws PluginParseException {
2210             throw new UnsupportedOperationException("This PluginLoader does not support addition.");
2211         }
2212 
2213         public void removePlugin(final Plugin plugin) throws PluginException {
2214             throw new UnsupportedOperationException("This PluginLoader does not support addition.");
2215         }
2216 
2217         @Override
2218         public boolean isDynamicPluginLoader() {
2219             return false;
2220         }
2221 
2222         @Override
2223         public ModuleDescriptor<?> createModule(final Plugin plugin, final Element module, final ModuleDescriptorFactory moduleDescriptorFactory) {
2224             return null;
2225         }
2226     }
2227 
2228     private static class DynamicSinglePluginLoader extends SinglePluginLoader implements PluginLoader, DynamicPluginLoader {
2229         private final AtomicBoolean canLoad = new AtomicBoolean(false);
2230 
2231         private final String key;
2232 
2233         public DynamicSinglePluginLoader(final String key, final String resource) {
2234             super(resource);
2235             this.key = key;
2236         }
2237 
2238         @Override
2239         public boolean isDynamicPluginLoader() {
2240             return true;
2241         }
2242 
2243         public String canLoad(final PluginArtifact pluginArtifact) throws PluginParseException {
2244             return canLoad.get() ? key : null;
2245         }
2246 
2247         public boolean supportsAddition() {
2248             return true;
2249         }
2250 
2251         @Override
2252         public Iterable<Plugin> loadAllPlugins(ModuleDescriptorFactory moduleDescriptorFactory) {
2253             if (canLoad.get()) {
2254                 return super.loadAllPlugins(moduleDescriptorFactory);
2255             } else {
2256                 return ImmutableList.of();
2257             }
2258         }
2259 
2260         @Override
2261         public Iterable<Plugin> loadFoundPlugins(final ModuleDescriptorFactory moduleDescriptorFactory) {
2262             if (canLoad.get()) {
2263                 return super.loadAllPlugins(moduleDescriptorFactory);
2264             } else {
2265                 return ImmutableList.of();
2266             }
2267         }
2268     }
2269 
2270     public static class PluginModuleEnabledListener {
2271         public volatile boolean called;
2272 
2273         @PluginEventListener
2274         public void onEnable(PluginModuleEnabledEvent event) {
2275             called = true;
2276         }
2277     }
2278 
2279     public static class PluginModuleDisabledListener {
2280         public volatile boolean called;
2281 
2282         @PluginEventListener
2283         public void onDisable(PluginModuleDisabledEvent event) {
2284             called = true;
2285         }
2286     }
2287 
2288     public static class PluginDisabledListener {
2289         public volatile boolean called;
2290 
2291         @PluginEventListener
2292         public void onDisable(PluginDisabledEvent event) {
2293             called = true;
2294         }
2295     }
2296 
2297 
2298     private static class DelayedSinglePluginLoader extends SinglePluginLoader {
2299 
2300         public DelayedSinglePluginLoader(String resource) {
2301             super(resource);
2302         }
2303 
2304         @Override
2305         public Iterable<Plugin> loadAllPlugins(ModuleDescriptorFactory moduleDescriptorFactory) {
2306             try {
2307                 Thread.sleep(10);
2308                 return super.loadAllPlugins(moduleDescriptorFactory);
2309             } catch (InterruptedException e) {
2310                 throw new RuntimeException(e);
2311             }
2312         }
2313     }
2314 }