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